From 340cab5f6db537afa3ff9735f04707f131575947 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Tue, 9 Jun 2026 16:24:50 -0400 Subject: [PATCH 01/20] =?UTF-8?q?feat(data):=20@alchemy/data=20MVP=20?= =?UTF-8?q?=E2=80=94=20viem=20actions=20over=20shared=20REST/RPC=20channel?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vertical-slice prototype of the Data SDK architecture: - 3 actions, one per seam: portfolio.getTokensByAddress (REST, multi-network body via AlchemyRestClient), nft.getNftsForOwner (REST, network-scoped URL with per-request override), transfers.getAssetTransfers (JSON-RPC over AlchemyTransport, override via derived transport instance) - dataActions decorator + createAlchemyDataClient convenience wrapper - common: resolveNetwork accepting viem Chain | slug | CAIP-2, derived from the existing daikon-generated ALCHEMY_RPC_MAPPING; AlchemyRestClient exported See packages/data/README.md for scope and deliberate omissions. Co-Authored-By: Claude Fable 5 --- packages/common/src/index.ts | 17 ++- .../common/src/networks/networkRegistry.ts | 138 ++++++++++++++++++ .../tests/networks/networkRegistry.test.ts | 58 ++++++++ packages/data/README.md | 45 ++++++ packages/data/inject-version.ts | 19 +++ packages/data/package.json | 63 ++++++++ .../data/src/actions/nft/getNftsForOwner.ts | 44 ++++++ .../actions/portfolio/getTokensByAddress.ts | 49 +++++++ .../actions/transfers/getAssetTransfers.ts | 50 +++++++ packages/data/src/client.ts | 79 ++++++++++ packages/data/src/dataClient.test.ts | 126 ++++++++++++++++ packages/data/src/decorator.ts | 67 +++++++++ packages/data/src/index.ts | 21 +++ packages/data/src/internal/clientHelpers.ts | 74 ++++++++++ packages/data/src/internal/endpoints.ts | 23 +++ packages/data/src/schema/rest.ts | 42 ++++++ packages/data/src/schema/rpc.ts | 30 ++++ packages/data/src/types.ts | 103 +++++++++++++ packages/data/src/version.ts | 3 + packages/data/tsconfig.build.json | 17 +++ packages/data/tsconfig.json | 9 ++ packages/data/vitest.config.ts | 14 ++ 22 files changed, 1087 insertions(+), 4 deletions(-) create mode 100644 packages/common/src/networks/networkRegistry.ts create mode 100644 packages/common/tests/networks/networkRegistry.test.ts create mode 100644 packages/data/README.md create mode 100644 packages/data/inject-version.ts create mode 100644 packages/data/package.json create mode 100644 packages/data/src/actions/nft/getNftsForOwner.ts create mode 100644 packages/data/src/actions/portfolio/getTokensByAddress.ts create mode 100644 packages/data/src/actions/transfers/getAssetTransfers.ts create mode 100644 packages/data/src/client.ts create mode 100644 packages/data/src/dataClient.test.ts create mode 100644 packages/data/src/decorator.ts create mode 100644 packages/data/src/index.ts create mode 100644 packages/data/src/internal/clientHelpers.ts create mode 100644 packages/data/src/internal/endpoints.ts create mode 100644 packages/data/src/schema/rest.ts create mode 100644 packages/data/src/schema/rpc.ts create mode 100644 packages/data/src/types.ts create mode 100644 packages/data/src/version.ts create mode 100644 packages/data/tsconfig.build.json create mode 100644 packages/data/tsconfig.json create mode 100644 packages/data/vitest.config.ts diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index ef3322c49e..2daf8cc61f 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -2,10 +2,10 @@ export type * from "./transport/alchemy.js"; export { alchemyTransport, isAlchemyTransport } from "./transport/alchemy.js"; -// http -- Revisit exporting this once it has a use case. -// export type * from "./rest/restClient.js"; -// export type * from "./rest/types.js"; -// export { AlchemyRestClient } from "./rest/restClient.js"; +// http -- exported for @alchemy/data (REST channel); hardening tracked in the data SDK plan +export type * from "./rest/restClient.js"; +export type * from "./rest/types.js"; +export { AlchemyRestClient } from "./rest/restClient.js"; // chain registry utilities export { @@ -14,6 +14,15 @@ export { getSupportedChainIds, } from "./transport/chainRegistry.js"; +// network registry (slug / CAIP-2 / viem Chain resolution) +export type { + KnownAlchemyNetwork, + AlchemyNetwork, + NetworkInput, + ResolvedNetwork, +} from "./networks/networkRegistry.js"; +export { resolveNetwork } from "./networks/networkRegistry.js"; + // utils export type * from "./utils/types.js"; export { assertNever } from "./utils/assertNever.js"; diff --git a/packages/common/src/networks/networkRegistry.ts b/packages/common/src/networks/networkRegistry.ts new file mode 100644 index 0000000000..1c5f7b887c --- /dev/null +++ b/packages/common/src/networks/networkRegistry.ts @@ -0,0 +1,138 @@ +import type { Chain } from "viem"; +import { BaseError } from "../errors/BaseError.js"; +import { ALCHEMY_RPC_MAPPING } from "../transport/chainRegistry.js"; + +/** + * Known Alchemy network slugs for autocomplete. + * + * TODO(data-sdk): generate this union from daikon via the ws-tools CLI in the + * same pass that generates ALCHEMY_RPC_MAPPING, so it is never hand-maintained. + * This subset exists to prove the MVP only. + */ +export type KnownAlchemyNetwork = + | "eth-mainnet" + | "eth-sepolia" + | "base-mainnet" + | "base-sepolia" + | "polygon-mainnet" + | "polygon-amoy" + | "arb-mainnet" + | "arb-sepolia" + | "opt-mainnet" + | "opt-sepolia" + | "solana-mainnet" + | "solana-devnet"; + +/** + * An Alchemy network identifier. Known slugs get autocomplete; arbitrary + * strings are accepted as an escape hatch so new networks work without an + * SDK release. + */ +export type AlchemyNetwork = KnownAlchemyNetwork | (string & {}); + +/** + * Any accepted network input: a viem Chain, an Alchemy network slug + * (e.g. "eth-mainnet"), or a CAIP-2 identifier (e.g. "eip155:1", + * "solana:mainnet"). + */ +export type NetworkInput = Chain | AlchemyNetwork; + +/** + * A resolved network: the Alchemy slug used for URL construction and REST + * payloads, plus the numeric chain ID when one exists (EVM only). + */ +export type ResolvedNetwork = { + slug: string; + chainId?: number; +}; + +// Solana networks have no numeric chain ID; CAIP-2 aliases match the +// identifiers wallet-apis already uses. +const SOLANA_SLUG_BY_CAIP2: Record = { + "solana:mainnet": "solana-mainnet", + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "solana-mainnet", + "solana:devnet": "solana-devnet", + "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": "solana-devnet", +}; + +const SOLANA_SLUGS = new Set(Object.values(SOLANA_SLUG_BY_CAIP2)); + +const SLUG_PATTERN = /^[a-z0-9-]+$/; + +// The slug is already present in every daikon-generated registry URL +// (https://{slug}.g.alchemy.com/v2); derive the slug<->chainId maps from it +// rather than maintaining a second copy. Once ws-tools emits structured +// entries ({ slug, chainId, caip2 }), these derivations go away. +const slugByChainId = new Map(); +const chainIdBySlug = new Map(); +for (const [chainId, url] of Object.entries(ALCHEMY_RPC_MAPPING)) { + const slug = new URL(url).hostname.split(".")[0]; + if (slug) { + slugByChainId.set(Number(chainId), slug); + chainIdBySlug.set(slug, Number(chainId)); + } +} + +/** + * Resolves any accepted network input — viem Chain, Alchemy network slug, or + * CAIP-2 identifier — to the Alchemy network slug (and chain ID when one + * exists). All three forms resolve against the same daikon-generated registry. + * + * @example + * ```ts + * import { mainnet } from "viem/chains"; + * resolveNetwork(mainnet); // { slug: "eth-mainnet", chainId: 1 } + * resolveNetwork("eth-mainnet"); // { slug: "eth-mainnet", chainId: 1 } + * resolveNetwork("eip155:1"); // { slug: "eth-mainnet", chainId: 1 } + * resolveNetwork("solana:mainnet"); // { slug: "solana-mainnet" } + * ``` + * + * @param {NetworkInput} input The network to resolve + * @returns {ResolvedNetwork} The resolved slug and optional chain ID + */ +export function resolveNetwork(input: NetworkInput): ResolvedNetwork { + if (typeof input === "object") { + const slug = slugByChainId.get(input.id); + if (!slug) { + throw new BaseError( + `Chain ${input.id} (${input.name}) is not in the Alchemy network registry. ` + + `Pass an Alchemy network slug (e.g. "eth-mainnet") instead.`, + ); + } + return { slug, chainId: input.id }; + } + + if (input.startsWith("eip155:")) { + const chainId = Number(input.slice("eip155:".length)); + const slug = slugByChainId.get(chainId); + if (!Number.isInteger(chainId) || !slug) { + throw new BaseError( + `CAIP-2 identifier "${input}" is not in the Alchemy network registry.`, + ); + } + return { slug, chainId }; + } + + if (input.startsWith("solana:")) { + const slug = SOLANA_SLUG_BY_CAIP2[input]; + if (!slug) { + throw new BaseError( + `CAIP-2 identifier "${input}" is not a known Solana network.`, + ); + } + return { slug }; + } + + if (!SLUG_PATTERN.test(input)) { + throw new BaseError( + `"${input}" is not a valid Alchemy network slug (expected lowercase letters, digits, and hyphens).`, + ); + } + + // Known slugs resolve a chain ID; unknown slugs are the escape hatch for + // networks newer than the shipped registry (URL is composed directly). + if (SOLANA_SLUGS.has(input)) { + return { slug: input }; + } + return { slug: input, chainId: chainIdBySlug.get(input) }; +} diff --git a/packages/common/tests/networks/networkRegistry.test.ts b/packages/common/tests/networks/networkRegistry.test.ts new file mode 100644 index 0000000000..b899d4801e --- /dev/null +++ b/packages/common/tests/networks/networkRegistry.test.ts @@ -0,0 +1,58 @@ +import { mainnet, arbitrumSepolia } from "viem/chains"; +import { describe, expect, it } from "vitest"; +import { resolveNetwork } from "../../src/networks/networkRegistry.js"; + +describe("resolveNetwork", () => { + it("resolves a viem chain via the registry", () => { + expect(resolveNetwork(mainnet)).toEqual({ + slug: "eth-mainnet", + chainId: 1, + }); + expect(resolveNetwork(arbitrumSepolia)).toEqual({ + slug: "arb-sepolia", + chainId: 421614, + }); + }); + + it("resolves a known slug to the same entry as the chain", () => { + expect(resolveNetwork("eth-mainnet")).toEqual(resolveNetwork(mainnet)); + }); + + it("resolves CAIP-2 identifiers", () => { + expect(resolveNetwork("eip155:1")).toEqual({ + slug: "eth-mainnet", + chainId: 1, + }); + expect(resolveNetwork("solana:mainnet")).toEqual({ + slug: "solana-mainnet", + }); + expect(resolveNetwork("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")).toEqual({ + slug: "solana-mainnet", + }); + }); + + it("passes through unknown slugs as the escape hatch", () => { + expect(resolveNetwork("some-new-chain")).toEqual({ + slug: "some-new-chain", + chainId: undefined, + }); + }); + + it("rejects inputs that are not valid hostname labels", () => { + expect(() => resolveNetwork("Not A Slug!")).toThrow(); + expect(() => resolveNetwork("eth-mainnet/../evil")).toThrow(); + }); + + it("rejects chains and CAIP-2 ids missing from the registry", () => { + expect(() => + resolveNetwork({ + id: 999999001, + name: "Unknown", + nativeCurrency: { name: "X", symbol: "X", decimals: 18 }, + rpcUrls: { default: { http: [] } }, + }), + ).toThrow(); + expect(() => resolveNetwork("eip155:999999001")).toThrow(); + expect(() => resolveNetwork("solana:unknownref")).toThrow(); + }); +}); diff --git a/packages/data/README.md b/packages/data/README.md new file mode 100644 index 0000000000..c6b81db92e --- /dev/null +++ b/packages/data/README.md @@ -0,0 +1,45 @@ +# @alchemy/data (MVP) + +A vertical-slice prototype of the Data APIs SDK, built to prove the architecture +before scaling to the full v1 surface (Portfolio, Prices, NFT, Token, Transfers). + +## What this proves + +One method per seam, not full coverage: + +| Method | Channel | What it demonstrates | +| --- | --- | --- | +| `portfolio.getTokensByAddress` | REST → global `api.g.alchemy.com/data/v1` | Multi-network request bodies via `AlchemyRestClient`; networks are payload, the client's chain is not involved | +| `nft.getNftsForOwner` | REST → `{network}.g.alchemy.com/nft/v3` | Network-scoped endpoint resolution with per-request `network` override falling back to the client default | +| `transfers.getAssetTransfers` | JSON-RPC → `AlchemyTransport` | Plain viem action; network override derives a transport instance from `client.transport.config` | + +Plus the two entry points: + +```ts +// Data-only developers (no viem knowledge required) +const data = createAlchemyDataClient({ apiKey, network: "eth-mainnet" }); + +// Developers already on a viem client with an Alchemy transport +const client = createClient({ chain: mainnet, transport: alchemyTransport({ apiKey }) }) + .extend(dataActions); +``` + +Network inputs accept all three formats everywhere, resolved by +`resolveNetwork()` in `@alchemy/common`: a viem `Chain`, an Alchemy slug +(`"eth-mainnet"`), or CAIP-2 (`"eip155:1"`, `"solana:mainnet"`). The slug ↔ +chain-ID mapping is derived from the existing daikon-generated +`ALCHEMY_RPC_MAPPING` — no second registry. + +## Companion changes in @alchemy/common + +- `networks/networkRegistry.ts`: `resolveNetwork` + network types (slug map + derived from the registry URLs; to be emitted by ws-tools properly) +- `AlchemyRestClient` is now exported (was written for signer v5 but unexported) + +## Deliberately out of scope (tracked in the data SDK scope plan) + +- Codegen from docs OpenAPI/OpenRPC specs (types here are hand-written) +- Rest client hardening: retries, timeouts, request-id, first-class query params +- Pagination iterators, error normalization, the SDK manifest, remaining methods +- ws-tools generator change to emit `{ slug, chainId, caip2 }` entries + + the `KnownAlchemyNetwork` union diff --git a/packages/data/inject-version.ts b/packages/data/inject-version.ts new file mode 100644 index 0000000000..05e1caae82 --- /dev/null +++ b/packages/data/inject-version.ts @@ -0,0 +1,19 @@ +import { readFileSync, writeFileSync } from "fs"; +import { dirname, resolve } from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const VERSION_FILE_PATH = "src/version.ts"; + +const packageJSON = JSON.parse(readFileSync("./package.json").toString()); + +writeFileSync( + resolve(__dirname, VERSION_FILE_PATH), + `// This file is autogenerated by inject-version.ts. Any changes will be +// overwritten on commit! +export const VERSION = "${packageJSON.version}"; +`, +); +console.log(`Wrote version to ${VERSION_FILE_PATH}.`); diff --git a/packages/data/package.json b/packages/data/package.json new file mode 100644 index 0000000000..f5e7e6dd4e --- /dev/null +++ b/packages/data/package.json @@ -0,0 +1,63 @@ +{ + "name": "@alchemy/data", + "version": "0.0.0", + "description": "Alchemy Data APIs SDK (Portfolio, Prices, NFT, Token, Transfers) — MVP", + "author": "Alchemy", + "license": "MIT", + "private": false, + "type": "module", + "main": "./dist/esm/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", + "typings": "./dist/types/index.d.ts", + "sideEffects": false, + "files": [ + "dist", + "src/**/*.ts", + "!dist/**/*.tsbuildinfo", + "!vitest.config.ts", + "!.env", + "!src/**/*.test.ts", + "!src/**/*.test-d.ts", + "!src/__tests__/**/*" + ], + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.js", + "default": "./dist/esm/index.js" + }, + "./package.json": "./package.json" + }, + "scripts": { + "prebuild": "tsx ./inject-version.ts", + "build": "pnpm run clean && pnpm run build:esm && pnpm run build:types", + "build:esm": "tsc --project tsconfig.build.json --outDir ./dist/esm", + "build:types": "tsc --project tsconfig.build.json --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", + "clean": "rm -rf ./dist", + "test": "vitest", + "test:run": "vitest run" + }, + "devDependencies": { + "typescript-template": "workspace:*", + "viem": "^2.45.0" + }, + "dependencies": { + "@alchemy/common": "workspace:*" + }, + "peerDependencies": { + "viem": "^2.45.0" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/alchemyplatform/aa-sdk.git" + }, + "bugs": { + "url": "https://github.com/alchemyplatform/aa-sdk/issues" + }, + "homepage": "https://github.com/alchemyplatform/aa-sdk#readme" +} diff --git a/packages/data/src/actions/nft/getNftsForOwner.ts b/packages/data/src/actions/nft/getNftsForOwner.ts new file mode 100644 index 0000000000..ad038ba464 --- /dev/null +++ b/packages/data/src/actions/nft/getNftsForOwner.ts @@ -0,0 +1,44 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetNftsForOwnerParams, + GetNftsForOwnerResult, +} from "../../types.js"; + +/** + * Fetches NFTs owned by an address on a single network. The network is + * resolved per request: an explicit `network` param wins, otherwise the + * client's configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftsForOwnerParams} params Owner address, optional network override, and filters + * @returns {Promise} The owned NFTs and pagination cursor + */ +export async function getNftsForOwner( + client: DataClient, + params: GetNftsForOwnerParams, +): Promise { + const { network, owner, contractAddresses, ...rest } = params; + const { slug } = resolveRequestNetwork(client, network); + + const query = new URLSearchParams({ owner }); + for (const [key, value] of Object.entries(rest)) { + if (value != null) query.set(key, String(value)); + } + for (const address of contractAddresses ?? []) { + query.append("contractAddresses[]", address); + } + + const restClient = getRestClient(client, getNftApiUrl(slug)); + // TODO(common-hardening): AlchemyRestClient should take query params + // first-class instead of this cast; tracked in the data SDK plan. + return restClient.request({ + route: `getNFTsForOwner?${query.toString()}` as "getNFTsForOwner", + method: "GET", + }); +} diff --git a/packages/data/src/actions/portfolio/getTokensByAddress.ts b/packages/data/src/actions/portfolio/getTokensByAddress.ts new file mode 100644 index 0000000000..dde71c5bb0 --- /dev/null +++ b/packages/data/src/actions/portfolio/getTokensByAddress.ts @@ -0,0 +1,49 @@ +import { resolveNetwork } from "@alchemy/common"; +import { DATA_API_URL } from "../../internal/endpoints.js"; +import { + getRestClient, + type DataClient, +} from "../../internal/clientHelpers.js"; +import type { PortfolioRestSchema } from "../../schema/rest.js"; +import type { + GetTokensByAddressParams, + GetTokensByAddressResult, +} from "../../types.js"; + +/** + * Fetches tokens (with optional metadata and prices) for one or more addresses + * across one or more networks in a single call. This is a multi-network + * request against the global Data API: networks travel in the request body, + * so the client's chain is not involved. + * + * @example + * ```ts + * import { mainnet, base } from "viem/chains"; + * + * const tokens = await getTokensByAddress(client, { + * addresses: [{ address: "0x...", networks: [mainnet, base, "solana-mainnet"] }], + * }); + * ``` + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetTokensByAddressParams} params Addresses paired with networks, plus options + * @returns {Promise} Token balances, metadata, and prices + */ +export async function getTokensByAddress( + client: DataClient, + params: GetTokensByAddressParams, +): Promise { + const { addresses, ...options } = params; + const restClient = getRestClient(client, DATA_API_URL); + return restClient.request({ + route: "assets/tokens/by-address", + method: "POST", + body: { + ...options, + addresses: addresses.map(({ address, networks }) => ({ + address, + networks: networks.map((n) => resolveNetwork(n).slug), + })), + }, + }); +} diff --git a/packages/data/src/actions/transfers/getAssetTransfers.ts b/packages/data/src/actions/transfers/getAssetTransfers.ts new file mode 100644 index 0000000000..f1726e9987 --- /dev/null +++ b/packages/data/src/actions/transfers/getAssetTransfers.ts @@ -0,0 +1,50 @@ +import { alchemyTransport } from "@alchemy/common"; +import { getRpcUrl } from "../../internal/endpoints.js"; +import { + getTransportConfig, + resolveRequestNetwork, + type DataClient, +} from "../../internal/clientHelpers.js"; +import type { DataRpcSchema } from "../../schema/rpc.js"; +import type { + GetAssetTransfersParams, + GetAssetTransfersResult, +} from "../../types.js"; +import type { EIP1193RequestFn } from "viem"; + +/** + * Fetches historical asset transfers (external, internal, token) for the + * resolved network via the `alchemy_getAssetTransfers` JSON-RPC method. + * + * Without a `network` override this is a plain viem action over the client's + * Alchemy transport. With an override, a transport instance is derived from + * the client's transport config and pointed at the override network's RPC URL + * — the same mechanism the transport's tracing support uses. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetAssetTransfersParams} params Transfer filters and optional network override + * @returns {Promise} The matching transfers and pagination cursor + */ +export async function getAssetTransfers( + client: DataClient, + params: GetAssetTransfersParams, +): Promise { + const { network, ...rpcParams } = params; + + const request = (() => { + if (!network) { + return client.request as EIP1193RequestFn; + } + const { slug } = resolveRequestNetwork(client, network); + const derived = alchemyTransport({ + ...getTransportConfig(client), + url: getRpcUrl(slug), + })({ retryCount: 0 }); + return derived.request as EIP1193RequestFn; + })(); + + return request({ + method: "alchemy_getAssetTransfers", + params: [rpcParams], + }); +} diff --git a/packages/data/src/client.ts b/packages/data/src/client.ts new file mode 100644 index 0000000000..c231c60cd3 --- /dev/null +++ b/packages/data/src/client.ts @@ -0,0 +1,79 @@ +import { + alchemyTransport, + resolveNetwork, + type AlchemyTransportConfig, + type NetworkInput, +} from "@alchemy/common"; +import { createClient, type Chain, type Client } from "viem"; +import { getRpcUrl } from "./internal/endpoints.js"; +import { dataActions, type DataActions } from "./decorator.js"; +import type { AlchemyTransport } from "@alchemy/common"; + +export type AlchemyDataClientOptions = Pick< + AlchemyTransportConfig, + "apiKey" | "jwt" | "url" | "fetchOptions" +> & { + /** + * Default network for network-scoped calls (NFT, Token, Transfers). + * Accepts a viem Chain, an Alchemy slug ("eth-mainnet"), or CAIP-2 + * ("eip155:1"). Multi-network calls (Portfolio) take explicit networks + * per request. + */ + network?: NetworkInput; +}; + +export type AlchemyDataClient = Client & + DataActions; + +/** + * Creates a Data API client. This is a convenience wrapper over + * `createClient` + `alchemyTransport` + the {@link dataActions} decorator — + * developers already holding a viem client with an Alchemy transport can use + * `client.extend(dataActions)` instead and get the identical behavior. + * + * @example + * ```ts + * import { createAlchemyDataClient } from "@alchemy/data"; + * + * const data = createAlchemyDataClient({ + * apiKey: process.env.ALCHEMY_API_KEY, + * network: "eth-mainnet", // or `mainnet` from viem/chains, or "eip155:1" + * }); + * + * const nfts = await data.nft.getNftsForOwner({ owner: "0x..." }); + * ``` + * + * @param {AlchemyDataClientOptions} options Auth (apiKey/jwt/proxy url) and default network + * @returns {AlchemyDataClient} A viem client extended with the Data API actions + */ +export function createAlchemyDataClient( + options: AlchemyDataClientOptions, +): AlchemyDataClient { + const { network, ...transportConfig } = options; + + // A viem Chain default flows through the transport's registry resolution + // untouched. Slug/CAIP-2 defaults are carried on a minimal chain definition + // (custom.alchemyNetwork) and the RPC URL is resolved up front — chain ID 0 + // mirrors how wallet-apis models Solana chains. + const chain: Chain | undefined = (() => { + if (network == null) return undefined; + if (typeof network === "object") return network; + const resolved = resolveNetwork(network); + return { + id: resolved.chainId ?? 0, + name: resolved.slug, + nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, + rpcUrls: { default: { http: [] } }, + custom: { alchemyNetwork: resolved.slug }, + }; + })(); + + const transport = alchemyTransport({ + ...transportConfig, + ...(chain && chain.custom?.alchemyNetwork && !transportConfig.url + ? { url: getRpcUrl(chain.custom.alchemyNetwork as string) } + : {}), + }); + + return createClient({ chain, transport }).extend(dataActions); +} diff --git a/packages/data/src/dataClient.test.ts b/packages/data/src/dataClient.test.ts new file mode 100644 index 0000000000..db288c8402 --- /dev/null +++ b/packages/data/src/dataClient.test.ts @@ -0,0 +1,126 @@ +import { alchemyTransport } from "@alchemy/common"; +import { createClient } from "viem"; +import { mainnet, base } from "viem/chains"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { createAlchemyDataClient } from "./client.js"; +import { dataActions } from "./decorator.js"; + +const fetchMock = vi.fn(); + +const jsonResponse = (body: unknown) => + new Response(JSON.stringify(body), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + +beforeEach(() => { + vi.stubGlobal("fetch", fetchMock); + fetchMock.mockReset(); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe("createAlchemyDataClient", () => { + it("routes multi-network portfolio calls to the global Data API with resolved slugs", async () => { + fetchMock.mockResolvedValueOnce(jsonResponse({ data: { tokens: [] } })); + + const data = createAlchemyDataClient({ + apiKey: "test-key", + network: "eth-mainnet", + }); + + await data.portfolio.getTokensByAddress({ + addresses: [ + // all three network input formats in one request + { address: "0xabc", networks: [mainnet, "base-mainnet", "eip155:10"] }, + { address: "0xdef", networks: ["solana-mainnet"] }, + ], + }); + + const [url, init] = fetchMock.mock.calls[0]!; + expect(url).toBe( + "https://api.g.alchemy.com/data/v1/assets/tokens/by-address", + ); + expect(init.method).toBe("POST"); + expect(JSON.parse(init.body)).toEqual({ + addresses: [ + { + address: "0xabc", + networks: ["eth-mainnet", "base-mainnet", "opt-mainnet"], + }, + { address: "0xdef", networks: ["solana-mainnet"] }, + ], + }); + }); + + it("routes NFT calls to the network-scoped URL, honoring per-request override", async () => { + fetchMock.mockImplementation(async () => + jsonResponse({ ownedNfts: [], totalCount: 0 }), + ); + + const data = createAlchemyDataClient({ + apiKey: "test-key", + network: "eth-mainnet", + }); + + await data.nft.getNftsForOwner({ owner: "0xabc" }); + expect(String(fetchMock.mock.calls[0]![0])).toBe( + "https://eth-mainnet.g.alchemy.com/nft/v3/getNFTsForOwner?owner=0xabc", + ); + + await data.nft.getNftsForOwner({ owner: "0xabc", network: base }); + expect(String(fetchMock.mock.calls[1]![0])).toBe( + "https://base-mainnet.g.alchemy.com/nft/v3/getNFTsForOwner?owner=0xabc", + ); + }); + + it("routes transfers over JSON-RPC, deriving a transport for network overrides", async () => { + fetchMock.mockImplementation(async () => + jsonResponse({ jsonrpc: "2.0", id: 1, result: { transfers: [] } }), + ); + + const data = createAlchemyDataClient({ + apiKey: "test-key", + network: "eth-mainnet", + }); + + const result = await data.transfers.getAssetTransfers({ + category: ["erc20"], + }); + expect(result).toEqual({ transfers: [] }); + expect(String(fetchMock.mock.calls[0]![0])).toContain( + "https://eth-mainnet.g.alchemy.com/v2", + ); + const rpcBody = JSON.parse(fetchMock.mock.calls[0]![1].body); + expect(rpcBody.method).toBe("alchemy_getAssetTransfers"); + expect(rpcBody.params).toEqual([{ category: ["erc20"] }]); + + await data.transfers.getAssetTransfers({ + category: ["erc20"], + network: "arb-mainnet", + }); + expect(String(fetchMock.mock.calls[1]![0])).toContain( + "https://arb-mainnet.g.alchemy.com/v2", + ); + }); +}); + +describe("dataActions decorator", () => { + it("behaves identically on a vanilla viem client with an Alchemy transport", async () => { + fetchMock.mockImplementation(async () => + jsonResponse({ ownedNfts: [], totalCount: 0 }), + ); + + const client = createClient({ + chain: mainnet, + transport: alchemyTransport({ apiKey: "test-key" }), + }).extend(dataActions); + + await client.nft.getNftsForOwner({ owner: "0xabc" }); + expect(String(fetchMock.mock.calls[0]![0])).toBe( + "https://eth-mainnet.g.alchemy.com/nft/v3/getNFTsForOwner?owner=0xabc", + ); + }); +}); diff --git a/packages/data/src/decorator.ts b/packages/data/src/decorator.ts new file mode 100644 index 0000000000..726868fad4 --- /dev/null +++ b/packages/data/src/decorator.ts @@ -0,0 +1,67 @@ +import type { + GetAssetTransfersParams, + GetAssetTransfersResult, + GetNftsForOwnerParams, + GetNftsForOwnerResult, + GetTokensByAddressParams, + GetTokensByAddressResult, +} from "./types.js"; +import type { DataClient } from "./internal/clientHelpers.js"; +import { getTokensByAddress } from "./actions/portfolio/getTokensByAddress.js"; +import { getNftsForOwner } from "./actions/nft/getNftsForOwner.js"; +import { getAssetTransfers } from "./actions/transfers/getAssetTransfers.js"; + +/** The namespaced Data API actions attached by the {@link dataActions} decorator. */ +export type DataActions = { + portfolio: { + getTokensByAddress: ( + params: GetTokensByAddressParams, + ) => Promise; + }; + nft: { + getNftsForOwner: ( + params: GetNftsForOwnerParams, + ) => Promise; + }; + transfers: { + getAssetTransfers: ( + params: GetAssetTransfersParams, + ) => Promise; + }; +}; + +/** + * A viem client decorator that attaches the Data API actions, grouped by + * namespace, to any client configured with an Alchemy transport. + * + * @example + * ```ts + * import { createClient } from "viem"; + * import { mainnet } from "viem/chains"; + * import { alchemyTransport } from "@alchemy/common"; + * import { dataActions } from "@alchemy/data"; + * + * const client = createClient({ + * chain: mainnet, + * transport: alchemyTransport({ apiKey: "..." }), + * }).extend(dataActions); + * + * const nfts = await client.nft.getNftsForOwner({ owner: "0x..." }); + * ``` + * + * @param {DataClient} client The client to decorate + * @returns {DataActions} The namespaced Data API actions + */ +export function dataActions(client: DataClient): DataActions { + return { + portfolio: { + getTokensByAddress: (params) => getTokensByAddress(client, params), + }, + nft: { + getNftsForOwner: (params) => getNftsForOwner(client, params), + }, + transfers: { + getAssetTransfers: (params) => getAssetTransfers(client, params), + }, + }; +} diff --git a/packages/data/src/index.ts b/packages/data/src/index.ts new file mode 100644 index 0000000000..71e7da0a3b --- /dev/null +++ b/packages/data/src/index.ts @@ -0,0 +1,21 @@ +// client +export type { AlchemyDataClient, AlchemyDataClientOptions } from "./client.js"; +export { createAlchemyDataClient } from "./client.js"; + +// decorator +export type { DataActions } from "./decorator.js"; +export { dataActions } from "./decorator.js"; + +// actions (individually importable for tree-shaking / composability) +export { getTokensByAddress } from "./actions/portfolio/getTokensByAddress.js"; +export { getNftsForOwner } from "./actions/nft/getNftsForOwner.js"; +export { getAssetTransfers } from "./actions/transfers/getAssetTransfers.js"; + +// schemas +export type { DataRpcSchema } from "./schema/rpc.js"; +export type { PortfolioRestSchema, NftRestSchema } from "./schema/rest.js"; + +// types +export type * from "./types.js"; + +export { VERSION } from "./version.js"; diff --git a/packages/data/src/internal/clientHelpers.ts b/packages/data/src/internal/clientHelpers.ts new file mode 100644 index 0000000000..8f5fa05ec2 --- /dev/null +++ b/packages/data/src/internal/clientHelpers.ts @@ -0,0 +1,74 @@ +import { + AlchemyRestClient, + resolveNetwork, + type AlchemyTransport, + type AlchemyTransportConfig, + type NetworkInput, + type ResolvedNetwork, + type RestRequestSchema, +} from "@alchemy/common"; +import { BaseError } from "@alchemy/common"; +import type { Chain, Client } from "viem"; + +/** The minimal client shape data actions operate on. */ +export type DataClient = Client; + +/** + * Reads the AlchemyTransportConfig back off an instantiated client transport. + * The transport attaches its creation config to the transport value precisely + * to support deriving new instances (tracing relies on the same mechanism). + * + * @param {DataClient} client The client to read from + * @returns {AlchemyTransportConfig} The transport's creation config + */ +export function getTransportConfig(client: DataClient): AlchemyTransportConfig { + return client.transport.config as AlchemyTransportConfig; +} + +/** + * Resolves the network for a request: explicit per-request input wins, + * otherwise the client's chain (set at client creation). The + * `custom.alchemyNetwork` field carries slug-configured defaults (e.g. Solana + * or escape-hatch networks that have no registry chain ID). + * + * @param {DataClient} client The client whose chain provides the default + * @param {NetworkInput} [override] Per-request network override + * @returns {ResolvedNetwork} The resolved network + */ +export function resolveRequestNetwork( + client: DataClient, + override?: NetworkInput, +): ResolvedNetwork { + if (override != null) { + return resolveNetwork(override); + } + const chain = client.chain; + const customSlug = (chain?.custom as { alchemyNetwork?: string } | undefined) + ?.alchemyNetwork; + if (customSlug) { + return resolveNetwork(customSlug); + } + if (chain) { + return resolveNetwork(chain); + } + throw new BaseError( + "No network available: pass `network` on the request or configure the client with a network/chain.", + ); +} + +/** + * Builds a typed REST client for a service base URL, reusing the auth + * (apiKey/JWT) and headers from the viem client's Alchemy transport so REST + * and JSON-RPC requests share one connection config. + * + * @param {DataClient} client The client whose transport supplies auth + * @param {string} url The service base URL + * @returns {AlchemyRestClient} A typed REST client + */ +export function getRestClient( + client: DataClient, + url: string, +): AlchemyRestClient { + const { apiKey, jwt } = getTransportConfig(client); + return new AlchemyRestClient({ apiKey, jwt, url }); +} diff --git a/packages/data/src/internal/endpoints.ts b/packages/data/src/internal/endpoints.ts new file mode 100644 index 0000000000..a870a9182f --- /dev/null +++ b/packages/data/src/internal/endpoints.ts @@ -0,0 +1,23 @@ +// Per-service endpoint resolution. The slug comes from resolveNetwork in +// @alchemy/common, so all three network input formats land here identically. + +/** Global, chain-agnostic Data API (multi-network request bodies). */ +export const DATA_API_URL = "https://api.g.alchemy.com/data/v1"; + +/** + * Network-scoped NFT v3 base URL. + * + * @param {string} slug The Alchemy network slug + * @returns {string} The NFT v3 base URL for that network + */ +export const getNftApiUrl = (slug: string): string => + `https://${slug}.g.alchemy.com/nft/v3`; + +/** + * Network-scoped JSON-RPC base URL. + * + * @param {string} slug The Alchemy network slug + * @returns {string} The RPC base URL for that network + */ +export const getRpcUrl = (slug: string): string => + `https://${slug}.g.alchemy.com/v2`; diff --git a/packages/data/src/schema/rest.ts b/packages/data/src/schema/rest.ts new file mode 100644 index 0000000000..ec748ec1de --- /dev/null +++ b/packages/data/src/schema/rest.ts @@ -0,0 +1,42 @@ +import type { RestRequestSchema } from "@alchemy/common"; +import type { + GetNftsForOwnerResult, + GetTokensByAddressResult, +} from "../types.js"; + +// NOTE(mvp): hand-written RestRequestSchema entries; the production plan +// generates these from the docs repo's bundled OpenAPI specs via +// `pnpm generate:sdk` (openapi-typescript), keyed by the SDK manifest. + +/** Routes served by the global Data API (https://api.g.alchemy.com/data/v1). */ +export type PortfolioRestSchema = readonly [ + { + Route: "assets/tokens/by-address"; + Method: "POST"; + Body: { + addresses: Array<{ address: string; networks: string[] }>; + withMetadata?: boolean; + withPrices?: boolean; + includeNativeTokens?: boolean; + includeErc20Tokens?: boolean; + }; + Response: GetTokensByAddressResult; + }, +]; + +/** Routes served by the network-scoped NFT API ({network}.g.alchemy.com/nft/v3). */ +export type NftRestSchema = readonly [ + { + Route: "getNFTsForOwner"; + Method: "GET"; + Body?: undefined; + Response: GetNftsForOwnerResult; + }, +]; + +// Compile-time check that the schemas satisfy the shared constraint. +type _AssertPortfolio = PortfolioRestSchema extends RestRequestSchema + ? true + : never; +type _AssertNft = NftRestSchema extends RestRequestSchema ? true : never; +export type _SchemaAssertions = [_AssertPortfolio, _AssertNft]; diff --git a/packages/data/src/schema/rpc.ts b/packages/data/src/schema/rpc.ts new file mode 100644 index 0000000000..6894686cd5 --- /dev/null +++ b/packages/data/src/schema/rpc.ts @@ -0,0 +1,30 @@ +import type { GetAssetTransfersResult } from "../types.js"; + +// NOTE(mvp): hand-written RpcSchema entry; the production plan generates these +// from the docs repo's bundled OpenRPC specs (alchemy_getAssetTransfers et al). + +export type GetAssetTransfersRpcParams = { + fromBlock?: string; + toBlock?: string; + fromAddress?: string; + toAddress?: string; + excludeZeroValue?: boolean; + category: string[]; + contractAddresses?: string[]; + order?: "asc" | "desc"; + withMetadata?: boolean; + pageKey?: string; + maxCount?: string; +}; + +/** + * viem RpcSchema entries for the Data JSON-RPC methods. Attach to a client to + * get typed `client.request({ method: "alchemy_getAssetTransfers", ... })`. + */ +export type DataRpcSchema = [ + { + Method: "alchemy_getAssetTransfers"; + Parameters: [GetAssetTransfersRpcParams]; + ReturnType: GetAssetTransfersResult; + }, +]; diff --git a/packages/data/src/types.ts b/packages/data/src/types.ts new file mode 100644 index 0000000000..9c1dffdf06 --- /dev/null +++ b/packages/data/src/types.ts @@ -0,0 +1,103 @@ +import type { NetworkInput } from "@alchemy/common"; + +// NOTE(mvp): these param/result types are hand-written against the docs specs +// to prove the architecture. The production plan generates them from the docs +// repo's bundled OpenAPI/OpenRPC output (see data-sdk-scope-plan.md), with +// reviewed, semver-bearing public names aliasing generated internals. + +/** An address paired with the networks to query it on. */ +export interface PortfolioAddressEntry { + address: string; + /** Networks to query; accepts viem Chains, Alchemy slugs, or CAIP-2 ids. */ + networks: NetworkInput[]; +} + +export interface GetTokensByAddressParams { + addresses: PortfolioAddressEntry[]; + withMetadata?: boolean; + withPrices?: boolean; + includeNativeTokens?: boolean; + includeErc20Tokens?: boolean; +} + +export interface PortfolioToken { + address: string; + network: string; + tokenAddress: string | null; + tokenBalance: string; + tokenMetadata?: { + name?: string | null; + symbol?: string | null; + decimals?: number | null; + logo?: string | null; + }; + tokenPrices?: Array<{ + currency: string; + value: string; + lastUpdatedAt: string; + }>; +} + +export interface GetTokensByAddressResult { + data: { + tokens: PortfolioToken[]; + pageKey?: string; + }; +} + +export interface GetNftsForOwnerParams { + owner: string; + /** Overrides the client-level network for this request. */ + network?: NetworkInput; + contractAddresses?: string[]; + withMetadata?: boolean; + pageKey?: string; + pageSize?: number; +} + +export interface GetNftsForOwnerResult { + ownedNfts: Array>; + totalCount: number; + pageKey?: string; + validAt?: Record; +} + +export interface GetAssetTransfersParams { + /** Overrides the client-level network for this request. */ + network?: NetworkInput; + fromBlock?: string; + toBlock?: string; + fromAddress?: string; + toAddress?: string; + excludeZeroValue?: boolean; + category: Array< + "external" | "internal" | "erc20" | "erc721" | "erc1155" | "specialnft" + >; + contractAddresses?: string[]; + order?: "asc" | "desc"; + withMetadata?: boolean; + pageKey?: string; + maxCount?: string; +} + +export interface AssetTransfer { + category: string; + blockNum: string; + from: string; + to: string | null; + value: number | null; + asset: string | null; + hash: string; + uniqueId: string; + rawContract: { + value: string | null; + address: string | null; + decimal: string | null; + }; + metadata?: { blockTimestamp: string }; +} + +export interface GetAssetTransfersResult { + transfers: AssetTransfer[]; + pageKey?: string; +} diff --git a/packages/data/src/version.ts b/packages/data/src/version.ts new file mode 100644 index 0000000000..128c5e1f2d --- /dev/null +++ b/packages/data/src/version.ts @@ -0,0 +1,3 @@ +// This file is autogenerated by inject-version.ts. Any changes will be +// overwritten on commit! +export const VERSION = "0.0.0"; diff --git a/packages/data/tsconfig.build.json b/packages/data/tsconfig.build.json new file mode 100644 index 0000000000..a9532f066c --- /dev/null +++ b/packages/data/tsconfig.build.json @@ -0,0 +1,17 @@ +{ + "extends": "typescript-template/build.json", + "exclude": [ + "node_modules", + "**/*/__tests__", + "**/*/*.test.ts", + "**/*/*.test-d.ts", + "**/*/*.e2e.test.ts", + "vitest.config.ts", + "vitest.config.e2e.ts" + ], + "include": ["src"], + "compilerOptions": { + "sourceMap": true, + "jsx": "react-jsx" + } +} diff --git a/packages/data/tsconfig.json b/packages/data/tsconfig.json new file mode 100644 index 0000000000..ad56d6c756 --- /dev/null +++ b/packages/data/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "typescript-template/base.json", + "compilerOptions": { + "jsx": "preserve", + "paths": { + "~test/*": ["../../.vitest/src/*"] + } + } +} diff --git a/packages/data/vitest.config.ts b/packages/data/vitest.config.ts new file mode 100644 index 0000000000..bf4b3f619a --- /dev/null +++ b/packages/data/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineProject } from "vitest/config"; + +export default defineProject({ + test: { + name: "alchemy/data", + globals: true, + include: ["src/**/*.test.ts"], + // Unit tests stub fetch; no anvil/bundler setup needed (same as common) + setupFiles: [], + globalSetup: undefined, + testTimeout: 10_000, + hookTimeout: 10_000, + }, +}); From 4d1b9a9d68f3a6f91e461966d6db2061dc63a1ec Mon Sep 17 00:00:00 2001 From: blake duncan Date: Tue, 9 Jun 2026 16:36:08 -0400 Subject: [PATCH 02/20] chore(data-apis): rename @alchemy/data to @alchemy/data-apis Matches the wallet-apis package naming standard. Co-Authored-By: Claude Fable 5 --- packages/common/src/index.ts | 2 +- packages/{data => data-apis}/README.md | 2 +- .../{data => data-apis}/inject-version.ts | 0 packages/{data => data-apis}/package.json | 2 +- .../src/actions/nft/getNftsForOwner.ts | 0 .../actions/portfolio/getTokensByAddress.ts | 0 .../actions/transfers/getAssetTransfers.ts | 0 packages/{data => data-apis}/src/client.ts | 2 +- .../src/dataClient.test.ts | 0 packages/{data => data-apis}/src/decorator.ts | 2 +- packages/{data => data-apis}/src/index.ts | 0 .../src/internal/clientHelpers.ts | 0 .../src/internal/endpoints.ts | 0 .../{data => data-apis}/src/schema/rest.ts | 0 .../{data => data-apis}/src/schema/rpc.ts | 0 packages/{data => data-apis}/src/types.ts | 0 packages/{data => data-apis}/src/version.ts | 0 .../{data => data-apis}/tsconfig.build.json | 0 packages/{data => data-apis}/tsconfig.json | 0 packages/{data => data-apis}/vitest.config.ts | 2 +- pnpm-lock.yaml | 102 +++++++----------- 21 files changed, 45 insertions(+), 69 deletions(-) rename packages/{data => data-apis}/README.md (98%) rename packages/{data => data-apis}/inject-version.ts (100%) rename packages/{data => data-apis}/package.json (98%) rename packages/{data => data-apis}/src/actions/nft/getNftsForOwner.ts (100%) rename packages/{data => data-apis}/src/actions/portfolio/getTokensByAddress.ts (100%) rename packages/{data => data-apis}/src/actions/transfers/getAssetTransfers.ts (100%) rename packages/{data => data-apis}/src/client.ts (97%) rename packages/{data => data-apis}/src/dataClient.test.ts (100%) rename packages/{data => data-apis}/src/decorator.ts (97%) rename packages/{data => data-apis}/src/index.ts (100%) rename packages/{data => data-apis}/src/internal/clientHelpers.ts (100%) rename packages/{data => data-apis}/src/internal/endpoints.ts (100%) rename packages/{data => data-apis}/src/schema/rest.ts (100%) rename packages/{data => data-apis}/src/schema/rpc.ts (100%) rename packages/{data => data-apis}/src/types.ts (100%) rename packages/{data => data-apis}/src/version.ts (100%) rename packages/{data => data-apis}/tsconfig.build.json (100%) rename packages/{data => data-apis}/tsconfig.json (100%) rename packages/{data => data-apis}/vitest.config.ts (91%) diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 2daf8cc61f..2d927a9751 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -2,7 +2,7 @@ export type * from "./transport/alchemy.js"; export { alchemyTransport, isAlchemyTransport } from "./transport/alchemy.js"; -// http -- exported for @alchemy/data (REST channel); hardening tracked in the data SDK plan +// http -- exported for @alchemy/data-apis (REST channel); hardening tracked in the data SDK plan export type * from "./rest/restClient.js"; export type * from "./rest/types.js"; export { AlchemyRestClient } from "./rest/restClient.js"; diff --git a/packages/data/README.md b/packages/data-apis/README.md similarity index 98% rename from packages/data/README.md rename to packages/data-apis/README.md index c6b81db92e..050316e193 100644 --- a/packages/data/README.md +++ b/packages/data-apis/README.md @@ -1,4 +1,4 @@ -# @alchemy/data (MVP) +# @alchemy/data-apis (MVP) A vertical-slice prototype of the Data APIs SDK, built to prove the architecture before scaling to the full v1 surface (Portfolio, Prices, NFT, Token, Transfers). diff --git a/packages/data/inject-version.ts b/packages/data-apis/inject-version.ts similarity index 100% rename from packages/data/inject-version.ts rename to packages/data-apis/inject-version.ts diff --git a/packages/data/package.json b/packages/data-apis/package.json similarity index 98% rename from packages/data/package.json rename to packages/data-apis/package.json index f5e7e6dd4e..3c2f776bb3 100644 --- a/packages/data/package.json +++ b/packages/data-apis/package.json @@ -1,5 +1,5 @@ { - "name": "@alchemy/data", + "name": "@alchemy/data-apis", "version": "0.0.0", "description": "Alchemy Data APIs SDK (Portfolio, Prices, NFT, Token, Transfers) — MVP", "author": "Alchemy", diff --git a/packages/data/src/actions/nft/getNftsForOwner.ts b/packages/data-apis/src/actions/nft/getNftsForOwner.ts similarity index 100% rename from packages/data/src/actions/nft/getNftsForOwner.ts rename to packages/data-apis/src/actions/nft/getNftsForOwner.ts diff --git a/packages/data/src/actions/portfolio/getTokensByAddress.ts b/packages/data-apis/src/actions/portfolio/getTokensByAddress.ts similarity index 100% rename from packages/data/src/actions/portfolio/getTokensByAddress.ts rename to packages/data-apis/src/actions/portfolio/getTokensByAddress.ts diff --git a/packages/data/src/actions/transfers/getAssetTransfers.ts b/packages/data-apis/src/actions/transfers/getAssetTransfers.ts similarity index 100% rename from packages/data/src/actions/transfers/getAssetTransfers.ts rename to packages/data-apis/src/actions/transfers/getAssetTransfers.ts diff --git a/packages/data/src/client.ts b/packages/data-apis/src/client.ts similarity index 97% rename from packages/data/src/client.ts rename to packages/data-apis/src/client.ts index c231c60cd3..2481e7d6a7 100644 --- a/packages/data/src/client.ts +++ b/packages/data-apis/src/client.ts @@ -33,7 +33,7 @@ export type AlchemyDataClient = Client & * * @example * ```ts - * import { createAlchemyDataClient } from "@alchemy/data"; + * import { createAlchemyDataClient } from "@alchemy/data-apis"; * * const data = createAlchemyDataClient({ * apiKey: process.env.ALCHEMY_API_KEY, diff --git a/packages/data/src/dataClient.test.ts b/packages/data-apis/src/dataClient.test.ts similarity index 100% rename from packages/data/src/dataClient.test.ts rename to packages/data-apis/src/dataClient.test.ts diff --git a/packages/data/src/decorator.ts b/packages/data-apis/src/decorator.ts similarity index 97% rename from packages/data/src/decorator.ts rename to packages/data-apis/src/decorator.ts index 726868fad4..27e98ad87d 100644 --- a/packages/data/src/decorator.ts +++ b/packages/data-apis/src/decorator.ts @@ -39,7 +39,7 @@ export type DataActions = { * import { createClient } from "viem"; * import { mainnet } from "viem/chains"; * import { alchemyTransport } from "@alchemy/common"; - * import { dataActions } from "@alchemy/data"; + * import { dataActions } from "@alchemy/data-apis"; * * const client = createClient({ * chain: mainnet, diff --git a/packages/data/src/index.ts b/packages/data-apis/src/index.ts similarity index 100% rename from packages/data/src/index.ts rename to packages/data-apis/src/index.ts diff --git a/packages/data/src/internal/clientHelpers.ts b/packages/data-apis/src/internal/clientHelpers.ts similarity index 100% rename from packages/data/src/internal/clientHelpers.ts rename to packages/data-apis/src/internal/clientHelpers.ts diff --git a/packages/data/src/internal/endpoints.ts b/packages/data-apis/src/internal/endpoints.ts similarity index 100% rename from packages/data/src/internal/endpoints.ts rename to packages/data-apis/src/internal/endpoints.ts diff --git a/packages/data/src/schema/rest.ts b/packages/data-apis/src/schema/rest.ts similarity index 100% rename from packages/data/src/schema/rest.ts rename to packages/data-apis/src/schema/rest.ts diff --git a/packages/data/src/schema/rpc.ts b/packages/data-apis/src/schema/rpc.ts similarity index 100% rename from packages/data/src/schema/rpc.ts rename to packages/data-apis/src/schema/rpc.ts diff --git a/packages/data/src/types.ts b/packages/data-apis/src/types.ts similarity index 100% rename from packages/data/src/types.ts rename to packages/data-apis/src/types.ts diff --git a/packages/data/src/version.ts b/packages/data-apis/src/version.ts similarity index 100% rename from packages/data/src/version.ts rename to packages/data-apis/src/version.ts diff --git a/packages/data/tsconfig.build.json b/packages/data-apis/tsconfig.build.json similarity index 100% rename from packages/data/tsconfig.build.json rename to packages/data-apis/tsconfig.build.json diff --git a/packages/data/tsconfig.json b/packages/data-apis/tsconfig.json similarity index 100% rename from packages/data/tsconfig.json rename to packages/data-apis/tsconfig.json diff --git a/packages/data/vitest.config.ts b/packages/data-apis/vitest.config.ts similarity index 91% rename from packages/data/vitest.config.ts rename to packages/data-apis/vitest.config.ts index bf4b3f619a..160b2a76d1 100644 --- a/packages/data/vitest.config.ts +++ b/packages/data-apis/vitest.config.ts @@ -2,7 +2,7 @@ import { defineProject } from "vitest/config"; export default defineProject({ test: { - name: "alchemy/data", + name: "alchemy/data-apis", globals: true, include: ["src/**/*.test.ts"], // Unit tests stub fetch; no anvil/bundler setup needed (same as common) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 077abdcdf6..7a5905d755 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -236,6 +236,19 @@ importers: specifier: ^2.45.0 version: 2.46.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.25.76) + packages/data-apis: + dependencies: + '@alchemy/common': + specifier: workspace:* + version: link:../common + devDependencies: + typescript-template: + specifier: workspace:* + version: link:../../templates/typescript + viem: + specifier: ^2.45.0 + version: 2.46.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.4.3) + packages/smart-accounts: dependencies: '@alchemy/common': @@ -1616,17 +1629,6 @@ packages: resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} engines: {node: ^14.21.3 || >=16} - '@noble/curves@1.4.2': - resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} - - '@noble/curves@1.8.0': - resolution: {integrity: sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==} - engines: {node: ^14.21.3 || >=16} - - '@noble/curves@1.8.1': - resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} - engines: {node: ^14.21.3 || >=16} - '@noble/curves@1.9.1': resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} engines: {node: ^14.21.3 || >=16} @@ -1635,18 +1637,6 @@ packages: resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.4.0': - resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} - engines: {node: '>= 16'} - - '@noble/hashes@1.7.0': - resolution: {integrity: sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==} - engines: {node: ^14.21.3 || >=16} - - '@noble/hashes@1.7.1': - resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} - engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} @@ -8967,7 +8957,7 @@ snapshots: '@base-org/account@1.1.1(bufferutil@4.1.0)(react@18.3.1)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@18.3.1))(utf-8-validate@5.0.10)(zod@4.4.3)': dependencies: - '@noble/hashes': 1.4.0 + '@noble/hashes': 1.8.0 clsx: 1.2.1 eventemitter3: 5.0.1 idb-keyval: 6.2.1 @@ -9001,7 +8991,7 @@ snapshots: '@coinbase/wallet-sdk@4.3.6(bufferutil@4.1.0)(react@18.3.1)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@18.3.1))(utf-8-validate@5.0.10)(zod@4.4.3)': dependencies: - '@noble/hashes': 1.4.0 + '@noble/hashes': 1.8.0 clsx: 1.2.1 eventemitter3: 5.0.1 idb-keyval: 6.2.1 @@ -9812,18 +9802,6 @@ snapshots: '@noble/ciphers@1.3.0': {} - '@noble/curves@1.4.2': - dependencies: - '@noble/hashes': 1.4.0 - - '@noble/curves@1.8.0': - dependencies: - '@noble/hashes': 1.7.0 - - '@noble/curves@1.8.1': - dependencies: - '@noble/hashes': 1.7.1 - '@noble/curves@1.9.1': dependencies: '@noble/hashes': 1.8.0 @@ -9832,12 +9810,6 @@ snapshots: dependencies: '@noble/hashes': 1.8.0 - '@noble/hashes@1.4.0': {} - - '@noble/hashes@1.7.0': {} - - '@noble/hashes@1.7.1': {} - '@noble/hashes@1.8.0': {} '@nodelib/fs.scandir@2.1.5': @@ -10576,8 +10548,8 @@ snapshots: '@scure/bip32@1.4.0': dependencies: - '@noble/curves': 1.4.2 - '@noble/hashes': 1.4.0 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 '@scure/base': 1.1.9 '@scure/bip32@1.7.0': @@ -10588,7 +10560,7 @@ snapshots: '@scure/bip39@1.3.0': dependencies: - '@noble/hashes': 1.4.0 + '@noble/hashes': 1.8.0 '@scure/base': 1.1.9 '@scure/bip39@1.6.0': @@ -11744,8 +11716,8 @@ snapshots: '@walletconnect/relay-auth@1.1.0': dependencies: - '@noble/curves': 1.8.0 - '@noble/hashes': 1.7.0 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 uint8arrays: 3.1.0 @@ -11971,8 +11943,8 @@ snapshots: '@walletconnect/utils@2.21.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.3)': dependencies: '@noble/ciphers': 1.2.1 - '@noble/curves': 1.8.1 - '@noble/hashes': 1.7.1 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/keyvaluestorage': 1.1.1 '@walletconnect/relay-api': 1.0.11 @@ -12015,8 +11987,8 @@ snapshots: '@walletconnect/utils@2.21.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.3)': dependencies: '@noble/ciphers': 1.2.1 - '@noble/curves': 1.8.1 - '@noble/hashes': 1.7.1 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/keyvaluestorage': 1.1.1 '@walletconnect/relay-api': 1.0.11 @@ -13637,8 +13609,8 @@ snapshots: ethereum-cryptography@2.2.1: dependencies: - '@noble/curves': 1.4.2 - '@noble/hashes': 1.4.0 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 '@scure/bip32': 1.4.0 '@scure/bip39': 1.3.0 @@ -14529,6 +14501,10 @@ snapshots: dependencies: ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) + isows@1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)): + dependencies: + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) + iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -16312,7 +16288,7 @@ snapshots: dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 - '@noble/curves': 1.9.1 + '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 @@ -16327,7 +16303,7 @@ snapshots: dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 - '@noble/curves': 1.9.1 + '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 @@ -16342,7 +16318,7 @@ snapshots: dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 - '@noble/curves': 1.9.1 + '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 @@ -18012,7 +17988,7 @@ snapshots: viem@2.46.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.22.4): dependencies: - '@noble/curves': 1.9.1 + '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 @@ -18029,7 +18005,7 @@ snapshots: viem@2.46.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.3): dependencies: - '@noble/curves': 1.9.1 + '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 @@ -18046,12 +18022,12 @@ snapshots: viem@2.46.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.25.76): dependencies: - '@noble/curves': 1.9.1 + '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76) - isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)) ox: 0.12.4(typescript@5.9.3)(zod@3.25.76) ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: @@ -18063,12 +18039,12 @@ snapshots: viem@2.46.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.4.3): dependencies: - '@noble/curves': 1.9.1 + '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 abitype: 1.2.3(typescript@5.9.3)(zod@4.4.3) - isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)) ox: 0.12.4(typescript@5.9.3)(zod@4.4.3) ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: From fa4818b70aa26f2055905cb61355144c9bfa4946 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Tue, 9 Jun 2026 16:51:41 -0400 Subject: [PATCH 03/20] chore(data-apis): add smoke-test script for live API validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 14-test script covering all three methods (portfolio, nft, transfers), all three network input formats (viem Chain, slug, CAIP-2), per-request network overrides, and the raw decorator path — all verified green. Run with: ALCHEMY_API_KEY= pnpm --filter @alchemy/data-apis smoke-test Co-Authored-By: Claude Fable 5 --- packages/data-apis/package.json | 3 +- packages/data-apis/scripts/smoke-test.ts | 258 +++++++++++++++++++++++ 2 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 packages/data-apis/scripts/smoke-test.ts diff --git a/packages/data-apis/package.json b/packages/data-apis/package.json index 3c2f776bb3..0b8bf7f2c2 100644 --- a/packages/data-apis/package.json +++ b/packages/data-apis/package.json @@ -36,7 +36,8 @@ "build:types": "tsc --project tsconfig.build.json --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", "clean": "rm -rf ./dist", "test": "vitest", - "test:run": "vitest run" + "test:run": "vitest run", + "smoke-test": "tsx scripts/smoke-test.ts" }, "devDependencies": { "typescript-template": "workspace:*", diff --git a/packages/data-apis/scripts/smoke-test.ts b/packages/data-apis/scripts/smoke-test.ts new file mode 100644 index 0000000000..5a7c8f25d6 --- /dev/null +++ b/packages/data-apis/scripts/smoke-test.ts @@ -0,0 +1,258 @@ +/** + * Smoke test for @alchemy/data-apis — runs against the real Alchemy API. + * + * Usage: + * ALCHEMY_API_KEY= pnpm --filter @alchemy/data-apis smoke-test + * + * What it covers: + * - createAlchemyDataClient with slug, viem Chain, and CAIP-2 network inputs + * - portfolio.getTokensByAddress (REST, multi-network, all three input formats) + * - nft.getNftsForOwner (REST, per-network, per-request override) + * - transfers.getAssetTransfers (JSON-RPC, per-request network override) + */ + +import { mainnet } from "viem/chains"; +import { BaseError } from "@alchemy/common"; +import { createAlchemyDataClient } from "../src/client.js"; +import { dataActions } from "../src/decorator.js"; +import { alchemyTransport } from "@alchemy/common"; +import { createClient } from "viem"; + +const API_KEY = process.env.ALCHEMY_API_KEY; +if (!API_KEY) { + console.error("ALCHEMY_API_KEY env var is required"); + process.exit(1); +} + +// Vitalik's address — publicly known, reliably has tokens, NFTs, and transfer history. +const VITALIK = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; + +let passed = 0; +let failed = 0; + +async function test(name: string, fn: () => Promise) { + process.stdout.write(` ${name} ... `); + try { + await fn(); + console.log("\x1b[32mPASS\x1b[0m"); + passed++; + } catch (err) { + console.log("\x1b[31mFAIL\x1b[0m"); + console.error(` ${err instanceof Error ? err.message : String(err)}`); + failed++; + } +} + +function assert(condition: boolean, message: string) { + if (!condition) throw new BaseError(`Assertion failed: ${message}`); +} + +// ─── Clients ────────────────────────────────────────────────────────────────── + +// Three ways to specify the default network +const clientBySlug = createAlchemyDataClient({ + apiKey: API_KEY, + network: "eth-mainnet", +}); +const clientByChain = createAlchemyDataClient({ + apiKey: API_KEY, + network: mainnet, +}); +const clientByCaip2 = createAlchemyDataClient({ + apiKey: API_KEY, + network: "eip155:1", +}); + +// Decorator path: bring-your-own viem client +const rawViemClient = createClient({ + chain: mainnet, + transport: alchemyTransport({ apiKey: API_KEY }), +}).extend(dataActions); + +// ─── Tests ──────────────────────────────────────────────────────────────────── + +console.log("\n\x1b[1mportfolio.getTokensByAddress\x1b[0m"); + +await test("single network (slug)", async () => { + const result = await clientBySlug.portfolio.getTokensByAddress({ + addresses: [{ address: VITALIK, networks: ["eth-mainnet"] }], + withMetadata: true, + includeNativeTokens: true, + }); + assert( + Array.isArray(result.data.tokens), + "result.data.tokens should be an array", + ); + assert(result.data.tokens.length > 0, "expected at least one token"); +}); + +await test("multi-network: viem Chain + slug + CAIP-2 in one call", async () => { + const result = await clientBySlug.portfolio.getTokensByAddress({ + addresses: [ + { + address: VITALIK, + networks: [ + mainnet, // viem Chain + "base-mainnet", // Alchemy slug + "eip155:137", // CAIP-2 (Polygon) + ], + }, + ], + withMetadata: false, + includeNativeTokens: true, + includeErc20Tokens: false, + }); + assert( + Array.isArray(result.data.tokens), + "result.data.tokens should be an array", + ); + // expect at least some native ETH/MATIC results across 3 networks + assert( + result.data.tokens.length > 0, + "expected tokens across multiple networks", + ); +}); + +console.log("\n\x1b[1mnft.getNftsForOwner\x1b[0m"); + +await test("uses client-level network (slug)", async () => { + const result = await clientBySlug.nft.getNftsForOwner({ owner: VITALIK }); + assert( + typeof result.totalCount === "number", + "totalCount should be a number", + ); + assert(result.totalCount > 0, "Vitalik should own some NFTs on mainnet"); +}); + +await test("uses client-level network (viem Chain)", async () => { + const result = await clientByChain.nft.getNftsForOwner({ owner: VITALIK }); + assert( + typeof result.totalCount === "number", + "totalCount should be a number", + ); +}); + +await test("uses client-level network (CAIP-2)", async () => { + const result = await clientByCaip2.nft.getNftsForOwner({ owner: VITALIK }); + assert( + typeof result.totalCount === "number", + "totalCount should be a number", + ); +}); + +await test("per-request network override (slug)", async () => { + // client defaults to mainnet but we override to base + const mainnetResult = await clientBySlug.nft.getNftsForOwner({ + owner: VITALIK, + }); + const baseResult = await clientBySlug.nft.getNftsForOwner({ + owner: VITALIK, + network: "base-mainnet", + }); + // Different chains — counts may differ (just verify both return valid shapes) + assert( + typeof mainnetResult.totalCount === "number", + "mainnet totalCount should be a number", + ); + assert( + typeof baseResult.totalCount === "number", + "base totalCount should be a number", + ); +}); + +await test("per-request network override (CAIP-2)", async () => { + const result = await clientBySlug.nft.getNftsForOwner({ + owner: VITALIK, + network: "eip155:1", + }); + assert( + typeof result.totalCount === "number", + "totalCount should be a number", + ); +}); + +await test("pageSize param", async () => { + const result = await clientBySlug.nft.getNftsForOwner({ + owner: VITALIK, + pageSize: 2, + withMetadata: false, + }); + assert(result.ownedNfts.length <= 2, "should respect pageSize=2"); +}); + +console.log("\n\x1b[1mtransfers.getAssetTransfers\x1b[0m"); + +await test("uses client-level network (slug)", async () => { + const result = await clientBySlug.transfers.getAssetTransfers({ + fromAddress: VITALIK, + category: ["external"], + maxCount: "0x5", + order: "desc", + withMetadata: true, + }); + assert(Array.isArray(result.transfers), "transfers should be an array"); + assert(result.transfers.length > 0, "expected at least one transfer"); +}); + +await test("uses client-level network (viem Chain)", async () => { + const result = await clientByChain.transfers.getAssetTransfers({ + fromAddress: VITALIK, + category: ["external"], + maxCount: "0x3", + }); + assert(Array.isArray(result.transfers), "transfers should be an array"); +}); + +await test("per-request network override (slug)", async () => { + const result = await clientBySlug.transfers.getAssetTransfers({ + fromAddress: VITALIK, + category: ["external", "erc20"], + network: "base-mainnet", + maxCount: "0x5", + }); + assert(Array.isArray(result.transfers), "transfers should be an array"); +}); + +await test("per-request network override (CAIP-2)", async () => { + const result = await clientBySlug.transfers.getAssetTransfers({ + fromAddress: VITALIK, + category: ["external"], + network: "eip155:137", // Polygon + maxCount: "0x3", + }); + assert(Array.isArray(result.transfers), "transfers should be an array"); +}); + +console.log("\n\x1b[1mdecorator path (raw viem client + dataActions)\x1b[0m"); + +await test("client.extend(dataActions) works identically", async () => { + const result = await rawViemClient.transfers.getAssetTransfers({ + fromAddress: VITALIK, + category: ["external"], + maxCount: "0x3", + order: "desc", + }); + assert(Array.isArray(result.transfers), "transfers should be an array"); +}); + +await test("nft via raw viem client", async () => { + const result = await rawViemClient.nft.getNftsForOwner({ + owner: VITALIK, + pageSize: 1, + }); + assert( + typeof result.totalCount === "number", + "totalCount should be a number", + ); +}); + +// ─── Summary ────────────────────────────────────────────────────────────────── + +console.log(`\n${"─".repeat(50)}`); +const total = passed + failed; +if (failed === 0) { + console.log(`\x1b[32m✓ All ${total} tests passed\x1b[0m`); +} else { + console.log(`\x1b[31m✗ ${failed}/${total} tests failed\x1b[0m`); + process.exit(1); +} From cc00aba23249fc63e4e028470fe5faf87e3f1a45 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Tue, 9 Jun 2026 16:56:48 -0400 Subject: [PATCH 04/20] refactor(data-apis): rename createAlchemyDataClient to createDataClient Package is already namespaced to Alchemy; the prefix is redundant. Mirrors the wallet-apis pattern of dropping the brand prefix from the factory function name. Co-Authored-By: Claude Fable 5 --- packages/data-apis/README.md | 2 +- packages/data-apis/scripts/smoke-test.ts | 10 +++++----- packages/data-apis/src/client.ts | 6 +++--- packages/data-apis/src/dataClient.test.ts | 10 +++++----- packages/data-apis/src/index.ts | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/data-apis/README.md b/packages/data-apis/README.md index 050316e193..dfe23b2f97 100644 --- a/packages/data-apis/README.md +++ b/packages/data-apis/README.md @@ -17,7 +17,7 @@ Plus the two entry points: ```ts // Data-only developers (no viem knowledge required) -const data = createAlchemyDataClient({ apiKey, network: "eth-mainnet" }); +const data = createDataClient({ apiKey, network: "eth-mainnet" }); // Developers already on a viem client with an Alchemy transport const client = createClient({ chain: mainnet, transport: alchemyTransport({ apiKey }) }) diff --git a/packages/data-apis/scripts/smoke-test.ts b/packages/data-apis/scripts/smoke-test.ts index 5a7c8f25d6..3f80447693 100644 --- a/packages/data-apis/scripts/smoke-test.ts +++ b/packages/data-apis/scripts/smoke-test.ts @@ -5,7 +5,7 @@ * ALCHEMY_API_KEY= pnpm --filter @alchemy/data-apis smoke-test * * What it covers: - * - createAlchemyDataClient with slug, viem Chain, and CAIP-2 network inputs + * - createDataClient with slug, viem Chain, and CAIP-2 network inputs * - portfolio.getTokensByAddress (REST, multi-network, all three input formats) * - nft.getNftsForOwner (REST, per-network, per-request override) * - transfers.getAssetTransfers (JSON-RPC, per-request network override) @@ -13,7 +13,7 @@ import { mainnet } from "viem/chains"; import { BaseError } from "@alchemy/common"; -import { createAlchemyDataClient } from "../src/client.js"; +import { createDataClient } from "../src/client.js"; import { dataActions } from "../src/decorator.js"; import { alchemyTransport } from "@alchemy/common"; import { createClient } from "viem"; @@ -50,15 +50,15 @@ function assert(condition: boolean, message: string) { // ─── Clients ────────────────────────────────────────────────────────────────── // Three ways to specify the default network -const clientBySlug = createAlchemyDataClient({ +const clientBySlug = createDataClient({ apiKey: API_KEY, network: "eth-mainnet", }); -const clientByChain = createAlchemyDataClient({ +const clientByChain = createDataClient({ apiKey: API_KEY, network: mainnet, }); -const clientByCaip2 = createAlchemyDataClient({ +const clientByCaip2 = createDataClient({ apiKey: API_KEY, network: "eip155:1", }); diff --git a/packages/data-apis/src/client.ts b/packages/data-apis/src/client.ts index 2481e7d6a7..0af048d2f5 100644 --- a/packages/data-apis/src/client.ts +++ b/packages/data-apis/src/client.ts @@ -33,9 +33,9 @@ export type AlchemyDataClient = Client & * * @example * ```ts - * import { createAlchemyDataClient } from "@alchemy/data-apis"; + * import { createDataClient } from "@alchemy/data-apis"; * - * const data = createAlchemyDataClient({ + * const data = createDataClient({ * apiKey: process.env.ALCHEMY_API_KEY, * network: "eth-mainnet", // or `mainnet` from viem/chains, or "eip155:1" * }); @@ -46,7 +46,7 @@ export type AlchemyDataClient = Client & * @param {AlchemyDataClientOptions} options Auth (apiKey/jwt/proxy url) and default network * @returns {AlchemyDataClient} A viem client extended with the Data API actions */ -export function createAlchemyDataClient( +export function createDataClient( options: AlchemyDataClientOptions, ): AlchemyDataClient { const { network, ...transportConfig } = options; diff --git a/packages/data-apis/src/dataClient.test.ts b/packages/data-apis/src/dataClient.test.ts index db288c8402..786656fbe2 100644 --- a/packages/data-apis/src/dataClient.test.ts +++ b/packages/data-apis/src/dataClient.test.ts @@ -2,7 +2,7 @@ import { alchemyTransport } from "@alchemy/common"; import { createClient } from "viem"; import { mainnet, base } from "viem/chains"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { createAlchemyDataClient } from "./client.js"; +import { createDataClient } from "./client.js"; import { dataActions } from "./decorator.js"; const fetchMock = vi.fn(); @@ -22,11 +22,11 @@ afterEach(() => { vi.unstubAllGlobals(); }); -describe("createAlchemyDataClient", () => { +describe("createDataClient", () => { it("routes multi-network portfolio calls to the global Data API with resolved slugs", async () => { fetchMock.mockResolvedValueOnce(jsonResponse({ data: { tokens: [] } })); - const data = createAlchemyDataClient({ + const data = createDataClient({ apiKey: "test-key", network: "eth-mainnet", }); @@ -60,7 +60,7 @@ describe("createAlchemyDataClient", () => { jsonResponse({ ownedNfts: [], totalCount: 0 }), ); - const data = createAlchemyDataClient({ + const data = createDataClient({ apiKey: "test-key", network: "eth-mainnet", }); @@ -81,7 +81,7 @@ describe("createAlchemyDataClient", () => { jsonResponse({ jsonrpc: "2.0", id: 1, result: { transfers: [] } }), ); - const data = createAlchemyDataClient({ + const data = createDataClient({ apiKey: "test-key", network: "eth-mainnet", }); diff --git a/packages/data-apis/src/index.ts b/packages/data-apis/src/index.ts index 71e7da0a3b..395254ca25 100644 --- a/packages/data-apis/src/index.ts +++ b/packages/data-apis/src/index.ts @@ -1,6 +1,6 @@ // client export type { AlchemyDataClient, AlchemyDataClientOptions } from "./client.js"; -export { createAlchemyDataClient } from "./client.js"; +export { createDataClient } from "./client.js"; // decorator export type { DataActions } from "./decorator.js"; From b5d7840b2b321d52d82d7d5423613c099883ebe6 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Tue, 9 Jun 2026 22:24:31 -0400 Subject: [PATCH 05/20] feat(data-apis): generate type internals from docs OpenAPI/OpenRPC specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds @alchemy/api-codegen (private workspace package) implementing the two-stage pipeline from the data SDK codegen plan: - snapshot (network): bundles specs from a local docs checkout using the docs repo's own tooling (redocly / generate:rpc), commits them under specs/ with a lockfile pinning the docs commit SHA + sha256 checksums - generate (offline, deterministic): emits committed TypeScript into packages/data-apis/src/generated — openapi-typescript output + RestRequestSchema entries for REST, json-schema-to-typescript params/ result types + viem RpcSchema entries for OpenRPC The hand-maintained codegen.manifest.ts maps spec operations to the generated surface; a renamed/removed spec operation hard-fails generate (drift alarm), uncovered operations are reported for visibility. data-apis schema/rest.ts, schema/rpc.ts, and types.ts are now thin hand-reviewed aliases over generated internals. Runtime action code is untouched; public types adopt spec-accurate optionality (transfers/ ownedNfts/totalCount/category optional) and richer fields. The spec's "Not Found (null)" string result branch is deliberately collapsed. Repo wiring: turbo generate task (was referenced by root scripts but undefined), nx generate outputs, prettier/eslint handling (generated files carry a file-level eslint-disable and are self-formatted with the repo prettier config; spec snapshots are ignored). Verified: 15 generator unit tests, 4 data-apis unit tests, build + typecheck clean, pnpm generate idempotent, 14/14 live smoke tests pass. Co-Authored-By: Claude Fable 5 --- .eslintignore | 4 + nx.json | 4 +- packages/api-codegen/README.md | 42 + packages/api-codegen/package.json | 25 + packages/api-codegen/specs/nft.json | 10336 ++++++++++++++++ packages/api-codegen/specs/portfolio.json | 3210 +++++ packages/api-codegen/specs/specs.lock.json | 12 + packages/api-codegen/specs/transfers.json | 566 + packages/api-codegen/src/cli.ts | 37 + packages/api-codegen/src/errors.ts | 7 + packages/api-codegen/src/format.ts | 29 + packages/api-codegen/src/generate.ts | 81 + packages/api-codegen/src/hash.ts | 12 + packages/api-codegen/src/index.ts | 10 + packages/api-codegen/src/manifest.ts | 152 + packages/api-codegen/src/paths.ts | 17 + .../api-codegen/src/rest/normalizePath.ts | 45 + packages/api-codegen/src/rest/openapiTypes.ts | 17 + .../api-codegen/src/rest/schemaEmitter.ts | 176 + packages/api-codegen/src/rpc/openrpcWalker.ts | 89 + packages/api-codegen/src/rpc/rpcEmitter.ts | 81 + packages/api-codegen/src/snapshot.ts | 194 + packages/api-codegen/src/targets.ts | 18 + packages/api-codegen/src/write.ts | 20 + .../tests/fixtures/rest.fixture.json | 82 + .../tests/fixtures/rpc.fixture.json | 43 + packages/api-codegen/tests/manifest.test.ts | 71 + .../api-codegen/tests/normalizePath.test.ts | 42 + packages/api-codegen/tests/rpcEmitter.test.ts | 56 + .../api-codegen/tests/schemaEmitter.test.ts | 59 + packages/api-codegen/tsconfig.json | 7 + packages/api-codegen/vitest.config.ts | 13 + packages/data-apis/README.md | 25 +- packages/data-apis/codegen.manifest.ts | 50 + packages/data-apis/package.json | 2 + packages/data-apis/scripts/smoke-test.ts | 13 +- .../src/generated/rest/nft.schema.ts | 36 + .../data-apis/src/generated/rest/nft.types.ts | 5214 ++++++++ .../src/generated/rest/portfolio.schema.ts | 31 + .../src/generated/rest/portfolio.types.ts | 1719 +++ .../data-apis/src/generated/rpc/transfers.ts | 125 + packages/data-apis/src/schema/rest.ts | 47 +- packages/data-apis/src/schema/rpc.ts | 24 +- packages/data-apis/src/types.ts | 137 +- pnpm-lock.yaml | 226 +- turbo.json | 5 + 46 files changed, 23022 insertions(+), 189 deletions(-) create mode 100644 packages/api-codegen/README.md create mode 100644 packages/api-codegen/package.json create mode 100644 packages/api-codegen/specs/nft.json create mode 100644 packages/api-codegen/specs/portfolio.json create mode 100644 packages/api-codegen/specs/specs.lock.json create mode 100644 packages/api-codegen/specs/transfers.json create mode 100644 packages/api-codegen/src/cli.ts create mode 100644 packages/api-codegen/src/errors.ts create mode 100644 packages/api-codegen/src/format.ts create mode 100644 packages/api-codegen/src/generate.ts create mode 100644 packages/api-codegen/src/hash.ts create mode 100644 packages/api-codegen/src/index.ts create mode 100644 packages/api-codegen/src/manifest.ts create mode 100644 packages/api-codegen/src/paths.ts create mode 100644 packages/api-codegen/src/rest/normalizePath.ts create mode 100644 packages/api-codegen/src/rest/openapiTypes.ts create mode 100644 packages/api-codegen/src/rest/schemaEmitter.ts create mode 100644 packages/api-codegen/src/rpc/openrpcWalker.ts create mode 100644 packages/api-codegen/src/rpc/rpcEmitter.ts create mode 100644 packages/api-codegen/src/snapshot.ts create mode 100644 packages/api-codegen/src/targets.ts create mode 100644 packages/api-codegen/src/write.ts create mode 100644 packages/api-codegen/tests/fixtures/rest.fixture.json create mode 100644 packages/api-codegen/tests/fixtures/rpc.fixture.json create mode 100644 packages/api-codegen/tests/manifest.test.ts create mode 100644 packages/api-codegen/tests/normalizePath.test.ts create mode 100644 packages/api-codegen/tests/rpcEmitter.test.ts create mode 100644 packages/api-codegen/tests/schemaEmitter.test.ts create mode 100644 packages/api-codegen/tsconfig.json create mode 100644 packages/api-codegen/vitest.config.ts create mode 100644 packages/data-apis/codegen.manifest.ts create mode 100644 packages/data-apis/src/generated/rest/nft.schema.ts create mode 100644 packages/data-apis/src/generated/rest/nft.types.ts create mode 100644 packages/data-apis/src/generated/rest/portfolio.schema.ts create mode 100644 packages/data-apis/src/generated/rest/portfolio.types.ts create mode 100644 packages/data-apis/src/generated/rpc/transfers.ts diff --git a/.eslintignore b/.eslintignore index 67cfff5b09..1b7d6301c4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -14,3 +14,7 @@ site/.vitepress/cache/**/* **/test-results/* **/playwright-report/* pnpm-lock.yaml + +# codegen: committed spec snapshots (generated .ts files instead carry a +# file-level eslint-disable banner so lint-staged can pass them explicitly) +packages/api-codegen/specs/** diff --git a/nx.json b/nx.json index c53e11e87f..5f6ee5c358 100644 --- a/nx.json +++ b/nx.json @@ -11,8 +11,8 @@ }, "generate": { "dependsOn": ["^build"], - "outputs": ["{projectRoot}/src/plugins"], - "cache": true + "outputs": ["{projectRoot}/src/plugins", "{projectRoot}/src/generated"], + "cache": false }, "prepare": { "dependsOn": ["^build"], diff --git a/packages/api-codegen/README.md b/packages/api-codegen/README.md new file mode 100644 index 0000000000..eda0639238 --- /dev/null +++ b/packages/api-codegen/README.md @@ -0,0 +1,42 @@ +# @alchemy/api-codegen + +Internal (unpublished) codegen tool that generates SDK type internals from the +docs repo's bundled OpenAPI/OpenRPC specs. Design doc: `data-sdk-codegen-plan.md` +(alchemy-cli repo, alongside the data SDK scope plan). + +## Two-stage pipeline + +``` +alchemyplatform/docs (hand-authored YAML) + │ pnpm snapshot ← network/local-checkout stage, run rarely + ▼ +specs/*.json + specs.lock.json ← committed snapshots, pinned by docs SHA + sha256 + │ pnpm generate ← offline + deterministic, run on every change + ▼ +packages//src/generated/ ← committed TypeScript (prettier-formatted) +``` + +- **`pnpm --filter @alchemy/api-codegen snapshot`** — bundles specs from a local + docs checkout (`--docs `, `ALCHEMY_DOCS_DIR`, or `../docs` relative to the + repo root) using the docs repo's own tooling (redocly for OpenAPI, its + `generate:rpc` script for OpenRPC), copies the bundled JSON into `specs/`, and + writes `specs.lock.json` (docs commit SHA, branch, sha256 per file). The docs + working tree must be clean (`--allow-dirty` to override). +- **`pnpm generate`** (root, or `pnpm --filter @alchemy/data-apis generate`) — + reads only the committed snapshots, verifies checksums against the lockfile, + and emits TypeScript into the target package. Never touches the network. + +## Updating specs + +1. Pull/checkout the desired commit in your local docs clone. +2. `pnpm --filter @alchemy/api-codegen snapshot` +3. `pnpm generate` +4. Review the `specs/` + `src/generated/` diff and commit. + +## Targets + +Each consuming package declares a `codegen.manifest.ts` at its package root +(operationIds / RPC methods → export names, path normalization rules) and a +`generate` script invoking `src/cli.ts --target `. Targets are registered +in `src/targets/`. The generator hard-errors if a manifest references a spec +operation that no longer exists in the snapshot — that's the drift alarm. diff --git a/packages/api-codegen/package.json b/packages/api-codegen/package.json new file mode 100644 index 0000000000..8d3879e679 --- /dev/null +++ b/packages/api-codegen/package.json @@ -0,0 +1,25 @@ +{ + "name": "@alchemy/api-codegen", + "version": "0.0.0", + "description": "Internal codegen tool: generates SDK type internals from the docs repo's bundled OpenAPI/OpenRPC specs", + "author": "Alchemy", + "license": "MIT", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "snapshot": "tsx src/cli.ts snapshot", + "test": "vitest", + "test:run": "vitest run" + }, + "devDependencies": { + "json-schema-to-typescript": "15.0.4", + "openapi-typescript": "7.13.0", + "prettier": "3.3.3", + "typescript-template": "workspace:*" + } +} diff --git a/packages/api-codegen/specs/nft.json b/packages/api-codegen/specs/nft.json new file mode 100644 index 0000000000..456762762a --- /dev/null +++ b/packages/api-codegen/specs/nft.json @@ -0,0 +1,10336 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "🎨 NFT API", + "version": "1.0" + }, + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "default": "eth-mainnet", + "enum": [ + "eth-mainnet", + "eth-sepolia", + "abstract-mainnet", + "abstract-testnet", + "anime-mainnet", + "anime-sepolia", + "apechain-mainnet", + "apechain-curtis", + "arb-mainnet", + "arb-sepolia", + "avax-mainnet", + "avax-fuji", + "base-mainnet", + "base-sepolia", + "berachain-mainnet", + "blast-mainnet", + "blast-sepolia", + "bnb-mainnet", + "celo-mainnet", + "celo-sepolia", + "gnosis-mainnet", + "gnosis-chiado", + "lens-mainnet", + "lens-sepolia", + "linea-mainnet", + "linea-sepolia", + "polygon-mainnet", + "polygon-amoy", + "monad-mainnet", + "monad-testnet", + "mythos-mainnet", + "opt-mainnet", + "opt-sepolia", + "robinhood-testnet", + "ronin-mainnet", + "ronin-saigon", + "rootstock-mainnet", + "rootstock-testnet", + "scroll-mainnet", + "scroll-sepolia", + "settlus-mainnet", + "settlus-septestnet", + "shape-mainnet", + "shape-sepolia", + "soneium-mainnet", + "soneium-minato", + "starknet-mainnet", + "starknet-sepolia", + "story-mainnet", + "story-aeneid", + "unichain-mainnet", + "unichain-sepolia", + "worldchain-mainnet", + "worldchain-sepolia", + "zetachain-mainnet", + "zetachain-testnet", + "zksync-mainnet", + "zksync-sepolia", + "zora-mainnet", + "zora-sepolia" + ] + } + } + } + ], + "paths": { + "/v3/{apiKey}/getNFTsForOwner": { + "get": { + "summary": "NFTs By Owner", + "description": "getNFTsForOwner - Retrieves all NFTs currently owned by a specified address. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).", + "tags": [ + "NFT Ownership Endpoints" + ], + "parameters": [ + { + "name": "owner", + "description": "String - Address for NFT owner (can be in ENS format for Eth Mainnet).", + "schema": { + "type": "string", + "default": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + }, + "in": "query", + "required": true + }, + { + "name": "contractAddresses[]", + "description": "Array of contract addresses to filter the responses with. Max limit 45 contracts.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query" + }, + { + "name": "withMetadata", + "description": "Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "schema": { + "type": "boolean", + "default": true + }, + "in": "query" + }, + { + "name": "orderBy", + "description": "Enum - ordering scheme to use for ordering NFTs in the response. If unspecified, NFTs will be ordered by contract address and token ID.\n - transferTime: NFTs will be ordered by the time they were transferred into the wallet, with newest NFTs first. Note: This ordering is supported on Ethereum Mainnet, Optimism Mainnet, Polygon Mainnet, Base Mainnet, Arbitrum One, Polygon Amoy, Base Sepolia, Arbitrum Sepolia, Ethereum Sepolia, and Optimism Sepolia.", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "transferTime" + ] + }, + "required": false + }, + { + "name": "excludeFilters[]", + "description": "Array of filters (as ENUMS) that will be applied to the query. NFTs that match one or more of these filters will be excluded from the response. May not be used in conjunction with includeFilters[]. Filter Options:\n - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**.\n - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only.\n - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts)", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "in": "query" + }, + { + "name": "includeFilters[]", + "description": "Array of filters (as ENUMS) that will be applied to the query. Only NFTs that match one or more of these filters will be included in the response. May not be used in conjunction with excludeFilters[]. Filter Options:\n - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**.\n - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only.\n - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts)", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "in": "query" + }, + { + "name": "spamConfidenceLevel", + "description": "Enum - the confidence level at which to filter spam at.\n\nConfidence Levels:\n - VERY_HIGH\n - HIGH\n - MEDIUM\n - LOW\n\nThe confidence level set means that any spam that is at that confidence level or higher will be filtered out. For example, if the confidence level is HIGH, contracts that we have HIGH or VERY_HIGH confidence in being spam will be filtered out from the response. \nDefaults to VERY_HIGH for Ethereum Mainnet and MEDIUM for Matic Mainnet.\n\n**Please note that this filter is only available on paid tiers. Upgrade your account [here](https://dashboard.alchemy.com/settings/billing/).**", + "schema": { + "type": "string", + "enum": [ + "VERY_HIGH", + "HIGH", + "MEDIUM", + "LOW" + ] + }, + "in": "query", + "required": false + }, + { + "name": "tokenUriTimeoutInMs", + "description": "No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0.", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "pageKey", + "description": "String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results.", + "schema": { + "type": "string" + }, + "in": "query" + }, + { + "name": "pageSize", + "description": "Number of NFTs to be returned per page. Defaults to 100. Max is 100.", + "schema": { + "type": "integer", + "default": 100 + }, + "in": "query" + } + ], + "responses": { + "200": { + "description": "Returns the list of all NFTs owned by the given address and satisfying the given input parameters.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ownedNfts": { + "type": "array", + "description": "Array of the NFT objects corresponding to the NFTs owned by the owner", + "items": { + "type": "object", + "description": "The object that represents an NFT and has all data corresponding to that NFT", + "properties": { + "contract": { + "type": "object", + "description": "The contract object that has details of a contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenType": { + "type": "string" + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Brief human-readable description" + }, + "image": { + "type": "object", + "description": "Details of the image corresponding to this contract", + "properties": { + "cachedUrl": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "thumbnailUrl": { + "type": "string", + "description": "The Url that has the thumbnail version of the NFT" + }, + "pngUrl": { + "type": "string", + "description": "The Url that has the NFT image in png" + }, + "contentType": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "size": { + "type": "integer", + "description": "The size of the media asset in bytes." + }, + "originalUrl": { + "type": "string", + "description": "The original Url of the image coming straight from the smart contract" + } + } + }, + "raw": { + "type": "object", + "description": "Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract", + "properties": { + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + } + } + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + } + } + }, + "collection": { + "type": "object", + "description": "The collection object that has details of a collection", + "properties": { + "name": { + "type": "string", + "description": "String - Collection name" + }, + "slug": { + "type": "string", + "description": "String - OpenSea collection slug" + }, + "externalUrl": { + "type": "string", + "description": "String - URL for the external site of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "String - Banner image URL for the collection" + } + } + }, + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "acquiredAt": { + "type": "object", + "description": "Only present if the request specified `orderBy=transferTime`.", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "Block timestamp of the block where the NFT was most recently acquired." + }, + "blockNumber": { + "type": "string", + "description": "Block number of the block where the NFT was most recently acquired." + } + } + }, + "animation": { + "type": "object", + "properties": { + "cachedUrl": { + "type": "string" + }, + "contentType": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "orginalUrl": { + "type": "string" + } + } + }, + "mint": { + "type": "object", + "properties": { + "mintAddress": { + "type": "string", + "description": "Address that minted the NFT" + }, + "blockNumber": { + "type": "integer", + "description": "Block number when the NFT was minted" + }, + "timestamp": { + "type": "string", + "description": "Timestamp when the NFT was minted" + }, + "transactionHash": { + "type": "string", + "description": "Transaction hash of the mint transaction" + } + } + }, + "owners": { + "type": "array", + "description": "List of all addresses that own the given NFT.", + "items": { + "type": "string" + } + } + } + } + }, + "totalCount": { + "type": "integer", + "description": "Integer - Total number of NFTs (distinct `tokenIds`) owned by the given address." + }, + "pageKey": { + "type": "string" + }, + "validAt": { + "type": "object", + "description": "Block Information of the block as of which the corresponding data is valid", + "properties": { + "blockNumber": { + "type": "integer", + "description": "The block number above information is valid as of" + }, + "blockHash": { + "type": "string", + "description": "The block hash above information is valid as of" + }, + "blockTimestamp": { + "type": "string", + "description": "The block timestamp above information is valid as of" + } + } + } + } + }, + "examples": { + "byDefault": { + "summary": "Response (By Default)", + "value": { + "ownedNfts": [ + { + "contract": { + "address": "0x0bEed7099AF7514cCEDF642CfEA435731176Fb02", + "name": "DuskBreakers", + "symbol": "DUSK", + "totalSupply": "10000", + "tokenType": "ERC721", + "contractDeployer": "0x9c78DDec1F16685ee6E58637a640514A1eD87BC4", + "deployedBlockNumber": 13736379, + "openSeaMetadata": { + "floorPrice": 0.0582, + "collectionName": "DuskBreakers", + "safelistRequestStatus": "verified", + "imageUrl": "https://i.seadn.io/gae/LGbFRVdClz6-HDd-7WZKONJ5Ody0sBXTvFOQL71BYo3j2iU2wWCX_zlk-Zs0KEhq1qgXViF-6aG_0WS2MdIVNJx2GRSIIYTiciuf-A?w=500&auto=format", + "description": "Being a DuskBreaker means joining a select squad of 10,000 recruits who spend their days exploring a mysterious alien spaceship filled with friends, foes, and otherworldly technology. You will be part of a community that directly influences the development of upcoming interactive media and gaming experiences within the DuskBreakers universe. Each of you will play an important role in building out this world. You break it, you take it! \r\n\r\nVisit [DuskBreakers](https://duskbreakers.gg) to learn more.", + "externalUrl": "http://duskbreakers.gg", + "twitterUsername": "duskbreakers", + "discordUrl": "https://discord.gg/duskbreakers", + "lastIngestedAt": "2023-04-19T17:25:59.000Z" + }, + "isSpam": null, + "spamClassifications": [] + }, + "tokenId": "28", + "tokenType": "ERC721", + "name": "DuskBreaker #28", + "description": "Breakers have the honor of serving humanity through their work on The Dusk. They are part of a select squad of 10,000 recruits who spend their days exploring a mysterious alien spaceship filled with friends, foes, and otherworldly technology.", + "image": { + "cachedUrl": "https://nft-cdn.alchemy.com/eth-mainnet/1f9e8be3feb42b5b66452537a4032668", + "thumbnailUrl": "https://res.cloudinary.com/alchemyapi/image/upload/thumbnailv2/eth-mainnet/1f9e8be3feb42b5b66452537a4032668", + "pngUrl": "https://res.cloudinary.com/alchemyapi/image/upload/convert-png/eth-mainnet/1f9e8be3feb42b5b66452537a4032668", + "contentType": "image/png", + "size": 1474037, + "originalUrl": "https://duskbreakers.gg/breaker_images/28.png" + }, + "raw": { + "tokenUri": "https://api.duskbreakers.gg/metadata/duskbreakers/28", + "metadata": { + "name": "DuskBreaker #28", + "description": "Breakers have the honor of serving humanity through their work on The Dusk. They are part of a select squad of 10,000 recruits who spend their days exploring a mysterious alien spaceship filled with friends, foes, and otherworldly technology.", + "image": "https://duskbreakers.gg/breaker_images/28.png", + "external_url": "https://duskbreakers.gg/", + "attributes": [ + { + "value": "Locust Rider Armor (Red)", + "trait_type": "Clothes" + }, + { + "value": "Base Drone (Blue)", + "trait_type": "Drone" + }, + { + "value": "Thin", + "trait_type": "Eyebrows" + }, + { + "value": "Button", + "trait_type": "Nose" + }, + { + "value": "Mohawk (Black)", + "trait_type": "Hair" + }, + { + "value": "Almond 2 (Red)", + "trait_type": "Eyes" + }, + { + "value": "Big Smile (Purple)", + "trait_type": "Mouth" + }, + { + "value": "Light Brown", + "trait_type": "Skin Tone" + }, + { + "value": "Yellow", + "trait_type": "Background" + }, + { + "value": "Facepaint (Stripe)", + "trait_type": "Face Augments" + } + ] + }, + "error": null + }, + "tokenUri": "https://api.duskbreakers.gg/metadata/duskbreakers/28", + "timeLastUpdated": "2023-04-19T21:25:39.563Z", + "balance": "1" + }, + { + "contract": { + "address": "0x0bEed7099AF7514cCEDF642CfEA435731176Fb02", + "name": "DuskBreakers", + "symbol": "DUSK", + "totalSupply": "10000", + "tokenType": "ERC721", + "contractDeployer": "0x9c78DDec1F16685ee6E58637a640514A1eD87BC4", + "deployedBlockNumber": 13736379, + "openSeaMetadata": { + "floorPrice": 0.0582, + "collectionName": "DuskBreakers", + "safelistRequestStatus": "verified", + "imageUrl": "https://i.seadn.io/gae/LGbFRVdClz6-HDd-7WZKONJ5Ody0sBXTvFOQL71BYo3j2iU2wWCX_zlk-Zs0KEhq1qgXViF-6aG_0WS2MdIVNJx2GRSIIYTiciuf-A?w=500&auto=format", + "description": "Being a DuskBreaker means joining a select squad of 10,000 recruits who spend their days exploring a mysterious alien spaceship filled with friends, foes, and otherworldly technology. You will be part of a community that directly influences the development of upcoming interactive media and gaming experiences within the DuskBreakers universe. Each of you will play an important role in building out this world. You break it, you take it! \r\n\r\nVisit [DuskBreakers](https://duskbreakers.gg) to learn more.", + "externalUrl": "http://duskbreakers.gg", + "twitterUsername": "duskbreakers", + "discordUrl": "https://discord.gg/duskbreakers", + "lastIngestedAt": "2023-04-19T17:25:59.000Z" + }, + "isSpam": null, + "spamClassifications": [] + }, + "tokenId": "29", + "tokenType": "ERC721", + "name": "DuskBreaker #29", + "description": "Breakers have the honor of serving humanity through their work on The Dusk. They are part of a select squad of 10,000 recruits who spend their days exploring a mysterious alien spaceship filled with friends, foes, and otherworldly technology.", + "image": { + "cachedUrl": "https://nft-cdn.alchemy.com/eth-mainnet/4eb0b7f434746250ff3c8200d10a2226", + "thumbnailUrl": "https://res.cloudinary.com/alchemyapi/image/upload/thumbnailv2/eth-mainnet/4eb0b7f434746250ff3c8200d10a2226", + "pngUrl": "https://res.cloudinary.com/alchemyapi/image/upload/convert-png/eth-mainnet/4eb0b7f434746250ff3c8200d10a2226", + "contentType": "image/png", + "size": 1480183, + "originalUrl": "https://duskbreakers.gg/breaker_images/29.png" + }, + "raw": { + "tokenUri": "https://api.duskbreakers.gg/metadata/duskbreakers/29", + "metadata": { + "name": "DuskBreaker #29", + "description": "Breakers have the honor of serving humanity through their work on The Dusk. They are part of a select squad of 10,000 recruits who spend their days exploring a mysterious alien spaceship filled with friends, foes, and otherworldly technology.", + "image": "https://duskbreakers.gg/breaker_images/29.png", + "external_url": "https://duskbreakers.gg/", + "attributes": [ + { + "value": "Standard Issue Armor 1 (Orange)", + "trait_type": "Clothes" + }, + { + "value": "Dark Metal", + "trait_type": "SmartSkin" + }, + { + "value": "Base Drone (Purple)", + "trait_type": "Drone" + }, + { + "value": "Thin", + "trait_type": "Eyebrows" + }, + { + "value": "Broad", + "trait_type": "Nose" + }, + { + "value": "Slick Back (Red)", + "trait_type": "Hair" + }, + { + "value": "Sharp (Blue)", + "trait_type": "Eyes" + }, + { + "value": "Smirk (Neutral)", + "trait_type": "Mouth" + }, + { + "value": "Tan", + "trait_type": "Skin Tone" + }, + { + "value": "Purple", + "trait_type": "Background" + } + ] + }, + "error": null + }, + "tokenUri": "https://api.duskbreakers.gg/metadata/duskbreakers/29", + "timeLastUpdated": "2023-04-19T21:25:39.704Z", + "balance": "1" + }, + { + "contract": { + "address": "0x209cE666978779756Ae1E747608cD93e4dFf45fD", + "name": "Knight of Chains Genesis", + "symbol": "Knight of Chains Genesis", + "totalSupply": null, + "tokenType": "ERC1155", + "contractDeployer": "0xA92520aFF50c5A1a4d25FCF90c972AA49EbE5299", + "deployedBlockNumber": 14847327, + "openSeaMetadata": { + "floorPrice": null, + "collectionName": "Knight of Chains Genesis.", + "safelistRequestStatus": "not_requested", + "imageUrl": "https://i.seadn.io/gae/eRhkkVikIOW_-lDc1moMrZlTcd5DPygPRmTJ69Anb-CfG_RMAxIsichM5kDvfdnXc6gfnKuGZOFCbP_58pUvz57TyUeNbFMKGydHoac?w=500&auto=format", + "description": "[The KnightsOfChain] (https://knightsofchain.link) is an exclusive community that can only be entered by owning a Knight.\n\nVisit [Website](https://knightsofchain.link) and get your benefits.\n\n(Genesis Knights #1-#31 were pre-minted by the team, and are held by high ranking community members. OG Knights #32-#231 have special benefits.)", + "externalUrl": "https://knightsofchain.link", + "twitterUsername": null, + "discordUrl": null, + "lastIngestedAt": "2023-03-20T03:52:07.000Z" + }, + "isSpam": null, + "spamClassifications": [] + }, + "tokenId": "97", + "tokenType": "ERC1155", + "name": null, + "description": null, + "image": { + "cachedUrl": null, + "thumbnailUrl": null, + "pngUrl": null, + "contentType": null, + "size": null, + "originalUrl": null + }, + "raw": { + "tokenUri": "https://knightsofchain.link/ipfs/97", + "metadata": {}, + "error": null + }, + "tokenUri": "https://knightsofchain.link/ipfs/97", + "timeLastUpdated": "2023-04-20T15:44:29.965Z", + "balance": "1" + } + ], + "totalCount": 3, + "validAt": { + "blockNumber": 17091500, + "blockHash": "0x2a34a65c4e0cd7fdf187d6a497214ad2bee255d2d3501868a6b8c09b4d1261bd", + "blockTimestamp": "2023-04-21T01:25:59Z" + }, + "pageKey": null + } + }, + "withoutMetadata": { + "summary": "Response (withMetadata = false)", + "value": { + "ownedNfts": [ + { + "contractAddress": "0x0bEed7099AF7514cCEDF642CfEA435731176Fb02", + "tokenId": "28", + "balance": "1" + }, + { + "contractAddress": "0x0bEed7099AF7514cCEDF642CfEA435731176Fb02", + "tokenId": "29", + "balance": "1" + } + ], + "totalCount": 2, + "validAt": { + "blockNumber": 17091500, + "blockHash": "0x2a34a65c4e0cd7fdf187d6a497214ad2bee255d2d3501868a6b8c09b4d1261bd", + "blockTimestamp": "2023-04-21T01:25:59Z" + }, + "pageKey": null + } + }, + "withContractFiltering": { + "summary": "Response (with contract filtering)", + "value": { + "ownedNfts": [ + { + "contractAddress": "0x0bEed7099AF7514cCEDF642CfEA435731176Fb02", + "tokenId": "28", + "balance": "1" + }, + { + "contractAddress": "0x0bEed7099AF7514cCEDF642CfEA435731176Fb02", + "tokenId": "29", + "balance": "1" + } + ], + "totalCount": 2, + "validAt": { + "blockNumber": 17091500, + "blockHash": "0x2a34a65c4e0cd7fdf187d6a497214ad2bee255d2d3501868a6b8c09b4d1261bd", + "blockTimestamp": "2023-04-21T01:25:59Z" + }, + "pageKey": null + } + }, + "withPagination": { + "summary": "Response (with pagination)", + "value": { + "ownedNfts": [ + { + "contractAddress": "0x0bEed7099AF7514cCEDF642CfEA435731176Fb02", + "tokenId": "28", + "balance": "1" + }, + { + "contractAddress": "0x0bEed7099AF7514cCEDF642CfEA435731176Fb02", + "tokenId": "29", + "balance": "1" + } + ], + "totalCount": 2, + "validAt": { + "blockNumber": 17091500, + "blockHash": "0x2a34a65c4e0cd7fdf187d6a497214ad2bee255d2d3501868a6b8c09b4d1261bd", + "blockTimestamp": "2023-04-21T01:25:59Z" + }, + "pageKey": "88434286-7eaa-472d-8739-32a0497c2a18" + } + } + } + } + } + } + }, + "operationId": "getNFTsForOwner-v3" + } + }, + "/v3/{apiKey}/getNFTsForContract": { + "get": { + "summary": "NFTs By Contract", + "description": "getNFTsForContract - Retrieves all NFTs associated with a specific NFT contract. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).", + "tags": [ + "NFT Metadata Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + }, + { + "name": "withMetadata", + "description": "Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "schema": { + "type": "boolean", + "default": true + }, + "in": "query" + }, + { + "name": "startToken", + "description": "String - A tokenID offset used for pagination. Can be a hex string, or a decimal. Users can specify the offset themselves to start from a custom offset, or to fetch multiple token ranges in parallel.", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "description": "Integer - Sets the total number of NFTs returned in the response. Defaults to 100.", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "tokenUriTimeoutInMs", + "description": "No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0.", + "in": "query", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns a list of NFTs associated with the specified contract address.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nfts": { + "description": "List of objects that represent NFTs stored under the queried contract address.", + "type": "array", + "items": { + "type": "object", + "description": "The object that represents an NFT and has all data corresponding to that NFT", + "properties": { + "contract": { + "type": "object", + "description": "The contract object that has details of a contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenType": { + "type": "string" + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Brief human-readable description" + }, + "image": { + "type": "object", + "description": "Details of the image corresponding to this contract", + "properties": { + "cachedUrl": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "thumbnailUrl": { + "type": "string", + "description": "The Url that has the thumbnail version of the NFT" + }, + "pngUrl": { + "type": "string", + "description": "The Url that has the NFT image in png" + }, + "contentType": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "size": { + "type": "integer", + "description": "The size of the media asset in bytes." + }, + "originalUrl": { + "type": "string", + "description": "The original Url of the image coming straight from the smart contract" + } + } + }, + "raw": { + "type": "object", + "description": "Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract", + "properties": { + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + } + } + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + } + } + }, + "collection": { + "type": "object", + "description": "The collection object that has details of a collection", + "properties": { + "name": { + "type": "string", + "description": "String - Collection name" + }, + "slug": { + "type": "string", + "description": "String - OpenSea collection slug" + }, + "externalUrl": { + "type": "string", + "description": "String - URL for the external site of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "String - Banner image URL for the collection" + } + } + }, + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "acquiredAt": { + "type": "object", + "description": "Only present if the request specified `orderBy=transferTime`.", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "Block timestamp of the block where the NFT was most recently acquired." + }, + "blockNumber": { + "type": "string", + "description": "Block number of the block where the NFT was most recently acquired." + } + } + }, + "animation": { + "type": "object", + "properties": { + "cachedUrl": { + "type": "string" + }, + "contentType": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "orginalUrl": { + "type": "string" + } + } + }, + "mint": { + "type": "object", + "properties": { + "mintAddress": { + "type": "string", + "description": "Address that minted the NFT" + }, + "blockNumber": { + "type": "integer", + "description": "Block number when the NFT was minted" + }, + "timestamp": { + "type": "string", + "description": "Timestamp when the NFT was minted" + }, + "transactionHash": { + "type": "string", + "description": "Transaction hash of the mint transaction" + } + } + }, + "owners": { + "type": "array", + "description": "List of all addresses that own the given NFT.", + "items": { + "type": "string" + } + } + } + } + }, + "pageKey": { + "type": "string", + "description": "String - An offset used for pagination. Can be passed back as the \"startToken\" of a subsequent request to get the next page of results. Absent if there are no more results." + } + } + } + } + } + } + }, + "operationId": "getNFTsForContract-v3" + } + }, + "/v3/{apiKey}/getNFTsForCollection": { + "get": { + "summary": "NFTs By Collection", + "description": "getNFTsForCollection - Retrieves all NFTs associated with a specific NFT collection. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).", + "tags": [ + "NFT Metadata Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string" + }, + "required": false + }, + { + "name": "collectionSlug", + "description": "String - OpenSea slug for the NFT collection.", + "in": "query", + "schema": { + "type": "string", + "default": "boredapeyachtclub" + }, + "required": false + }, + { + "name": "withMetadata", + "description": "Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "schema": { + "type": "boolean", + "default": true + }, + "in": "query" + }, + { + "name": "startToken", + "description": "String - A tokenID offset used for pagination. Can be a hex string, or a decimal. Users can specify the offset themselves to start from a custom offset, or to fetch multiple token ranges in parallel.", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "description": "Integer - Sets the total number of NFTs returned in the response. Defaults to 100.", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "tokenUriTimeoutInMs", + "description": "No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0.", + "in": "query", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns a list of NFTs associated with the specified collection.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nfts": { + "description": "List of objects that represent NFTs stored under the queried contract address or collection slug.", + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "object", + "properties": { + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenMetadata": { + "type": "object", + "properties": { + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + } + } + } + } + }, + "tokenUri": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + } + } + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "external_url": { + "type": "string", + "description": "String - The image URL that appears alongside the asset image on NFT platforms." + }, + "background_color": { + "type": "string", + "description": "String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + }, + "media": { + "type": "array", + "items": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + } + } + } + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "contractMetadata": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "opensea": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + } + } + } + }, + "nextToken": { + "type": "string", + "description": "String - An offset used for pagination. Can be passed back as the \"startToken\" of a subsequent request to get the next page of results. Absent if there are no more results." + } + } + } + } + } + } + }, + "operationId": "getNFTsForCollection-v3" + } + }, + "/v3/{apiKey}/getNFTMetadata": { + "get": { + "summary": "NFT Metadata By Token ID", + "description": "getNFTMetadata - Retrieves the metadata associated with a specific NFT. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).", + "tags": [ + "NFT Metadata Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + }, + { + "name": "tokenId", + "description": "String - The ID of the token. Can be in hex or decimal format.", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": true + }, + { + "name": "tokenType", + "description": "String - 'ERC721' or 'ERC1155'; specifies type of token to query for. API requests will perform faster if this is specified.", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "tokenUriTimeoutInMs", + "description": "No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0.", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "refreshCache", + "description": "Defaults to false for faster response times. If true will refresh metadata for given token. If false will check the cache and use it or refresh if cache doesn't exist.", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Returns the metadata of the specified NFT.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "The object that represents an NFT and has all data corresponding to that NFT", + "properties": { + "contract": { + "type": "object", + "description": "The contract object that has details of a contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenType": { + "type": "string" + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Brief human-readable description" + }, + "image": { + "type": "object", + "description": "Details of the image corresponding to this contract", + "properties": { + "cachedUrl": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "thumbnailUrl": { + "type": "string", + "description": "The Url that has the thumbnail version of the NFT" + }, + "pngUrl": { + "type": "string", + "description": "The Url that has the NFT image in png" + }, + "contentType": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "size": { + "type": "integer", + "description": "The size of the media asset in bytes." + }, + "originalUrl": { + "type": "string", + "description": "The original Url of the image coming straight from the smart contract" + } + } + }, + "raw": { + "type": "object", + "description": "Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract", + "properties": { + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + } + } + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + } + } + }, + "collection": { + "type": "object", + "description": "The collection object that has details of a collection", + "properties": { + "name": { + "type": "string", + "description": "String - Collection name" + }, + "slug": { + "type": "string", + "description": "String - OpenSea collection slug" + }, + "externalUrl": { + "type": "string", + "description": "String - URL for the external site of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "String - Banner image URL for the collection" + } + } + }, + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "acquiredAt": { + "type": "object", + "description": "Only present if the request specified `orderBy=transferTime`.", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "Block timestamp of the block where the NFT was most recently acquired." + }, + "blockNumber": { + "type": "string", + "description": "Block number of the block where the NFT was most recently acquired." + } + } + }, + "animation": { + "type": "object", + "properties": { + "cachedUrl": { + "type": "string" + }, + "contentType": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "orginalUrl": { + "type": "string" + } + } + }, + "mint": { + "type": "object", + "properties": { + "mintAddress": { + "type": "string", + "description": "Address that minted the NFT" + }, + "blockNumber": { + "type": "integer", + "description": "Block number when the NFT was minted" + }, + "timestamp": { + "type": "string", + "description": "Timestamp when the NFT was minted" + }, + "transactionHash": { + "type": "string", + "description": "Transaction hash of the mint transaction" + } + } + }, + "owners": { + "type": "array", + "description": "List of all addresses that own the given NFT.", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "operationId": "getNFTMetadata-v3" + } + }, + "/v3/{apiKey}/getNFTMetadataBatch": { + "post": { + "summary": "NFT Metadata By Token ID [Batch]", + "description": "getNFTMetadataBatch - Retrieves metadata for up to 100 specified NFT contracts in a single request. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).", + "tags": [ + "NFT Metadata Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "array", + "description": "List of token objects to batch request NFT metadata for. Maximum 100.", + "items": { + "type": "object", + "required": [ + "contractAddress", + "tokenId" + ], + "properties": { + "contractAddress": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenType": { + "type": "string" + } + } + } + }, + "tokenUriTimeoutInMs": { + "type": "integer" + }, + "refreshCache": { + "type": "boolean", + "default": false + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Returns an array of NFT metadata corresponding to the batch query.", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of NFT objects corresponding to the query", + "items": { + "type": "object", + "description": "The object that represents an NFT and has all data corresponding to that NFT", + "properties": { + "contract": { + "type": "object", + "description": "The contract object that has details of a contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenType": { + "type": "string" + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Brief human-readable description" + }, + "image": { + "type": "object", + "description": "Details of the image corresponding to this contract", + "properties": { + "cachedUrl": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "thumbnailUrl": { + "type": "string", + "description": "The Url that has the thumbnail version of the NFT" + }, + "pngUrl": { + "type": "string", + "description": "The Url that has the NFT image in png" + }, + "contentType": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "size": { + "type": "integer", + "description": "The size of the media asset in bytes." + }, + "originalUrl": { + "type": "string", + "description": "The original Url of the image coming straight from the smart contract" + } + } + }, + "raw": { + "type": "object", + "description": "Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract", + "properties": { + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + } + } + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + } + } + }, + "collection": { + "type": "object", + "description": "The collection object that has details of a collection", + "properties": { + "name": { + "type": "string", + "description": "String - Collection name" + }, + "slug": { + "type": "string", + "description": "String - OpenSea collection slug" + }, + "externalUrl": { + "type": "string", + "description": "String - URL for the external site of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "String - Banner image URL for the collection" + } + } + }, + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "acquiredAt": { + "type": "object", + "description": "Only present if the request specified `orderBy=transferTime`.", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "Block timestamp of the block where the NFT was most recently acquired." + }, + "blockNumber": { + "type": "string", + "description": "Block number of the block where the NFT was most recently acquired." + } + } + }, + "animation": { + "type": "object", + "properties": { + "cachedUrl": { + "type": "string" + }, + "contentType": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "orginalUrl": { + "type": "string" + } + } + }, + "mint": { + "type": "object", + "properties": { + "mintAddress": { + "type": "string", + "description": "Address that minted the NFT" + }, + "blockNumber": { + "type": "integer", + "description": "Block number when the NFT was minted" + }, + "timestamp": { + "type": "string", + "description": "Timestamp when the NFT was minted" + }, + "transactionHash": { + "type": "string", + "description": "Transaction hash of the mint transaction" + } + } + }, + "owners": { + "type": "array", + "description": "List of all addresses that own the given NFT.", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "operationId": "getNFTMetadataBatch-v3" + } + }, + "/v3/{apiKey}/getContractMetadata": { + "get": { + "summary": "Contract Metadata By Address", + "description": "getContractMetadata - Retrieves high-level collection or contract-level information for an NFT. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).", + "tags": [ + "NFT Metadata Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns the contract metadata for the specified address.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "String - Contract address for the queried NFT collection" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + } + } + } + } + }, + "operationId": "getContractMetadata-v3" + } + }, + "/v3/{apiKey}/getCollectionMetadata": { + "get": { + "summary": "Collection Metadata By Slug", + "description": "getCollectionMetadata - Retrieves high-level collection or contract-level information for an NFT collection. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).", + "tags": [ + "NFT Metadata Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "collectionSlug", + "description": "String - OpenSea slug for the NFT collection.", + "in": "query", + "schema": { + "type": "string", + "default": "boredapeyachtclub" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns the collection metadata for the specified slug.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "String - Name of the queried NFT Collection" + }, + "slug": { + "description": "The human-readable string used to identify the collection on OpenSea.", + "type": "string" + }, + "floorPrice": { + "type": "object", + "description": "Floor price data for the collection", + "properties": { + "marketplace": { + "description": "The marketplace the floor price is on", + "type": "string" + }, + "floorPrice": { + "description": "Floor price of the collection on the marketplace", + "type": "number" + }, + "priceCurrency": { + "description": "The currency of the floor price", + "type": "string" + } + } + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + } + } + } + } + } + } + }, + "operationId": "getCollectionMetadata-v3" + } + }, + "/v3/{apiKey}/invalidateContract": { + "get": { + "summary": "Invalidate Contract Cache", + "description": "Marks all cached tokens for the specified contract as stale, ensuring the next query fetches live data instead of cached data.\n\nPlease note that this endpoint is only available on **Ethereum**, **Polygon**, **Arbitrum**, **Optimism** & **Base** networks.\n", + "tags": [ + "NFT Metadata Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns confirmation of cache invalidation along with the number of tokens invalidated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "True - if the queried contract is marked as spam.\nFalse - if the queried contract is considered valid.\n", + "properties": { + "success": { + "type": "string", + "description": "True if the contract was invalidated.\nFalse - if it wasn't.\n" + }, + "numTokensInvalidated": { + "type": "number", + "description": "The number of tokens that were invalidated as a result of running this query." + } + } + } + } + } + } + }, + "operationId": "invalidateContract-v3" + } + }, + "/v3/{apiKey}/getContractMetadataBatch": { + "post": { + "summary": "Contract Metadata By Address [Batch]", + "description": "getContractMetadataBatch - Retrieves metadata for a list of specified contract addresses in a single request.", + "tags": [ + "NFT Metadata Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "contractAddresses": { + "type": "array", + "description": "List of contract addresses to batch metadata requests for.", + "default": [ + "0xe785E82358879F061BC3dcAC6f0444462D4b5330", + "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d" + ], + "items": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Returns an array of contract metadata corresponding to the batch query.", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of contract metadata objects corresponding to the query.", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "String - Contract address for the queried NFT collection" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + } + } + } + } + } + }, + "operationId": "getContractMetadataBatch-v3" + } + }, + "/v3/{apiKey}/getOwnersForNFT": { + "get": { + "summary": "Owners By NFT", + "description": "getOwnersForNFT - Retrieves the owner(s) for a specific token. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).", + "tags": [ + "NFT Ownership Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + }, + { + "name": "tokenId", + "description": "String - The ID of the token. Can be in hex or decimal format.", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns the owner(s) of the specified NFT.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "owners": { + "type": "array", + "description": "List of all addresses that own the given NFT.", + "items": { + "type": "string" + } + }, + "pageKey": { + "type": "string" + } + } + }, + "examples": { + "getOwnersForNFT_response": { + "value": { + "owners": [ + "0x9f4F78A6c4a5E6F8AFA81631b9120ae3C831b494" + ], + "pageKey": null + } + } + } + } + } + } + }, + "operationId": "getOwnersForNFT-v3" + } + }, + "/v3/{apiKey}/getOwnersForContract": { + "get": { + "summary": "Owners By Contract", + "description": "getOwnersForContract - Retrieves all owners associated with a specific NFT contract. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).", + "tags": [ + "NFT Ownership Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0x495f947276749ce646f68ac8c248420045cb7b5e" + }, + "required": true + }, + { + "name": "withTokenBalances", + "description": "Boolean - If set to `true` the query will include the token balances per token id for each owner. `false` by default.", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "description": "String - used for contracts with >50,000 owners. `pageKey` field can be passed back as request parameter to get the next page of results.", + "name": "pageKey", + "schema": { + "type": "string" + }, + "in": "query" + } + ], + "responses": { + "200": { + "description": "Returns a list of all owners for the specified contract.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "owners": { + "description": "List of all addresses that own one of the NFTs from the queried contract address. The format is applicable when `withTokenBalances=true`.", + "type": "array", + "items": { + "type": "object", + "properties": { + "ownerAddress": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "tokenBalances": { + "type": "array", + "description": "a list of the token ids and balances for the owner of the collection", + "items": { + "type": "object", + "properties": { + "tokenId": { + "type": "string", + "description": "tokenId of the NFT in the collection that an owner has" + }, + "balance": { + "type": "integer", + "description": "the number of the specified token in the collection that the user owns" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "operationId": "getOwnersForContract-v3" + } + }, + "/v3/{apiKey}/getSpamContracts": { + "get": { + "summary": "Spam Contracts", + "description": "Returns a list of all spam contracts marked by Alchemy.\n\nPlease note that this API endpoint is only available to paid tier customers. Upgrade your account [here](https://dashboard.alchemy.com/account).\n\nSpam NFT functionality is available on Mainnet for the following chains: Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast. More to come soon!\n", + "tags": [ + "NFT Spam Endpoints" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet", + "polygon-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns a list of all spam contracts marked by Alchemy.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Object that contains a list of contract addresses.", + "properties": { + "contractAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of contract addresses earmarked as spam by Alchemy." + } + } + } + } + } + } + }, + "operationId": "getSpamContracts-v3" + } + }, + "/v3/{apiKey}/isSpamContract": { + "get": { + "summary": "Is Spam Contract", + "description": "Determines whether a specific contract is marked as spam by Alchemy.\n\nPlease note that this API endpoint is only available to paid tier customers. Upgrade your account [here](https://dashboard.alchemy.com/account).\n\nSpam NFT functionality is available on Mainnet for the following chains: Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast. More to come soon!\n", + "tags": [ + "NFT Spam Endpoints" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet", + "polygon-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0x495f947276749ce646f68ac8c248420045cb7b5e" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns whether the specified contract is marked as spam.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "This object contains a boolean value indicating the spam status of the contract.", + "properties": { + "isSpamContract": { + "type": "boolean", + "description": "True - if the queried contract is marked as spam.\nFalse - if the queried contract is considered valid.\n" + } + } + } + } + } + } + }, + "operationId": "isSpamContract-v3" + } + }, + "/v3/{apiKey}/isAirdropNFT": { + "get": { + "summary": "Is Airdrop NFT", + "description": "Determines whether a specific token is marked as an airdrop. Airdrops are defined as NFTs minted to a user address in a transaction sent by a different address.\n\nPlease note that this endpoint is only available on Ethereum (mainnet only) & Polygon (mainnet, amoy & mumbai).\n", + "tags": [ + "NFT Spam Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + }, + { + "name": "tokenId", + "description": "String - The ID of the token. Can be in hex or decimal format.", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns whether the specified token is marked as an airdrop.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "This object contains a boolean value indicating whether the token is an airdrop.", + "properties": { + "isAirdrop": { + "type": "boolean", + "description": "True - if the queried token is marked as an airdrop.\nFalse - if the queried token is not marked as an airdrop.\n" + } + } + } + } + } + } + }, + "operationId": "isAirdropNFT-v3" + } + }, + "/v3/{apiKey}/summarizeNFTAttributes": { + "get": { + "summary": "Attributes Summary By Contract", + "description": "Generates a summary of attribute prevalence for a specific NFT collection.\n\nPlease note that this endpoint is only available on Ethereum (mainnet) & Polygon (mainnet & mumbai).\n", + "tags": [ + "NFT Metadata Endpoints" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet", + "polygon-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns a summary of attribute prevalence for the specified NFT collection.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Prevalence counts for each attribute within a collection.", + "properties": { + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "summary": { + "type": "object", + "description": "Object mapping trait types to the prevalence of each trait within that type." + }, + "contractAddress": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + } + } + }, + "examples": { + "summarizeNFTAttributes_response": { + "value": { + "summary": { + "Earrings": { + "WoW Coins": 437, + "Pizza Lovers": 188, + "Lucky Charms": 415, + "White Ovals": 210, + "Artist Palettes": 21, + "Queen's Emeralds": 206, + "Silver Drops": 822, + "Flower Power": 366, + "Pearls": 833, + "Spikes": 776, + "Yam's Fave": 381, + "Classic Hoops": 780, + "Ocean Hoops": 770, + "Triple Rings": 823, + "60s Fantasy": 399, + "Lightning Bolts": 226, + "Empresses Of Darkness": 103 + }, + "Necklace": { + "WoW Coin": 481, + "Malka": 494, + "Amazonite Energy": 490, + "Satin Choker": 721, + "Back To The 90s": 706, + "Empress of Darkness": 58, + "Spike Choker": 449, + "Golden Bib": 251, + "Golden Flakes": 710, + "Art Lover": 29, + "Rainbow": 474, + "Gold Ruler": 477, + "Wolf Pendant": 229, + "Tutti Frutti Beads": 691, + "Sun Keeper": 730 + }, + "Eyes": { + "Purple To The Left": 158, + "Heterochromia To The Left": 57, + "Brown To The Right": 455, + "Black Eye Roll": 881, + "Yellow To The Left": 141, + "Purple Eye Roll": 145, + "Green Straight": 433, + "Blue To The Left": 407, + "Green To The Right": 410, + "Black Straight": 794, + "Purple To The Right": 145, + "Black To The Right": 870, + "Green Eye Roll": 413, + "Yellow Straight": 128, + "Brown To The Left": 465, + "Brown Eye Roll": 416, + "Heterochromia To The Right": 76, + "Blue Straight": 415, + "Black To The Left": 877, + "Heterochromia Eye Roll": 85, + "Purple Straight": 158, + "Brown Straight": 434, + "Yellow Eye Roll": 141, + "Heterochromia Straight": 77, + "Yellow To The Right": 139, + "Blue To The Right": 416, + "Green To The Left": 424, + "Blue Eye Roll": 440 + }, + "Background": { + "Green Purple": 905, + "Purple Pink": 905, + "Dark Emerald": 924, + "Yellow Pink": 896, + "Pink Pastel": 849, + "Blue Green": 924, + "Soft Purple": 983, + "Green Orange": 907, + "Dark Purple": 876, + "Red Turquoise": 914, + "Orange Yellow": 917 + }, + "Mouth": { + "Cigarette": 502, + "Whistle": 868, + "Slight Smile": 1666, + "Stern": 1733, + "Countryside": 927, + "Huh": 506, + "Slightly Open": 1661, + "Bubble Gum": 404, + "Surprised": 1733 + }, + "Clothes": { + "80s Silk Shirt": 400, + "70s Shirt": 421, + "Fantasy Shirt": 542, + "Adventurer": 583, + "Striped Tee": 567, + "Naiade": 98, + "Tunic": 193, + "Checkmate": 396, + "Painter's Overall": 550, + "Witch Dress": 198, + "Little Red Dress": 437, + "Cabaret Corset": 535, + "Polka Dot Top": 573, + "Freedom Is Power Tee": 368, + "Warrior Armor": 177, + "Emerald Elven Cape": 117, + "Faux Fur Coat": 404, + "Red Leather Jacket": 374, + "White Tee": 533, + "Tuxedo": 100, + "Steampunk Octopus Top": 186, + "Queen's Dress": 391, + "Cherry Tee": 590, + "NFT Goddesses Top": 189, + "Gala Dress": 192, + "Psychedelic Dress": 492, + "Futuristic Dress": 394 + }, + "Facial Features": { + "Nose Piercing": 598, + "Red Eyeliner": 608, + "Leader": 224, + "Neck Tattoo": 227, + "Pearl Eyes": 207, + "Red Blue Bolt": 97, + "Rose Tattoo": 286, + "Feline Eyes": 590, + "Elven Warrior": 99, + "Marilyn": 633, + "Freckles": 581, + "Flashy Blue": 304, + "Sunset": 297, + "Heart Tattoo": 591, + "Rainbow": 578, + "Eyebrow Tattoo MMXXI": 303, + "Eye Scar": 308, + "Treble Bass Clef Tattoo": 210, + "Crystal Queen": 221, + "Antoinette": 582, + "Cyber Warrior": 120, + "Eyebrow Piercing": 619, + "Claw Scar": 236 + }, + "Hairstyle": { + "Badass Bob": 178, + "Curly Ponytail": 390, + "Finger Waves": 398, + "Colorful": 186, + "Fuchsia": 562, + "Retro": 408, + "Royal": 227, + "Boy Cut": 566, + "Bob": 653, + "Bun": 607, + "Long Dark": 416, + "Curly Pearl Updo": 122, + "Lucky Green": 417, + "Lioness": 600, + "Natural Red": 608, + "Double Buns": 182, + "Cotton Candy": 228, + "Rose Hair": 388, + "Purple Rainbow": 187, + "Lollipop": 612, + "Silver": 205, + "Braided Ponytail": 561, + "Platinum Pixie": 570, + "Black And White": 110, + "Feeling Turquoise": 412 + }, + "Lips Color": { + "Space": 195, + "Gold": 622, + "Purple": 1967, + "Burgundy": 1995, + "Party Pink": 1114, + "Passion Red": 3008, + "Flashy Blue": 1099 + }, + "Skin Tone": { + "Rainbow Bright": 197, + "Light Warm Yellow": 1021, + "Burning Red": 497, + "Cyber Green": 511, + "Night Goddess": 85, + "Deep Warm Gold": 1026, + "Light Medium Warm Gold": 997, + "Deep Bronze": 1047, + "Medium Olive": 976, + "Deep Neutral": 996, + "Medium Gold": 937, + "Light Warm Olive": 1031, + "Cool Blue": 486, + "Golden": 193 + }, + "Face Accessories": { + "Oversized Statement Sunglasses": 396, + "Psychedelic Sunglasses": 390, + "Resting Butterfly": 83, + "Red Round Sunglasses": 695, + "Classic Aviator WoW": 414, + "Black Mask": 398, + "Cateye Sunglasses": 221, + "On Fire": 116, + "70s Feels": 718, + "3D Glasses": 216, + "Round Glasses": 704, + "Black Round Retro": 403, + "Hypnotic Glasses": 209 + } + }, + "totalSupply": "10000", + "contractAddress": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + } + } + } + } + } + } + }, + "operationId": "summarizeNFTAttributes-v3" + } + }, + "/v3/{apiKey}/getFloorPrice": { + "get": { + "summary": "Floor Prices By Slug", + "description": "Retrieves the floor prices of an NFT collection across different marketplaces.\n\nPlease note that this endpoint is only available on Ethereum mainnet for Opensea & Looksrare marketplaces.\n", + "tags": [ + "NFT Sales Endpoints" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0x1f02bf9dde7c79137a08b2dd4fc964bfd2499734" + }, + "required": true + }, + { + "name": "collectionSlug", + "description": "String - OpenSea slug for the NFT collection.", + "in": "query", + "schema": { + "type": "string", + "default": "boredapeyachtclub" + }, + "required": false + } + ], + "responses": { + "200": { + "description": "Returns the floor prices of the specified NFT collection across different marketplaces.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nftMarketplaceName": { + "type": "object", + "description": "Name of the NFT marketplace where the collection is listed (in camel case). Current marketplaces supported - `openSea`, `looksRare`. So instead of the word `nftMarketplaceName` you will see marketplace names like `openSea` here.", + "properties": { + "floorPrice": { + "type": "number", + "description": "Number - The floor price of the collection on the given marketplace." + }, + "priceCurrency": { + "type": "string", + "description": "String - The currency in which the floor price is denominated. Typically, denominated in ETH", + "enum": [ + "ETH" + ] + }, + "collectionUrl": { + "type": "string", + "description": "String - Link to the collection on the given marketplace." + }, + "retrievedAt": { + "type": "string", + "description": "String - UTC timestamp of when the floor price was retrieved from the marketplace." + }, + "error": { + "type": "string", + "description": "String - Returns the error `unable to fetch floor price` if there was an error fetching floor prices from the given marketplace." + } + } + } + } + } + } + } + } + }, + "operationId": "getFloorPrice-v3" + } + }, + "/v3/{apiKey}/searchContractMetadata": { + "get": { + "summary": "Search Contract Metadata", + "description": "Searches for a keyword across metadata of all ERC-721 and ERC-1155 smart contracts.\n\nThis endpoint is currently in BETA. If you have any questions or feedback, please contact us at support@alchemy.com or open a ticket in the dashboard.\n\nPlease note that this endpoint is only available on **Ethereum**, **Polygon**, **Arbitrum**, **Optimism** & **Base** networks.\n", + "tags": [ + "NFT Metadata Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "query", + "description": "String - The search string that you want to search for in contract metadata", + "in": "query", + "schema": { + "type": "string", + "default": "bored" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns the list of NFT contracts where the metadata has one or more keywords from the search string.", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "List of contracts where the metadata contains one or more keywords from the search string.", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "String - Contract address for the queried NFT collection" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + } + } + } + } + } + }, + "operationId": "searchContractMetadata-v3" + } + }, + "/v3/{apiKey}/isHolderOfContract": { + "get": { + "summary": "Is Holder Of Contract", + "description": "isHolderOfContract - Determines whether a specific wallet holds any NFT from a given contract. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).", + "tags": [ + "NFT Ownership Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "wallet", + "description": "String - Wallet address to check for contract ownership.", + "schema": { + "type": "string", + "default": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + }, + "in": "query", + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns whether the specified wallet holds any NFT from the given contract.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Data related to a wallet's ownership of any token in an NFT contract.", + "properties": { + "isHolderOfContract": { + "type": "boolean", + "description": "Whether the given wallet owns any token in the given NFT contract." + } + } + } + } + } + } + }, + "operationId": "isHolderOfContract-v3" + } + }, + "/v3/{apiKey}/computeRarity": { + "get": { + "summary": "Attribute Rarity By NFT", + "description": "Calculates the rarity of each attribute within an NFT.\n\nPlease note that this endpoint is only available on Ethereum (mainnet) & Polygon (mainnet & mumbai).\n", + "tags": [ + "NFT Metadata Endpoints" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet", + "polygon-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + }, + { + "name": "tokenId", + "description": "String - The ID of the token. Can be in hex or decimal format.", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns the rarity information for each attribute of the specified NFT.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Object containing the rarity info of the collection.", + "properties": { + "rarities": { + "type": "array", + "description": "NFT attributes and their associated prevalence.", + "items": { + "type": "object", + "properties": { + "trait_type": { + "type": "string", + "description": "Name of the trait category, i.e., Hat, Color, Face, etc." + }, + "value": { + "type": "string", + "description": "Value for the trait, i.e., White Cap, Blue, Angry, etc." + }, + "prevalence": { + "type": "number", + "description": "Floating point value from 0 to 1 representing the prevalence of this value for this trait type." + } + } + } + } + } + } + } + } + } + }, + "operationId": "computeRarity-v3" + } + }, + "/v3/{apiKey}/getNFTSales": { + "get": { + "summary": "NFT Sales", + "description": "Retrieves NFT sales that have occurred through on-chain marketplaces.\n\nPlease note that this endpoint is only available on Ethereum (Seaport, Wyvern, X2Y2, Blur, LooksRare, Cryptopunks), Polygon (Seaport) & Optimism (Seaport) mainnets.\n", + "tags": [ + "NFT Sales Endpoints" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet", + "polygon-mainnet", + "opt-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "fromBlock", + "description": "String - The block number to start fetching NFT sales data from. Allowed values are decimal and hex integers, and \"latest\". Defaults to \"0\".", + "in": "query", + "schema": { + "type": "string", + "default": "0" + } + }, + { + "name": "toBlock", + "description": "String - The block number to start fetching NFT sales data from. Allowed values are decimal and hex integers, and \"latest\". Defaults to \"latest\".", + "in": "query", + "schema": { + "type": "string", + "default": "latest" + } + }, + { + "name": "order", + "description": "Enum - Whether to return the results ascending from startBlock or descending from startBlock. Defaults to descending (false).", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + }, + { + "name": "marketplace", + "description": "Enum - The name of the NFT marketplace to filter sales by. The endpoint currently supports \"seaport\", \"wyvern\", \"looksrare\", \"x2y2\", \"blur\", and \"cryptopunks\". Defaults to returning sales from all supported marketplaces.", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "seaport", + "looksrare", + "x2y2", + "wyvern", + "blur", + "cryptopunks" + ] + }, + "required": false + }, + { + "description": "String - The contract address of an NFT collection to filter sales by. Defaults to returning all NFT contracts.", + "name": "contractAddress", + "in": "query", + "schema": { + "type": "string" + }, + "required": false + }, + { + "description": "String - The token ID of an NFT within the collection specified by contractAddress to filter sales by. Defaults to returning all token IDs.", + "name": "tokenId", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": false + }, + { + "name": "buyerAddress", + "description": "String - The address of the NFT buyer to filter sales by. Defaults to returning sales involving any buyer.", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "sellerAddress", + "description": "String - The address of the NFT seller to filter sales by. Defaults to returning sales involving any seller.", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "taker", + "description": "Enum - Filter by whether the buyer or seller was the taker in the NFT trade. Allowed filter values are \"BUYER\" and \"SELLER\". Defaults to returning both buyer and seller taker trades.", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "BUYER", + "SELLER" + ] + }, + "required": false + }, + { + "description": "Integer - The maximum number of NFT sales to return. Maximum and default values are 1000.", + "name": "limit", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "pageKey", + "description": "String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results.", + "schema": { + "type": "string" + }, + "in": "query" + } + ], + "responses": { + "200": { + "description": "Returns a list of NFT sales that match the query parameters.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nftSales": { + "description": "List of NFT sales that match the query.", + "type": "array", + "items": { + "type": "object", + "properties": { + "marketplace": { + "type": "string", + "description": "String - The marketplace the sale took place on." + }, + "marketplaceAddress": { + "type": "string", + "description": "String - The address of the marketplace contract." + }, + "contractAddress": { + "type": "string", + "description": "String - The contract address of the collection the NFT belongs to." + }, + "tokenId": { + "type": "string", + "description": "String - The decimal token ID of the NFT being sold." + }, + "quantity": { + "type": "string", + "description": "Integer - The number of tokens sold in the sale as a decimal integer string." + }, + "buyerAddress": { + "type": "string", + "description": "String - The address of the buyer in the NFT sale." + }, + "sellerAddress": { + "type": "string", + "description": "String - The address of the seller in the NFT sale." + }, + "taker": { + "type": "string", + "description": "String - Whether the price taker in the trade was the buyer or the seller.", + "enum": [ + "BUYER", + "SELLER" + ] + }, + "sellerFee": { + "type": "object", + "description": "The payment from buyer to the seller.", + "properties": { + "amount": { + "type": "string", + "description": "String - The amount of the payment from the buyer to seller as a decimal integer string." + }, + "tokenAddress": { + "type": "string", + "description": "String - The smart contract address of the token used for the payment." + }, + "symbol": { + "type": "string", + "description": "String - The symbol of the token used for the payment." + }, + "decimals": { + "type": "integer", + "description": "Integer - The number of decimals of the token used for the payment." + } + } + }, + "protocolFee": { + "type": "object", + "description": "The payment from buyer to the NFT marketplace protocol.", + "properties": { + "amount": { + "type": "string", + "description": "String - The amount of the payment to the marketplace as a decimal integer string." + }, + "tokenAddress": { + "type": "string", + "description": "String - The smart contract address of the token used for the payment." + }, + "symbol": { + "type": "string", + "description": "String - The symbol of the token used for the payment." + }, + "decimals": { + "type": "integer", + "description": "Integer - The number of decimals of the token used for the payment." + } + } + }, + "royaltyFee": { + "type": "object", + "description": "The payment from buyer to the royalty address of the NFT collection.", + "properties": { + "amount": { + "type": "string", + "description": "String - The amount of the payment to the royalty collector as a decimal integer string." + }, + "tokenAddress": { + "type": "string", + "description": "String - The smart contract address of the token used for the payment." + }, + "symbol": { + "type": "string", + "description": "String - The symbol of the token used for the payment." + }, + "decimals": { + "type": "integer", + "description": "Integer - The number of decimals of the token used for the payment." + } + } + }, + "blockNumber": { + "type": "integer", + "description": "Integer - The block number the NFT sale took place in." + }, + "logIndex": { + "type": "integer", + "description": "Integer - The log number of the sale event emitted within the block." + }, + "bundleIndex": { + "type": "integer", + "description": "Integer - The index of the token within the bundle of NFTs sold in the sale." + }, + "transactionHash": { + "type": "string", + "description": "String - The transaction hash of the transaction containing the sale." + } + } + } + }, + "pageKey": { + "type": "string", + "description": "String - The page key to use to fetch the next page of results. Returns null if there are no more results." + }, + "validAt": { + "type": "object", + "description": "Block Information of the block as of which the corresponding data is valid", + "properties": { + "blockNumber": { + "type": "integer", + "description": "The block number above information is valid as of" + }, + "blockHash": { + "type": "string", + "description": "The block hash above information is valid as of" + }, + "blockTimestamp": { + "type": "string", + "description": "The block timestamp above information is valid as of" + } + } + } + } + }, + "examples": { + "nftSales_response": { + "summary": "Response (with pagination)", + "value": { + "nftSales": [ + { + "marketplace": "seaport", + "contractAddress": "0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b", + "tokenId": "13749", + "quantity": "1", + "buyerAddress": "0x78f6c2458b53d0735208992c693bb2b2dafebb52", + "sellerAddress": "0x558a18f94cabdea4e47c5965384f457d8e870419", + "taker": "BUYER", + "sellerFee": { + "amount": "11100000000000000000", + "symbol": "ETH", + "decimals": 18 + }, + "protocolFee": { + "amount": "300000000000000000", + "symbol": "ETH", + "decimals": 18 + }, + "royaltyFee": { + "amount": "600000000000000000", + "symbol": "ETH", + "decimals": 18 + }, + "blockNumber": 15000002, + "logIndex": 130, + "bundleIndex": 0, + "transactionHash": "0xecfa1b29c9016bd2556fde637c6b48484eeb14f273af54c49317e3856ab7cb16" + }, + { + "marketplace": "looksrare", + "contractAddress": "0x34d85c9cdeb23fa97cb08333b511ac86e1c4e258", + "tokenId": "75417", + "quantity": "1", + "buyerAddress": "0xb3aa9923489bc2bfec323bf05346acd4afbc92a0", + "sellerAddress": "0x206ccba024c236dced07c35b4e9eb0bade7ef166", + "taker": "BUYER", + "sellerFee": { + "amount": "2222700000000000000", + "symbol": "WETH", + "decimals": 18 + }, + "protocolFee": { + "amount": "47800000000000000", + "symbol": "WETH", + "decimals": 18 + }, + "royaltyFee": { + "amount": "119500000000000000", + "symbol": "WETH", + "decimals": 18 + }, + "blockNumber": 15000002, + "logIndex": 197, + "bundleIndex": 0, + "transactionHash": "0x4c23163e4f855e143e573776bc6129bee370dff6ce760e71553fc93201b292e2" + } + ], + "pageKey": "MTUwMDAwNzgsODcsMA", + "validAt": { + "blockNumber": 17091500, + "blockHash": "0x2a34a65c4e0cd7fdf187d6a497214ad2bee255d2d3501868a6b8c09b4d1261bd", + "blockTimestamp": "2023-04-21T01:25:59Z" + } + } + } + } + } + } + } + }, + "operationId": "getNFTSales-v3" + } + }, + "/v3/{apiKey}/getContractsForOwner": { + "get": { + "summary": "Contracts By Owner", + "description": "getContractsForOwner - Retrieves all NFT contracts held by a specified owner address.", + "tags": [ + "NFT Ownership Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "owner", + "description": "String - Address for NFT owner (can be in ENS format for Eth Mainnet).", + "schema": { + "type": "string", + "default": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + }, + "in": "query", + "required": true + }, + { + "name": "pageKey", + "description": "String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results.", + "schema": { + "type": "string" + }, + "in": "query" + }, + { + "name": "pageSize", + "description": "Number of NFTs to be returned per page. Defaults to 100. Max is 100.", + "schema": { + "type": "integer", + "default": 100 + }, + "in": "query" + }, + { + "name": "withMetadata", + "description": "Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "schema": { + "type": "boolean", + "default": true + }, + "in": "query" + }, + { + "name": "includeFilters[]", + "description": "Array of filters (as ENUMS) that will be applied to the query. Only NFTs that match one or more of these filters will be included in the response. May not be used in conjunction with excludeFilters[]. Filter Options:\n - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**.\n - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only.\n - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts)", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "in": "query" + }, + { + "name": "excludeFilters[]", + "description": "Array of filters (as ENUMS) that will be applied to the query. NFTs that match one or more of these filters will be excluded from the response. May not be used in conjunction with includeFilters[]. Filter Options:\n - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**.\n - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only.\n - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts)", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "in": "query" + }, + { + "name": "orderBy", + "description": "Enum - ordering scheme to use for ordering NFTs in the response. If unspecified, NFTs will be ordered by contract address and token ID.\n - transferTime: NFTs will be ordered by the time they were transferred into the wallet, with newest NFTs first. Note: This ordering is supported on Ethereum Mainnet, Optimism Mainnet, Polygon Mainnet, Base Mainnet, Arbitrum One, Polygon Amoy, Base Sepolia, Arbitrum Sepolia, Ethereum Sepolia, and Optimism Sepolia.", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "transferTime" + ] + }, + "required": false + }, + { + "name": "spamConfidenceLevel", + "description": "Enum - the confidence level at which to filter spam at.\n\nConfidence Levels:\n - VERY_HIGH\n - HIGH\n - MEDIUM\n - LOW\n\nThe confidence level set means that any spam that is at that confidence level or higher will be filtered out. For example, if the confidence level is HIGH, contracts that we have HIGH or VERY_HIGH confidence in being spam will be filtered out from the response. \nDefaults to VERY_HIGH for Ethereum Mainnet and MEDIUM for Matic Mainnet.\n\n**Please note that this filter is only available on paid tiers. Upgrade your account [here](https://dashboard.alchemy.com/settings/billing/).**", + "schema": { + "type": "string", + "enum": [ + "VERY_HIGH", + "HIGH", + "MEDIUM", + "LOW" + ] + }, + "in": "query", + "required": false + } + ], + "responses": { + "200": { + "description": "Returns a list of NFT contracts held by the specified owner address.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "contracts": { + "type": "array", + "items": { + "type": "object", + "description": "The object that represents a smart contract and has all data corresponding to that contract", + "properties": { + "address": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "name": { + "description": "The name of the contract, i.e. \"Bored Ape Yacht Club\".", + "type": "string" + }, + "symbol": { + "description": "The symbol of the contract, i.e. BAYC.", + "type": "string" + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string" + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "totalBalance": { + "type": "number", + "description": "Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens." + }, + "numDistinctTokensOwned": { + "type": "number", + "description": "Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens." + }, + "isSpam": { + "type": "boolean", + "description": "`True` if the contract is detected as spam contract. `False` if it is not spam or has not been evaluated by our system yet" + }, + "displayNft": { + "type": "object", + "description": "Details of the display NFT for this contract. This NFT and its image can be used to represent the contract when displaying info about it.", + "properties": { + "tokenId": { + "description": "One of the tokens from this contract held by the owner.", + "type": "string" + }, + "name": { + "description": "The title of the token held by the owner i.e. \"Something #22\".", + "type": "string" + } + } + }, + "image": { + "type": "object", + "description": "Details of the image corresponding to this contract", + "properties": { + "cachedUrl": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "thumbnailUrl": { + "type": "string", + "description": "The Url that has the thumbnail version of the NFT" + }, + "pngUrl": { + "type": "string", + "description": "The Url that has the NFT image in png" + }, + "contentType": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "size": { + "type": "integer", + "description": "The size of the media asset in bytes." + }, + "originalUrl": { + "type": "string", + "description": "The original Url of the image coming straight from the smart contract" + } + } + } + } + } + }, + "pageKey": { + "type": "string" + }, + "totalCount": { + "type": "string", + "description": "String - Total number of NFT contracts held by the given address returned in this page." + } + } + }, + "examples": { + "withoutMetadata": { + "summary": "Response (withMetadata = false)", + "value": { + "contracts": [ + { + "address": "0x000386e3f7559d9b6a2f5c46b4ad1a9587d59dc3", + "totalBalance": 912, + "numDistinctTokensOwned": 80, + "isSpam": true, + "tokenId": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "address": "0x0015f391949f25c3211063104ad4afc99210f85c", + "totalBalance": 17, + "numDistinctTokensOwned": 6, + "isSpam": true, + "tokenId": "0x0000000000000000000000000000000000000000000000000000000000000002" + }, + { + "address": "0x005b92d71a934dbe48e985b6469881cf4b0308fc", + "totalBalance": 1, + "numDistinctTokensOwned": 1, + "isSpam": true, + "tokenId": "0x0000000000000000000000000000000000000000000000000000000000000003" + } + ], + "totalCount": 2120, + "pageKey": "20ef9df5-0d81-42e5-b741-140f595a407b" + } + }, + "withMetadata": { + "summary": "Response (withMetadata = true)", + "value": { + "contracts": [ + { + "address": "0x1C310c2fbB0D9755A6b918F990bC8D3504f2c684", + "name": "The Wonderful Husl Founder Cards", + "symbol": "The Wonderful Husl Founder Cards", + "totalSupply": null, + "tokenType": "ERC1155", + "contractDeployer": "0x0bdD0AEC835F92a465290cdd57b27FBd00376F53", + "deployedBlockNumber": 15664554, + "openSeaMetadata": { + "floorPrice": null, + "collectionName": "The Wonderful Husl Founder Cards", + "safelistRequestStatus": "not_requested", + "imageUrl": "https://i.seadn.io/gcs/files/754e38769c80c9d6188444dddb10ec80.png?w=500&auto=format", + "description": "[Husl](https://www.huslnft.xyz) is building the bridge between business and NFTs. Husl Founders are the driven, the passionate and the focused members of the community ready to change their future. Owning a Founders Card gets you exclusive perks, early access to business management, and discounts on managed services for your business as NFT. [Learn More](https://www.huslnft.xyz)", + "externalUrl": "https://www.huslnft.xyz", + "twitterUsername": null, + "discordUrl": null, + "lastIngestedAt": "2023-03-20T01:36:19.000Z" + }, + "totalBalance": "1", + "numDistinctTokensOwned": "1", + "isSpam": true, + "displayNft": { + "tokenId": "233", + "name": null + }, + "image": { + "cachedUrl": "https://nft-cdn.alchemy.com/eth-mainnet/d08d0d0fac8edf36ea09eae34b332814", + "thumbnailUrl": "https://res.cloudinary.com/alchemyapi/image/upload/thumbnailv2/eth-mainnet/d08d0d0fac8edf36ea09eae34b332814", + "pngUrl": "https://res.cloudinary.com/alchemyapi/image/upload/convert-png/eth-mainnet/d08d0d0fac8edf36ea09eae34b332814", + "contentType": "video/mp4", + "size": 36190302, + "originalUrl": "https://ipfs.io/ipfs/QmX2mM8r33W7KUBQSWXFAKNC2t654EXmWiX9vkrfrEaEnS" + } + } + ], + "totalCount": 2120, + "pageKey": "03949322-9b2c-4fdd-aab6-1369e29fa5b2" + } + } + } + } + } + } + }, + "operationId": "getContractsForOwner-v3" + } + }, + "/v3/{apiKey}/getCollectionsForOwner": { + "get": { + "summary": "Collections By Owner", + "description": "Retrieves all NFT collections held by a specified owner address.\n\nThis endpoint is only supported on Ethereum. Use `getContractsForOwner` for support across all other chains we support!\n", + "tags": [ + "NFT Ownership Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "owner", + "description": "String - Address for NFT owner (can be in ENS format for Eth Mainnet).", + "schema": { + "type": "string", + "default": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + }, + "in": "query", + "required": true + }, + { + "name": "pageKey", + "description": "String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results.", + "schema": { + "type": "string" + }, + "in": "query" + }, + { + "name": "pageSize", + "description": "Number of NFTs to be returned per page. Defaults to 100. Max is 100.", + "schema": { + "type": "integer", + "default": 100 + }, + "in": "query" + }, + { + "name": "withMetadata", + "description": "Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "schema": { + "type": "boolean", + "default": true + }, + "in": "query" + }, + { + "name": "includeFilters[]", + "description": "Array of filters (as ENUMS) that will be applied to the query. Only NFTs that match one or more of these filters will be included in the response. May not be used in conjunction with excludeFilters[]. Filter Options:\n - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**.\n - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only.\n - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts)", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "in": "query" + }, + { + "name": "excludeFilters[]", + "description": "Array of filters (as ENUMS) that will be applied to the query. NFTs that match one or more of these filters will be excluded from the response. May not be used in conjunction with includeFilters[]. Filter Options:\n - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**.\n - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only.\n - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts)", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "in": "query" + } + ], + "responses": { + "200": { + "description": "Returns a list of NFT collections held by the specified owner address.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "collections": { + "type": "array", + "items": { + "type": "object", + "description": "Metadata for an NFT collection held by an owner address. Includes general metadata about the collection, as well as information specific to the owner such as the total balance and the token ID of a random NFT for display purposes.", + "properties": { + "name": { + "description": "The name of the collection, i.e. \"Bored Ape Yacht Club\".", + "type": "string" + }, + "slug": { + "description": "The human-readable string used to identify the collection on OpenSea.", + "type": "string" + }, + "floorPrice": { + "type": "object", + "description": "Floor price data for the collection", + "properties": { + "marketplace": { + "description": "The marketplace the floor price is on", + "type": "string" + }, + "floorPrice": { + "description": "Floor price of the collection on the marketplace", + "type": "number" + }, + "priceCurrency": { + "description": "The currency of the floor price", + "type": "string" + } + } + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "contract": { + "type": "object", + "description": "Contract-level data for a collection, such as contract type, name, and symbol.", + "properties": { + "address": { + "description": "Address of the contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + } + } + }, + "totalBalance": { + "type": "number", + "description": "Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens." + }, + "numDistinctTokensOwned": { + "type": "number", + "description": "Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens." + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "displayNft": { + "type": "object", + "description": "Details of the display NFT for this contract. This NFT and its image can be used to represent the contract when displaying info about it.", + "properties": { + "tokenId": { + "description": "One of the tokens from this contract held by the owner.", + "type": "string" + }, + "name": { + "description": "The title of the token held by the owner i.e. \"Something #22\".", + "type": "string" + } + } + }, + "image": { + "type": "object", + "description": "Details of the image corresponding to this contract", + "properties": { + "cachedUrl": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "thumbnailUrl": { + "type": "string", + "description": "The Url that has the thumbnail version of the NFT" + }, + "pngUrl": { + "type": "string", + "description": "The Url that has the NFT image in png" + }, + "contentType": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "size": { + "type": "integer", + "description": "The size of the media asset in bytes." + }, + "originalUrl": { + "type": "string", + "description": "The original Url of the image coming straight from the smart contract" + } + } + } + } + } + }, + "pageKey": { + "type": "string" + }, + "totalCount": { + "type": "string", + "description": "String - Total number of NFT collections held by the given address." + } + } + }, + "examples": { + "withoutMetadata": { + "summary": "Response (withMetadata = false)", + "value": { + "collections": [ + { + "address": "0x3a5051566b2241285BE871f650C445A88A970edd", + "name": "The Humanoids ", + "slug": "thehumanoids", + "totalBalance": "1", + "numDistinctTokensOwned": "1", + "isSpam": false + } + ], + "totalCount": 2120, + "pageKey": "20ef9df5-0d81-42e5-b741-140f595a407b" + } + }, + "withMetadata": { + "summary": "Response (withMetadata = true)", + "value": { + "collections": [ + { + "name": "The Humanoids", + "slug": "thehumanoids", + "floorPrice": { + "marketplace": "seaport", + "floorPrice": 0.37, + "priceCurrency": "ETH" + }, + "description": "The Humanoids (Gen 1) is a collection of 10,000 unique 3D 4K personalities.\n\n[GEN 1.1 (CUSTOMIZABLE PFP)](https://opensea.io/collection/the-humanoids-gen-1-1/) | [DISCORD](https://discord.gg/thehumanoids) | [TWITTER](https://twitter.com/thehumanoids)\n\nStake your Gen 1 Humanoid and earn $ION to customize Gen 1.1 Humanoids using our proprietary Trait Factory.\n\nNote: Holder Count is inaccurate as Humanoids are currently being staked.", + "externalUrl": "http://thehumanoids.com", + "twitterUsername": "thehumanoids", + "discordUrl": "https://discord.gg/thehumanoids", + "contract": { + "address": "0x3a5051566b2241285BE871f650C445A88A970edd", + "name": "The Humanoids ", + "symbol": "HMNDS", + "tokenType": "ERC721", + "contractDeployer": "0xB8256c1c6654cedb9607644b07deC91Ca15fb9f6", + "deployedBlockNumber": 13313830 + }, + "totalBalance": "1", + "numDistinctTokensOwned": "1", + "isSpam": false, + "displayNft": { + "tokenId": "5880", + "name": "Humanoid #5880" + }, + "image": { + "cachedUrl": "https://nft-cdn.alchemy.com/eth-mainnet/57dab2f078ca70e310c387064f66daaa", + "thumbnailUrl": "https://res.cloudinary.com/alchemyapi/image/upload/thumbnailv2/eth-mainnet/57dab2f078ca70e310c387064f66daaa", + "pngUrl": "https://res.cloudinary.com/alchemyapi/image/upload/convert-png/eth-mainnet/57dab2f078ca70e310c387064f66daaa", + "contentType": "image/jpeg", + "size": 1898134, + "originalUrl": "https://ipfs.io/ipfs/QmcjYgWMokcqnaSGZ31GVbGDe9V9z1KeNerRGfgeBEkn4k/5880.jpg" + } + } + ], + "totalCount": 2120, + "pageKey": "03949322-9b2c-4fdd-aab6-1369e29fa5b2" + } + } + } + } + } + } + }, + "operationId": "getCollectionsForOwner-v3" + } + }, + "/v3/{apiKey}/reportSpam": { + "get": { + "summary": "Report Spam Address", + "description": "Reports a specific address to the API if it is suspected to be spam.\n\nSpam NFT functionality is available on Mainnet for the following chains: Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast. More to come soon!\n", + "tags": [ + "NFT Spam Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "address", + "description": "String - The address to check for spam status.", + "in": "query", + "schema": { + "type": "string", + "default": "0x495f947276749ce646f68ac8c248420045cb7b5e" + }, + "required": true + }, + { + "name": "isSpam", + "in": "query", + "required": true, + "schema": { + "type": "boolean", + "description": "Whether the address is spam.", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Returns a confirmation message if the address was successfully reported as spam.", + "content": { + "application/json": { + "schema": { + "type": "string", + "description": "String - \"Address was successfully reported as spam\" if calling the API was successful." + } + } + } + } + }, + "operationId": "reportSpam-v3" + } + }, + "/v3/{apiKey}/refreshNftMetadata": { + "post": { + "summary": "Refresh NFT Metadata", + "description": "Submits a request for Alchemy to refresh the cached metadata of a specific NFT token.\n\nPlease note that this endpoint is only supported on Ethereum (Mainnet & Sepolia), Polygon (Mainnet, Mumbai & Amoy), Arbitrum One (mainnet), Optimism (mainnet) & Base (mainnet). For other chains, you could use the `getNFTMetadata` endpoint with the `refreshCache` parameter set to `true` to refresh the metadata!\n", + "tags": [ + "NFT Metadata Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "contractAddress", + "tokenId" + ], + "properties": { + "contractAddress": { + "type": "string", + "description": "Contract address of the token you want to refresh.", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "tokenId": { + "type": "string", + "description": "Token ID of the token you want to refresh. Must belong to the contract address.", + "default": "44" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Returns the status of the refresh request along with the estimated time to complete.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "If the token is successfully queued for ingestion the value will be \"Queued\"." + }, + "estimatedMsToRefresh": { + "type": "string", + "description": "Estimated time until the metadata refresh is complete for this token." + } + } + }, + "examples": { + "byDefault": { + "summary": "Successful Response", + "value": { + "status": "Queued", + "estimatedMsToRefresh": 10000 + } + } + } + } + } + } + }, + "operationId": "refreshNftMetadata-v3" + } + }, + "/v2/{apiKey}/getNFTs": { + "get": { + "summary": "getNFTs", + "description": "Gets all NFTs currently owned by a given address.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "owner", + "description": "String - Address for NFT owner (can be in ENS format for Eth Mainnet).", + "schema": { + "type": "string", + "default": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + }, + "in": "query", + "required": true + }, + { + "name": "contractAddresses[]", + "description": "Array of contract addresses to filter the responses with. Max limit 45 contracts.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query" + }, + { + "name": "withMetadata", + "description": "Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "schema": { + "type": "boolean", + "default": true + }, + "in": "query" + }, + { + "name": "orderBy", + "description": "Enum - ordering scheme to use for ordering NFTs in the response. If unspecified, NFTs will be ordered by contract address and token ID.\n - transferTime: NFTs will be ordered by the time they were transferred into the wallet, with newest NFTs first. Note: This ordering is supported on Ethereum Mainnet, Optimism Mainnet, Polygon Mainnet, Base Mainnet, Arbitrum One, Polygon Amoy, Base Sepolia, Arbitrum Sepolia, Ethereum Sepolia, and Optimism Sepolia.", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "transferTime" + ] + }, + "required": false + }, + { + "name": "excludeFilters[]", + "description": "Array of filters (as ENUMS) that will be applied to the query. NFTs that match one or more of these filters will be excluded from the response. May not be used in conjunction with includeFilters[]. Filter Options:\n - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**.\n - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only.\n - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts)", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "in": "query" + }, + { + "name": "includeFilters[]", + "description": "Array of filters (as ENUMS) that will be applied to the query. Only NFTs that match one or more of these filters will be included in the response. May not be used in conjunction with excludeFilters[]. Filter Options:\n - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**.\n - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only.\n - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts)", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "in": "query" + }, + { + "name": "spamConfidenceLevel", + "description": "Enum - the confidence level at which to filter spam at.\n\nConfidence Levels:\n - VERY_HIGH\n - HIGH\n - MEDIUM\n - LOW\n\nThe confidence level set means that any spam that is at that confidence level or higher will be filtered out. For example, if the confidence level is HIGH, contracts that we have HIGH or VERY_HIGH confidence in being spam will be filtered out from the response. \nDefaults to VERY_HIGH for Ethereum Mainnet and MEDIUM for Matic Mainnet.\n\n**Please note that this filter is only available on paid tiers. Upgrade your account [here](https://dashboard.alchemy.com/settings/billing/).**", + "schema": { + "type": "string", + "enum": [ + "VERY_HIGH", + "HIGH", + "MEDIUM", + "LOW" + ] + }, + "in": "query", + "required": false + }, + { + "name": "tokenUriTimeoutInMs", + "description": "No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0.", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "pageKey", + "description": "String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results.", + "schema": { + "type": "string" + }, + "in": "query" + }, + { + "name": "pageSize", + "description": "Number of NFTs to be returned per page. Defaults to 100. Max is 100.", + "schema": { + "type": "integer", + "default": 100 + }, + "in": "query" + } + ], + "responses": { + "200": { + "description": "Returns the list of all NFTs owned by the given address and satisfying the given input parameters.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ownedNfts": { + "type": "array", + "items": { + "type": "object", + "description": "The object that represents an NFT and has all data corresponding to that NFT", + "properties": { + "contract": { + "description": "Object - Contract for returned NFT", + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "String - Address of NFT contract." + } + } + }, + "id": { + "type": "object", + "properties": { + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenMetadata": { + "type": "object", + "properties": { + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + } + } + } + } + }, + "balance": { + "type": "string", + "description": "String - Token balance" + }, + "title": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Brief human-readable description" + }, + "tokenUri": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + } + } + }, + "media": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "external_url": { + "type": "string", + "description": "String - The image URL that appears alongside the asset image on NFT platforms." + }, + "background_color": { + "type": "string", + "description": "String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + }, + "media": { + "type": "array", + "items": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + } + } + } + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + }, + "contractMetadata": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "opensea": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + }, + "spamInfo": { + "type": "object", + "description": "Information about whether and why a contract was marked as spam.", + "properties": { + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "acquiredAt": { + "type": "object", + "description": "Only present if the request specified `orderBy=transferTime`.", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "Block timestamp of the block where the NFT was most recently acquired." + }, + "blockNumber": { + "type": "string", + "description": "Block number of the block where the NFT was most recently acquired." + } + } + } + } + } + }, + "pageKey": { + "type": "string" + }, + "totalCount": { + "type": "integer", + "description": "Integer - Total number of NFTs (distinct `tokenIds`) owned by the given address." + }, + "blockHash": { + "type": "string", + "description": "String - The canonical head block hash of when your request was received i.e. the block corresponding to `latest`" + } + } + }, + "examples": { + "byDefault": { + "summary": "Response (By Default)", + "value": { + "ownedNfts": [ + { + "contract": { + "address": "0x0beed7099af7514ccedf642cfea435731176fb02" + }, + "id": { + "tokenId": "28", + "tokenMetadata": { + "tokenType": "ERC721" + } + }, + "title": "DuskBreaker #28", + "description": "Breakers have the honor of serving humanity through their work on The Dusk. They are part of a select squad of 10,000 recruits who spend their days exploring a mysterious alien spaceship filled with friends, foes, and otherworldly technology.", + "tokenUri": { + "raw": "https://duskbreakers.gg/api/breakers/28", + "gateway": "https://duskbreakers.gg/api/breakers/28" + }, + "media": [ + { + "raw": "https://duskbreakers.gg/breaker_images/28.png", + "gateway": "https://duskbreakers.gg/breaker_images/28.png" + } + ], + "metadata": { + "name": "DuskBreaker #28", + "description": "Breakers have the honor of serving humanity through their work on The Dusk. They are part of a select squad of 10,000 recruits who spend their days exploring a mysterious alien spaceship filled with friends, foes, and otherworldly technology.", + "image": "https://duskbreakers.gg/breaker_images/28.png", + "external_url": "https://duskbreakers.gg", + "attributes": [ + { + "value": "Locust Rider Armor (Red)", + "trait_type": "Clothes" + }, + { + "value": "Big Smile (Purple)", + "trait_type": "Mouth" + }, + { + "value": "Yellow", + "trait_type": "Background" + } + ] + }, + "timeLastUpdated": "2022-02-16T22:52:54.719Z", + "contractMetadata": { + "name": "DuskBreakers", + "symbol": "DUSK", + "totalSupply": "10000", + "tokenType": "ERC721" + } + }, + { + "contract": { + "address": "0x97597002980134bea46250aa0510c9b90d87a587" + }, + "id": { + "tokenId": "5527", + "tokenMetadata": { + "tokenType": "ERC721" + } + }, + "title": "Runner #5527", + "description": "Chain Runners are Mega City renegades 100% generated on chain.", + "tokenUri": { + "raw": "https://api.chainrunners.xyz/tokens/metadata/5527?dna=73247164192459371523281785218958151913554625578441142916970699984935810987041", + "gateway": "https://api.chainrunners.xyz/tokens/metadata/5527?dna=73247164192459371523281785218958151913554625578441142916970699984935810987041" + }, + "media": [ + { + "raw": "https://img.chainrunners.xyz/api/v1/tokens/png/5527", + "gateway": "https://img.chainrunners.xyz/api/v1/tokens/png/5527" + } + ], + "metadata": { + "name": "Runner #5527", + "description": "Chain Runners are Mega City renegades 100% generated on chain.", + "image": "https://img.chainrunners.xyz/api/v1/tokens/png/5527", + "attributes": [ + { + "value": "Purple Green Diag", + "trait_type": "Background" + }, + { + "value": "Human", + "trait_type": "Race" + }, + { + "value": "Cig", + "trait_type": "Mouth Accessory" + } + ] + }, + "timeLastUpdated": "2022-02-18T00:42:04.401Z", + "contractMetadata": { + "name": "Chain Runners", + "symbol": "RUN", + "totalSupply": "10000", + "tokenType": "ERC721" + } + } + ], + "totalCount": 6, + "blockHash": "0xeb2d26af5b6175344a14091777535a2cb21c681665a734a8285f889981987630" + } + }, + "withoutMetadata": { + "summary": "Response (withMetadata = false)", + "value": { + "ownedNfts": [ + { + "contract": { + "address": "0x0beed7099af7514ccedf642cfea435731176fb02" + }, + "id": { + "tokenId": "0x000000000000000000000000000000000000000000000000000000000000001c" + } + }, + { + "contract": { + "address": "0x0beed7099af7514ccedf642cfea435731176fb02" + }, + "id": { + "tokenId": "0x000000000000000000000000000000000000000000000000000000000000001d" + }, + "balance": "1" + }, + { + "contract": { + "address": "0x97597002980134bea46250aa0510c9b90d87a587" + }, + "id": { + "tokenId": "0x0000000000000000000000000000000000000000000000000000000000001597" + }, + "balance": "1" + } + ], + "totalCount": 6, + "blockHash": "0xf9a2a4e15116680e22b160c734529f62d89d54cde0759daf5135672fad0ecebc" + } + }, + "withContractFiltering": { + "summary": "Response (with contract filtering)", + "value": { + "ownedNfts": [ + { + "contract": { + "address": "0x34d77a17038491a2a9eaa6e690b7c7cd39fc8392" + }, + "id": { + "tokenId": "0x0000000000000000000000000000000000000000000000000000000000000277" + } + } + ], + "totalCount": 1, + "blockHash": "0x3d8bca59c08e41f55d46ebbe738327eb12955cf280bd06ef7d40352919c188d8" + } + }, + "withPagination": { + "summary": "Response (with pagination)", + "value": { + "ownedNfts": [ + { + "contract": { + "address": "0x97597002980134bea46250aa0510c9b90d87a587" + }, + "id": { + "tokenId": "0x00000000000000000000000000000000000000000000000000000000000009cb" + } + }, + { + "contract": { + "address": "0x97597002980134bea46250aa0510c9b90d87a587" + }, + "id": { + "tokenId": "0x00000000000000000000000000000000000000000000000000000000000009cc" + } + }, + { + "contract": { + "address": "0x5ab21ec0bfa0b29545230395e3adaca7d552c948" + }, + "id": { + "tokenId": "0x00000000000000000000000000000000000000000000000000000000000006dc" + } + }, + { + "contract": { + "address": "0x3b3ee1931dc30c1957379fac9aba94d1c48a5405" + }, + "id": { + "tokenId": "0x000000000000000000000000000000000000000000000000000000000000001a" + } + }, + { + "contract": { + "address": "0x69c40e500b84660cb2ab09cb9614fa2387f95f64" + }, + "id": { + "tokenId": "0x0000000000000000000000000000000000000000000000000000000000000391" + } + }, + { + "contract": { + "address": "0x97597002980134bea46250aa0510c9b90d87a587" + }, + "id": { + "tokenId": "0x00000000000000000000000000000000000000000000000000000000000008d5" + } + }, + { + "contract": { + "address": "0x97597002980134bea46250aa0510c9b90d87a587" + }, + "id": { + "tokenId": "0x0000000000000000000000000000000000000000000000000000000000000a1d" + } + }, + { + "contract": { + "address": "0x97597002980134bea46250aa0510c9b90d87a587" + }, + "id": { + "tokenId": "0x000000000000000000000000000000000000000000000000000000000000002a" + } + }, + { + "contract": { + "address": "0x97597002980134bea46250aa0510c9b90d87a587" + }, + "id": { + "tokenId": "0x000000000000000000000000000000000000000000000000000000000000038e" + } + }, + { + "contract": { + "address": "0x97597002980134bea46250aa0510c9b90d87a587" + }, + "id": { + "tokenId": "0x000000000000000000000000000000000000000000000000000000000000244b" + } + } + ], + "pageKey": "88434286-7eaa-472d-8739-32a0497c2a18", + "totalCount": 277, + "blockHash": "0x94d5ab52b8a6571733f6b183ef89f31573b82a4e78f8129b0ce90ef0beaf208b" + } + } + } + } + } + } + }, + "operationId": "getNFTs" + } + }, + "/v2/{apiKey}/getNFTMetadata": { + "get": { + "summary": "getNFTMetadata", + "description": "Gets the metadata associated with a given NFT.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + }, + { + "name": "tokenId", + "description": "String - The ID of the token. Can be in hex or decimal format.", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": true + }, + { + "name": "tokenType", + "description": "String - 'ERC721' or 'ERC1155'; specifies type of token to query for. API requests will perform faster if this is specified.", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "tokenUriTimeoutInMs", + "description": "No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0.", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "refreshCache", + "description": "Defaults to false for faster response times. If true will refresh metadata for given token. If false will check the cache and use it or refresh if cache doesn't exist.", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "The object that represents an NFT and has all data corresponding to that NFT", + "properties": { + "contract": { + "description": "Object - Contract for returned NFT", + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "String - Address of NFT contract." + } + } + }, + "id": { + "type": "object", + "properties": { + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenMetadata": { + "type": "object", + "properties": { + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + } + } + } + } + }, + "balance": { + "type": "string", + "description": "String - Token balance" + }, + "title": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Brief human-readable description" + }, + "tokenUri": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + } + } + }, + "media": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "external_url": { + "type": "string", + "description": "String - The image URL that appears alongside the asset image on NFT platforms." + }, + "background_color": { + "type": "string", + "description": "String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + }, + "media": { + "type": "array", + "items": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + } + } + } + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + }, + "contractMetadata": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "opensea": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + }, + "spamInfo": { + "type": "object", + "description": "Information about whether and why a contract was marked as spam.", + "properties": { + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "acquiredAt": { + "type": "object", + "description": "Only present if the request specified `orderBy=transferTime`.", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "Block timestamp of the block where the NFT was most recently acquired." + }, + "blockNumber": { + "type": "string", + "description": "Block number of the block where the NFT was most recently acquired." + } + } + } + } + } + } + } + } + }, + "operationId": "getNFTMetadata" + } + }, + "/v2/{apiKey}/getNFTMetadataBatch": { + "post": { + "summary": "getNFTMetadataBatch", + "description": "Gets the metadata associated with up to 100 given NFT contracts.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "array", + "description": "List of token objects to batch request NFT metadata for. Maximum 100.", + "items": { + "type": "object", + "required": [ + "contractAddress", + "tokenId" + ], + "properties": { + "contractAddress": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenType": { + "type": "string" + } + } + } + }, + "tokenUriTimeoutInMs": { + "type": "integer" + }, + "refreshCache": { + "type": "boolean", + "default": false + } + } + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "description": "The object that represents an NFT and has all data corresponding to that NFT", + "properties": { + "contract": { + "description": "Object - Contract for returned NFT", + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "String - Address of NFT contract." + } + } + }, + "id": { + "type": "object", + "properties": { + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenMetadata": { + "type": "object", + "properties": { + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + } + } + } + } + }, + "balance": { + "type": "string", + "description": "String - Token balance" + }, + "title": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Brief human-readable description" + }, + "tokenUri": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + } + } + }, + "media": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "external_url": { + "type": "string", + "description": "String - The image URL that appears alongside the asset image on NFT platforms." + }, + "background_color": { + "type": "string", + "description": "String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + }, + "media": { + "type": "array", + "items": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + } + } + } + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + }, + "contractMetadata": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "opensea": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + }, + "spamInfo": { + "type": "object", + "description": "Information about whether and why a contract was marked as spam.", + "properties": { + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "acquiredAt": { + "type": "object", + "description": "Only present if the request specified `orderBy=transferTime`.", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "Block timestamp of the block where the NFT was most recently acquired." + }, + "blockNumber": { + "type": "string", + "description": "Block number of the block where the NFT was most recently acquired." + } + } + } + } + } + } + } + } + } + }, + "operationId": "getNFTMetadataBatch" + } + }, + "/v2/{apiKey}/getContractMetadata": { + "get": { + "summary": "getContractMetadata", + "description": "Queries NFT high-level collection/contract level information.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "String - Contract address for the queried NFT collection" + }, + "contractMetadata": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "opensea": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + } + } + } + } + } + } + }, + "operationId": "getContractMetadata" + } + }, + "/v2/{apiKey}/getContractMetadataBatch": { + "post": { + "summary": "getContractMetadataBatch", + "description": "Gets the metadata associated with the given list of contract addresses", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "contractAddresses": { + "type": "array", + "description": "list of contract addresses to batch metadata requests for", + "default": [ + "0xe785E82358879F061BC3dcAC6f0444462D4b5330", + "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d" + ], + "items": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "contractMetadata": { + "type": "object", + "description": "The object that represents a smart contract and has all data corresponding to that contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "totalBalance": { + "type": "number", + "description": "Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens." + }, + "numDistinctTokensOwned": { + "type": "number", + "description": "Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens." + }, + "isSpam": { + "type": "boolean" + }, + "tokenId": { + "description": "One of the tokens from this contract held by the owner.", + "type": "string" + }, + "name": { + "description": "The name of the contract, i.e. \"Bored Ape Yacht Club\".", + "type": "string" + }, + "title": { + "description": "The title of the token held by the owner i.e. \"Something #22\".", + "type": "string" + }, + "symbol": { + "description": "The symbol of the contract, i.e. BAYC.", + "type": "string" + }, + "tokenType": { + "description": "The NFT standard used by the contract, i.e. ERC721 or ERC1155.", + "type": "string" + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "media": { + "type": "array", + "items": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + } + }, + "opensea": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + } + } + } + } + } + } + } + }, + "operationId": "getContractMetadataBatch" + } + }, + "/v2/{apiKey}/getNFTsForCollection": { + "get": { + "summary": "getNFTsForCollection", + "description": "Gets all NFTs for a given NFT contract.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string" + }, + "required": false + }, + { + "name": "collectionSlug", + "description": "String - OpenSea slug for the NFT collection.", + "in": "query", + "schema": { + "type": "string", + "default": "boredapeyachtclub" + }, + "required": false + }, + { + "name": "withMetadata", + "description": "Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "schema": { + "type": "boolean", + "default": true + }, + "in": "query" + }, + { + "name": "startToken", + "description": "String - A tokenID offset used for pagination. Can be a hex string, or a decimal. Users can specify the offset themselves to start from a custom offset, or to fetch multiple token ranges in parallel.", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "description": "Integer - Sets the total number of NFTs returned in the response. Defaults to 100.", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "tokenUriTimeoutInMs", + "description": "No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0.", + "in": "query", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nfts": { + "description": "List of objects that represent NFTs stored under the queried contract address or collection slug.", + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "object", + "properties": { + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenMetadata": { + "type": "object", + "properties": { + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + } + } + } + } + }, + "tokenUri": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + } + } + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "external_url": { + "type": "string", + "description": "String - The image URL that appears alongside the asset image on NFT platforms." + }, + "background_color": { + "type": "string", + "description": "String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + }, + "media": { + "type": "array", + "items": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + } + } + } + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "contractMetadata": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "opensea": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + } + } + } + }, + "nextToken": { + "type": "string", + "description": "String - An offset used for pagination. Can be passed back as the \"startToken\" of a subsequent request to get the next page of results. Absent if there are no more results." + } + } + } + } + } + } + }, + "operationId": "getNFTsForCollection" + } + }, + "/v2/{apiKey}/getOwnersForToken": { + "get": { + "summary": "getOwnersForToken", + "description": "Get the owner(s) for a token.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the token to get the owner for.", + "in": "query", + "schema": { + "type": "string", + "default": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + "required": true + }, + { + "name": "tokenId", + "description": "String - The ID of the token. Can be in hex or decimal format.", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": true + }, + { + "name": "pageKey", + "description": "String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results.", + "schema": { + "type": "string" + }, + "in": "query" + }, + { + "name": "pageSize", + "description": "Number of owners to be returned per page.", + "schema": { + "type": "integer" + }, + "in": "query" + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "owners": { + "type": "array", + "description": "List of all addresses that own the given NFT.", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "operationId": "getOwnersForToken" + } + }, + "/v2/{apiKey}/getOwnersForCollection": { + "get": { + "summary": "getOwnersForCollection", + "description": "Gets all owners for a given NFT contract.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0x1f02bf9dde7c79137a08b2dd4fc964bfd2499734" + }, + "required": true + }, + { + "name": "withTokenBalances", + "description": "Boolean - If set to `true` the query will include the token balances per token id for each owner. `false` by default.", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "description": "String - used for collections with >50,000 owners. `pageKey` field can be passed back as request parameter to get the next page of results.", + "name": "pageKey", + "schema": { + "type": "string" + }, + "in": "query" + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "List of all addresses that own the given NFT.", + "items": { + "type": "string" + } + } + } + } + } + }, + "operationId": "getOwnersForCollection" + } + }, + "/v2/{apiKey}/getSpamContracts": { + "get": { + "summary": "getSpamContracts", + "description": "Returns a list of all spam contracts marked by Alchemy.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet", + "polygon-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Object that has list of contract addresses", + "properties": { + "contractAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of contract addresses earmarked as spam by Alchemy." + } + } + } + } + } + } + }, + "operationId": "getSpamContracts" + } + }, + "/v2/{apiKey}/isSpamContract": { + "get": { + "summary": "isSpamContract", + "description": "Returns whether a contract is marked as spam or not by Alchemy.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet", + "polygon-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0x495f947276749ce646f68ac8c248420045cb7b5e" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "boolean", + "description": "True - if the queried contract is marked as spam.\nFalse - if the queried contract is considered valid or if the contract hasn't been evaluated by us yet.\n" + } + } + } + } + }, + "operationId": "isSpamContract" + } + }, + "/v2/{apiKey}/isAirdrop": { + "get": { + "summary": "isAirdrop", + "description": "Returns whether a token is marked as an airdrop or not. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + }, + { + "name": "tokenId", + "description": "String - The ID of the token. Can be in hex or decimal format.", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "boolean", + "description": "True - if the queried token is marked as an airdrop.\nFalse - if the queried token is not marked as an airdrop.\n" + } + } + } + } + }, + "operationId": "isAirdrop" + } + }, + "/v2/{apiKey}/invalidateContract": { + "get": { + "summary": "invalidateContract", + "description": "Marks all cached tokens for the particular contract as stale. So the next time the endpoint is queried it fetches live data instead of fetching from cache.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "True - if the queried contract is marked as spam.\nFalse - if the queried contract is considered valid.\n", + "properties": { + "success": { + "type": "string", + "description": "True if the contract was invalidated.\nFalse - if it wasn't.\n" + }, + "numTokensInvalidated": { + "type": "number", + "description": "The number of tokens that were invalidated as a result of running this query." + } + } + } + } + } + } + }, + "operationId": "invalidateContract" + } + }, + "/v2/{apiKey}/getFloorPrice": { + "get": { + "summary": "getFloorPrice", + "description": "Returns the floor prices of a NFT collection by marketplace.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nftMarketplace": { + "type": "object", + "description": "Name of the NFT marketplace where the collection is listed. Current marketplaces supported - OpenSea, LooksRare", + "properties": { + "floorPrice": { + "type": "number", + "description": "Number - The floor price of the collection on the given marketplace." + }, + "priceCurrency": { + "type": "string", + "description": "String - The currency in which the floor price is denominated. Typically, denominated in ETH", + "enum": [ + "ETH" + ] + }, + "collectionUrl": { + "type": "string", + "description": "String - Link to the collection on the given marketplace." + }, + "retrievedAt": { + "type": "string", + "description": "String - UTC timestamp of when the floor price was retrieved from the marketplace." + }, + "error": { + "type": "string", + "description": "String - Returns an error if there was an error fetching floor prices from the given marketplace." + } + } + } + } + } + } + } + } + }, + "operationId": "getFloorPrice" + } + }, + "/v2/{apiKey}/computeRarity": { + "get": { + "summary": "computeRarity", + "description": "Computes the rarity of each attribute of an NFT.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet", + "polygon-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + }, + { + "name": "tokenId", + "description": "String - The ID of the token. Can be in hex or decimal format.", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Object containing the rarity info of the collection", + "properties": { + "rarities": { + "type": "array", + "description": "NFT attributes and their associated prevalence.", + "items": { + "type": "object", + "properties": { + "trait_type": { + "type": "string", + "description": "Name of the trait category, i.e. Hat, Color, Face, etc." + }, + "value": { + "type": "string", + "description": "Value for the trait, i.e. White Cap, Blue, Angry, etc." + }, + "prevalence": { + "type": "number", + "description": "Floating point value from 0 to 1 representing the prevalence of this value for this trait type." + } + } + } + } + } + } + } + } + } + }, + "operationId": "computeRarity" + } + }, + "/v2/{apiKey}/searchContractMetadata": { + "get": { + "summary": "searchContractMetadata", + "description": "Search for a keyword across metadata of all ERC-721 and ERC-1155 smart contracts", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "query", + "description": "String - The search string that you want to search for in contract metadata", + "in": "query", + "schema": { + "type": "string", + "default": "bored" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns the list of NFT contracts where the metadata has one or more keywords from the search string.", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "List of contracts where the metadata contains one or more keywords from the search string.", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "contractMetadata": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "opensea": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + } + } + } + } + } + } + } + }, + "operationId": "searchContractMetadata" + } + }, + "/v2/{apiKey}/summarizeNFTAttributes": { + "get": { + "summary": "summarizeNFTAttributes", + "description": "Generate a summary of attribute prevalence for an NFT collection.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet", + "polygon-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Prevalence counts for each attribute within a collection.", + "properties": { + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "summary": { + "type": "object", + "description": "Object mapping trait types to the prevalence of each trait within that type." + }, + "contractAddress": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + } + } + } + } + } + } + }, + "operationId": "summarizeNFTAttributes" + } + }, + "/v2/{apiKey}/isHolderOfCollection": { + "get": { + "summary": "isHolderOfCollection", + "description": "Checks whether a wallet holds a NFT in a given collection", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "wallet", + "description": "String - Address for NFT owner (can be in ENS format for Eth Mainnet).", + "schema": { + "type": "string", + "default": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + }, + "in": "query", + "required": true + }, + { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Data related to a wallet's ownership of any token in an NFT collection.", + "properties": { + "isHolderOfCollection": { + "type": "boolean", + "description": "Whether the given wallet owns any token in the given NFT collection." + } + } + } + } + } + } + }, + "operationId": "isHolderOfCollection" + } + }, + "/v2/{apiKey}/getNFTSales": { + "get": { + "summary": "getNFTSales", + "description": "Gets NFT sales that have happened through on-chain marketplaces", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "servers": [ + { + "url": "https://{network}.g.alchemy.com/nft", + "variables": { + "network": { + "enum": [ + "eth-mainnet", + "polygon-mainnet", + "opt-mainnet" + ], + "default": "eth-mainnet" + } + } + } + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "fromBlock", + "description": "String - The block number to start fetching NFT sales data from. Allowed values are decimal and hex integers, and \"latest\". Defaults to \"0\".", + "in": "query", + "schema": { + "type": "string", + "default": "0" + } + }, + { + "name": "toBlock", + "description": "String - The block number to start fetching NFT sales data from. Allowed values are decimal and hex integers, and \"latest\". Defaults to \"latest\".", + "in": "query", + "schema": { + "type": "string", + "default": "latest" + } + }, + { + "name": "order", + "description": "Enum - Whether to return the results ascending from startBlock or descending from startBlock. Defaults to descending (false).", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + }, + { + "name": "marketplace", + "description": "Enum - The name of the NFT marketplace to filter sales by. The endpoint currently supports \"seaport\", \"wyvern\", \"looksrare\", \"x2y2\", \"blur\", and \"cryptopunks\". Defaults to returning sales from all supported marketplaces.", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "seaport", + "looksrare", + "x2y2", + "wyvern", + "blur", + "cryptopunks" + ] + }, + "required": false + }, + { + "description": "String - The contract address of a NFT collection to filter sales by. Defaults to returning all NFT contracts.", + "name": "contractAddress", + "in": "query", + "schema": { + "type": "string" + }, + "required": false + }, + { + "description": "String - The token ID of an NFT within the collection specified by contractAddress to filter sales by. Defaults to returning all token IDs.", + "name": "tokenId", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": false + }, + { + "name": "buyerAddress", + "description": "String - The address of the NFT buyer to filter sales by. Defaults to returning sales involving any buyer.", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "sellerAddress", + "description": "String - The address of the NFT seller to filter sales by. Defaults to returning sales involving any seller.", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "taker", + "description": "Enum - Filter by whether the buyer or seller was the taker in the NFT trade. Allowed filter values are \"BUYER\" and \"SELLER\". Defaults to returning both buyer and seller taker trades.", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "BUYER", + "SELLER" + ] + }, + "required": false + }, + { + "description": "Integer - The maximum number of NFT sales to return. Maximum and default values are 1000.", + "name": "limit", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "pageKey", + "description": "String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results.", + "schema": { + "type": "string" + }, + "in": "query" + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nftSales": { + "description": "List of NFT sales that match the query", + "type": "array", + "items": { + "type": "object", + "properties": { + "marketplace": { + "type": "string", + "description": "String - The marketplace the sale took place on." + }, + "contractAddress": { + "type": "string", + "description": "String - The contract address of the collection the NFT belongs to." + }, + "tokenId": { + "type": "string", + "description": "String - The decimal token ID of the NFT being sold." + }, + "quantity": { + "type": "string", + "description": "Integer - The number of tokens sold in the sale as a decimal integer string." + }, + "buyerAddress": { + "type": "string", + "description": "String - The address of the buyer in the NFT sale." + }, + "sellerAddress": { + "type": "string", + "description": "String - The address of the seller in the NFT sale." + }, + "taker": { + "type": "string", + "description": "String - Whether the price taker in the trade was the buyer or the seller.", + "enum": [ + "BUYER", + "SELLER" + ] + }, + "sellerFee": { + "type": "object", + "description": "The payment from buyer to the seller", + "properties": { + "amount": { + "type": "string", + "description": "String - The amount of the payment from the buyer to seller as a decimal integer string." + }, + "symbol": { + "type": "string", + "description": "String - The symbol of the token used for the payment." + }, + "decimals": { + "type": "integer", + "description": "Integer - The number of decimals of the token used for the payment." + } + } + }, + "protocolFee": { + "type": "object", + "description": "The payment from buyer to the NFT marketplace protocol", + "properties": { + "amount": { + "type": "string", + "description": "String - The amount of the payment to the marketplace as a decimal integer string." + }, + "symbol": { + "type": "string", + "description": "String - The symbol of the token used for the payment." + }, + "decimals": { + "type": "integer", + "description": "Integer - The number of decimals of the token used for the payment." + } + } + }, + "royaltyFee": { + "type": "object", + "description": "The payment from buyer to the royalty address of the NFT collection", + "properties": { + "amount": { + "type": "string", + "description": "String - The amount of the payment to the royalty collector as a decimal integer string." + }, + "symbol": { + "type": "string", + "description": "String - The symbol of the token used for the payment." + }, + "decimals": { + "type": "integer", + "description": "Integer - The number of decimals of the token used for the payment." + } + } + }, + "blockNumber": { + "type": "integer", + "description": "Integer - The block number the NFT sale took place in." + }, + "logIndex": { + "type": "integer", + "description": "Integer - The log number of the sale event emitted within the block." + }, + "bundleIndex": { + "type": "integer", + "description": "Integer - The index of the token within the bundle of NFTs sold in the sale." + }, + "transactionHash": { + "type": "string", + "description": "String - The transaction hash of the transaction containing the sale." + } + } + } + }, + "pageKey": { + "type": "string", + "description": "String - The page key to use to fetch the next page of results. Returns null if there are no more results." + } + } + }, + "examples": { + "nftSales_response": { + "summary": "Response (with pagination)", + "value": { + "nftSales": [ + { + "marketplace": "seaport", + "contractAddress": "0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b", + "tokenId": "13749", + "quantity": "1", + "buyerAddress": "0x78f6c2458b53d0735208992c693bb2b2dafebb52", + "sellerAddress": "0x558a18f94cabdea4e47c5965384f457d8e870419", + "taker": "BUYER", + "sellerFee": { + "amount": "11100000000000000000", + "symbol": "ETH", + "decimals": 18 + }, + "protocolFee": { + "amount": "300000000000000000", + "symbol": "ETH", + "decimals": 18 + }, + "royaltyFee": { + "amount": "600000000000000000", + "symbol": "ETH", + "decimals": 18 + }, + "blockNumber": 15000002, + "logIndex": 130, + "bundleIndex": 0, + "transactionHash": "0xecfa1b29c9016bd2556fde637c6b48484eeb14f273af54c49317e3856ab7cb16" + }, + { + "marketplace": "looksrare", + "contractAddress": "0x34d85c9cdeb23fa97cb08333b511ac86e1c4e258", + "tokenId": "75417", + "quantity": "1", + "buyerAddress": "0xb3aa9923489bc2bfec323bf05346acd4afbc92a0", + "sellerAddress": "0x206ccba024c236dced07c35b4e9eb0bade7ef166", + "taker": "BUYER", + "sellerFee": { + "amount": "2222700000000000000", + "symbol": "WETH", + "decimals": 18 + }, + "protocolFee": { + "amount": "47800000000000000", + "symbol": "WETH", + "decimals": 18 + }, + "royaltyFee": { + "amount": "119500000000000000", + "symbol": "WETH", + "decimals": 18 + }, + "blockNumber": 15000002, + "logIndex": 197, + "bundleIndex": 0, + "transactionHash": "0x4c23163e4f855e143e573776bc6129bee370dff6ce760e71553fc93201b292e2" + } + ], + "pageKey": "MTUwMDAwNzgsODcsMA", + "validAt": { + "blockNumber": 17091500, + "blockHash": "0x2a34a65c4e0cd7fdf187d6a497214ad2bee255d2d3501868a6b8c09b4d1261bd", + "blockTimestamp": "2023-04-21T01:25:59Z" + } + } + } + } + } + } + } + }, + "operationId": "getNFTSales" + } + }, + "/v2/{apiKey}/getContractsForOwner": { + "get": { + "summary": "getContractsForOwner", + "description": "Gets all NFT contracts held by an owner address.", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "owner", + "description": "String - Address for NFT owner (can be in ENS format for Eth Mainnet).", + "schema": { + "type": "string", + "default": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + }, + "in": "query", + "required": true + }, + { + "name": "pageKey", + "description": "String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results.", + "schema": { + "type": "string" + }, + "in": "query" + }, + { + "name": "pageSize", + "description": "Number of NFTs to be returned per page. Defaults to 100. Max is 100.", + "schema": { + "type": "integer", + "default": 100 + }, + "in": "query" + }, + { + "name": "withMetadata", + "description": "Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "schema": { + "type": "boolean", + "default": true + }, + "in": "query" + }, + { + "name": "includeFilters[]", + "description": "Array of filters (as ENUMS) that will be applied to the query. Only NFTs that match one or more of these filters will be included in the response. May not be used in conjunction with excludeFilters[]. Filter Options:\n - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**.\n - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only.\n - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts)", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "in": "query" + }, + { + "name": "excludeFilters[]", + "description": "Array of filters (as ENUMS) that will be applied to the query. NFTs that match one or more of these filters will be excluded from the response. May not be used in conjunction with includeFilters[]. Filter Options:\n - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**.\n - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only.\n - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts)", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "in": "query" + }, + { + "name": "orderBy", + "description": "Enum - ordering scheme to use for ordering NFTs in the response. If unspecified, NFTs will be ordered by contract address and token ID.\n - transferTime: NFTs will be ordered by the time they were transferred into the wallet, with newest NFTs first. Note: This ordering is supported on Ethereum Mainnet, Optimism Mainnet, Polygon Mainnet, Base Mainnet, Arbitrum One, Polygon Amoy, Base Sepolia, Arbitrum Sepolia, Ethereum Sepolia, and Optimism Sepolia.", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "transferTime" + ] + }, + "required": false + }, + { + "name": "spamConfidenceLevel", + "description": "Enum - the confidence level at which to filter spam at.\n\nConfidence Levels:\n - VERY_HIGH\n - HIGH\n - MEDIUM\n - LOW\n\nThe confidence level set means that any spam that is at that confidence level or higher will be filtered out. For example, if the confidence level is HIGH, contracts that we have HIGH or VERY_HIGH confidence in being spam will be filtered out from the response. \nDefaults to VERY_HIGH for Ethereum Mainnet and MEDIUM for Matic Mainnet.\n\n**Please note that this filter is only available on paid tiers. Upgrade your account [here](https://dashboard.alchemy.com/settings/billing/).**", + "schema": { + "type": "string", + "enum": [ + "VERY_HIGH", + "HIGH", + "MEDIUM", + "LOW" + ] + }, + "in": "query", + "required": false + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "contracts": { + "type": "array", + "items": { + "type": "object", + "description": "The object that represents a smart contract and has all data corresponding to that contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "totalBalance": { + "type": "number", + "description": "Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens." + }, + "numDistinctTokensOwned": { + "type": "number", + "description": "Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens." + }, + "isSpam": { + "type": "boolean" + }, + "tokenId": { + "description": "One of the tokens from this contract held by the owner.", + "type": "string" + }, + "name": { + "description": "The name of the contract, i.e. \"Bored Ape Yacht Club\".", + "type": "string" + }, + "title": { + "description": "The title of the token held by the owner i.e. \"Something #22\".", + "type": "string" + }, + "symbol": { + "description": "The symbol of the contract, i.e. BAYC.", + "type": "string" + }, + "tokenType": { + "description": "The NFT standard used by the contract, i.e. ERC721 or ERC1155.", + "type": "string" + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "media": { + "type": "array", + "items": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + } + }, + "opensea": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + } + }, + "pageKey": { + "type": "string" + }, + "totalCount": { + "type": "string", + "description": "String - Total number of NFT contracts held by the given address returned in this page." + } + } + }, + "examples": { + "withoutMetadata": { + "summary": "Response (withMetadata = false)", + "value": { + "contracts": [ + { + "address": "0x000386e3f7559d9b6a2f5c46b4ad1a9587d59dc3", + "totalBalance": 912, + "numDistinctTokensOwned": 80, + "isSpam": true, + "tokenId": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "address": "0x0015f391949f25c3211063104ad4afc99210f85c", + "totalBalance": 17, + "numDistinctTokensOwned": 6, + "isSpam": true, + "tokenId": "0x0000000000000000000000000000000000000000000000000000000000000002" + }, + { + "address": "0x005b92d71a934dbe48e985b6469881cf4b0308fc", + "totalBalance": 1, + "numDistinctTokensOwned": 1, + "isSpam": true, + "tokenId": "0x0000000000000000000000000000000000000000000000000000000000000003" + } + ], + "totalCount": 2120, + "pageKey": "20ef9df5-0d81-42e5-b741-140f595a407b" + } + }, + "withMetadata": { + "summary": "Response (withMetadata = true)", + "value": { + "contracts": [ + { + "address": "0x1C310c2fbB0D9755A6b918F990bC8D3504f2c684", + "name": "The Wonderful Husl Founder Cards", + "symbol": "The Wonderful Husl Founder Cards", + "totalSupply": null, + "tokenType": "ERC1155", + "contractDeployer": "0x0bdD0AEC835F92a465290cdd57b27FBd00376F53", + "deployedBlockNumber": 15664554, + "openSeaMetadata": { + "floorPrice": null, + "collectionName": "The Wonderful Husl Founder Cards", + "safelistRequestStatus": "not_requested", + "imageUrl": "https://i.seadn.io/gcs/files/754e38769c80c9d6188444dddb10ec80.png?w=500&auto=format", + "description": "[Husl](https://www.huslnft.xyz) is building the bridge between business and NFTs. Husl Founders are the driven, the passionate and the focused members of the community ready to change their future. Owning a Founders Card gets you exclusive perks, early access to business management, and discounts on managed services for your business as NFT. [Learn More](https://www.huslnft.xyz)", + "externalUrl": "https://www.huslnft.xyz", + "twitterUsername": null, + "discordUrl": null, + "lastIngestedAt": "2023-03-20T01:36:19.000Z" + }, + "totalBalance": "1", + "numDistinctTokensOwned": "1", + "isSpam": true, + "displayNft": { + "tokenId": "233", + "name": null + }, + "image": { + "cachedUrl": "https://nft-cdn.alchemy.com/eth-mainnet/d08d0d0fac8edf36ea09eae34b332814", + "thumbnailUrl": "https://res.cloudinary.com/alchemyapi/image/upload/thumbnailv2/eth-mainnet/d08d0d0fac8edf36ea09eae34b332814", + "pngUrl": "https://res.cloudinary.com/alchemyapi/image/upload/convert-png/eth-mainnet/d08d0d0fac8edf36ea09eae34b332814", + "contentType": "video/mp4", + "size": 36190302, + "originalUrl": "https://ipfs.io/ipfs/QmX2mM8r33W7KUBQSWXFAKNC2t654EXmWiX9vkrfrEaEnS" + } + } + ], + "totalCount": 2120, + "pageKey": "03949322-9b2c-4fdd-aab6-1369e29fa5b2" + } + } + } + } + } + } + }, + "operationId": "getContractsForOwner" + } + }, + "/v2/{apiKey}/reportSpam": { + "get": { + "summary": "reportSpam", + "description": "Report a particular address to our APIs if you think it is spam", + "tags": [ + "NFT API V2 Methods (Older Version)" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + { + "name": "address", + "description": "String - The address to check for spam status.", + "in": "query", + "schema": { + "type": "string", + "default": "0x495f947276749ce646f68ac8c248420045cb7b5e" + }, + "required": true + }, + { + "name": "isSpam", + "description": "Boolean - Whether the address is spam.", + "in": "query", + "schema": { + "type": "boolean", + "default": true + }, + "required": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "string", + "description": "String - \"Address was successfully reported as spam\" if calling the API was successful. " + } + } + } + } + }, + "operationId": "reportSpam" + } + } + }, + "components": { + "parameters": { + "apiKey": { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + }, + "owner": { + "name": "owner", + "description": "String - Address for NFT owner (can be in ENS format for Eth Mainnet).", + "schema": { + "type": "string", + "default": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + }, + "in": "query", + "required": true + }, + "wallet": { + "name": "wallet", + "description": "String - Wallet address to check for contract ownership.", + "schema": { + "type": "string", + "default": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + }, + "in": "query", + "required": true + }, + "pageKey": { + "name": "pageKey", + "description": "String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results.", + "schema": { + "type": "string" + }, + "in": "query" + }, + "pageSize": { + "name": "pageSize", + "description": "Number of NFTs to be returned per page. Defaults to 100. Max is 100.", + "schema": { + "type": "integer", + "default": 100 + }, + "in": "query" + }, + "getOwnersForTokenPageSize": { + "name": "pageSize", + "description": "Number of owners to be returned per page.", + "schema": { + "type": "integer" + }, + "in": "query" + }, + "contractAddresses": { + "name": "contractAddresses[]", + "description": "Array of contract addresses to filter the responses with. Max limit 45 contracts.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query" + }, + "withMetadata": { + "name": "withMetadata", + "description": "Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "schema": { + "type": "boolean", + "default": true + }, + "in": "query" + }, + "excludeFilters": { + "name": "excludeFilters[]", + "description": "Array of filters (as ENUMS) that will be applied to the query. NFTs that match one or more of these filters will be excluded from the response. May not be used in conjunction with includeFilters[]. Filter Options:\n - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**.\n - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only.\n - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts)", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "in": "query" + }, + "address": { + "name": "address", + "description": "String - any valid blockchain address for NFT collections, contracts, mints, etc.", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": false + }, + "addressRequired": { + "name": "address", + "description": "String - any valid blockchain address for NFT collections, contracts, mints, etc.", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + }, + "includeFilters": { + "name": "includeFilters[]", + "description": "Array of filters (as ENUMS) that will be applied to the query. Only NFTs that match one or more of these filters will be included in the response. May not be used in conjunction with excludeFilters[]. Filter Options:\n - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**.\n - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only.\n - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts)", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "in": "query" + }, + "spamConfidenceLevel": { + "name": "spamConfidenceLevel", + "description": "Enum - the confidence level at which to filter spam at.\n\nConfidence Levels:\n - VERY_HIGH\n - HIGH\n - MEDIUM\n - LOW\n\nThe confidence level set means that any spam that is at that confidence level or higher will be filtered out. For example, if the confidence level is HIGH, contracts that we have HIGH or VERY_HIGH confidence in being spam will be filtered out from the response. \nDefaults to VERY_HIGH for Ethereum Mainnet and MEDIUM for Matic Mainnet.\n\n**Please note that this filter is only available on paid tiers. Upgrade your account [here](https://dashboard.alchemy.com/settings/billing/).**", + "schema": { + "type": "string", + "enum": [ + "VERY_HIGH", + "HIGH", + "MEDIUM", + "LOW" + ] + }, + "in": "query", + "required": false + }, + "collectionSlug": { + "name": "collectionSlug", + "description": "String - OpenSea slug for the NFT collection.", + "in": "query", + "schema": { + "type": "string", + "default": "boredapeyachtclub" + }, + "required": false + }, + "collectionSlugRequired": { + "name": "collectionSlug", + "description": "String - OpenSea slug for the NFT collection.", + "in": "query", + "schema": { + "type": "string", + "default": "boredapeyachtclub" + }, + "required": true + }, + "contractAddressRequired": { + "name": "contractAddress", + "description": "String - Contract address for the NFT contract (ERC721 and ERC1155 supported).", + "in": "query", + "schema": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "required": true + }, + "tokenId": { + "name": "tokenId", + "description": "String - The ID of the token. Can be in hex or decimal format.", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": true + }, + "tokenIdV3": { + "name": "tokenId", + "description": "String - The ID of the token in decimal format.", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": true + }, + "tokenIdForNFTSales": { + "name": "tokenId", + "description": "String - The ID of the token. Can be in hex or decimal format.", + "in": "query", + "schema": { + "type": "string", + "default": "44" + }, + "required": false + }, + "tokenType": { + "name": "tokenType", + "description": "String - 'ERC721' or 'ERC1155'; specifies type of token to query for. API requests will perform faster if this is specified.", + "in": "query", + "schema": { + "type": "string" + } + }, + "startToken": { + "name": "startToken", + "description": "String - A tokenID offset used for pagination. Can be a hex string, or a decimal. Users can specify the offset themselves to start from a custom offset, or to fetch multiple token ranges in parallel.", + "in": "query", + "schema": { + "type": "string" + } + }, + "limit": { + "name": "limit", + "description": "Integer - Sets the total number of NFTs returned in the response. Defaults to 100.", + "in": "query", + "schema": { + "type": "integer" + } + }, + "tokenUriTimeoutInMs": { + "name": "tokenUriTimeoutInMs", + "description": "No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0.", + "in": "query", + "schema": { + "type": "integer" + } + }, + "withTokenBalances": { + "name": "withTokenBalances", + "description": "Boolean - If set to `true` the query will include the token balances per token id for each owner. `false` by default.", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + }, + "refreshCache": { + "name": "refreshCache", + "description": "Defaults to false for faster response times. If true will refresh metadata for given token. If false will check the cache and use it or refresh if cache doesn't exist.", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + }, + "block": { + "name": "block", + "description": "String - The point in time or block number (in hex or decimal) to fetch collection ownership information for.", + "in": "query", + "schema": { + "type": "string" + } + }, + "fromBlock": { + "name": "fromBlock", + "description": "String - The block number to start fetching NFT sales data from. Allowed values are decimal and hex integers, and \"latest\". Defaults to \"0\".", + "in": "query", + "schema": { + "type": "string", + "default": "0" + } + }, + "order": { + "name": "order", + "description": "Enum - Whether to return the results ascending from startBlock or descending from startBlock. Defaults to descending (false).", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + } + }, + "orderBy": { + "name": "orderBy", + "description": "Enum - ordering scheme to use for ordering NFTs in the response. If unspecified, NFTs will be ordered by contract address and token ID.\n - transferTime: NFTs will be ordered by the time they were transferred into the wallet, with newest NFTs first. Note: This ordering is supported on Ethereum Mainnet, Optimism Mainnet, Polygon Mainnet, Base Mainnet, Arbitrum One, Polygon Amoy, Base Sepolia, Arbitrum Sepolia, Ethereum Sepolia, and Optimism Sepolia.", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "transferTime" + ] + }, + "required": false + }, + "marketplace": { + "name": "marketplace", + "description": "Enum - The name of the NFT marketplace to filter sales by. The endpoint currently supports \"seaport\", \"wyvern\", \"looksrare\", \"x2y2\", \"blur\", and \"cryptopunks\". Defaults to returning sales from all supported marketplaces.", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "seaport", + "looksrare", + "x2y2", + "wyvern", + "blur", + "cryptopunks" + ] + }, + "required": false + }, + "buyerAddress": { + "name": "buyerAddress", + "description": "String - The address of the NFT buyer to filter sales by. Defaults to returning sales involving any buyer.", + "in": "query", + "schema": { + "type": "string" + } + }, + "sellerAddress": { + "name": "sellerAddress", + "description": "String - The address of the NFT seller to filter sales by. Defaults to returning sales involving any seller.", + "in": "query", + "schema": { + "type": "string" + } + }, + "taker": { + "name": "taker", + "description": "Enum - Filter by whether the buyer or seller was the taker in the NFT trade. Allowed filter values are \"BUYER\" and \"SELLER\". Defaults to returning both buyer and seller taker trades.", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "BUYER", + "SELLER" + ] + }, + "required": false + }, + "query": { + "name": "query", + "description": "String - The search string that you want to search for in contract metadata", + "in": "query", + "schema": { + "type": "string", + "default": "bored" + }, + "required": true + } + }, + "schemas": { + "rawv3": { + "type": "object", + "description": "Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract", + "properties": { + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + } + } + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + } + } + }, + "id": { + "type": "object", + "properties": { + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenMetadata": { + "type": "object", + "properties": { + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + } + } + } + } + }, + "idV3": { + "type": "object", + "properties": { + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenMetadata": { + "type": "object", + "properties": { + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + } + } + } + } + }, + "tokenUri": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + } + } + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "external_url": { + "type": "string", + "description": "String - The image URL that appears alongside the asset image on NFT platforms." + }, + "background_color": { + "type": "string", + "description": "String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + }, + "media": { + "type": "array", + "items": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + } + } + } + }, + "ownedContract": { + "type": "object", + "description": "The object that represents a smart contract and has all data corresponding to that contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "totalBalance": { + "type": "number", + "description": "Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens." + }, + "numDistinctTokensOwned": { + "type": "number", + "description": "Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens." + }, + "isSpam": { + "type": "boolean" + }, + "tokenId": { + "description": "One of the tokens from this contract held by the owner.", + "type": "string" + }, + "name": { + "description": "The name of the contract, i.e. \"Bored Ape Yacht Club\".", + "type": "string" + }, + "title": { + "description": "The title of the token held by the owner i.e. \"Something #22\".", + "type": "string" + }, + "symbol": { + "description": "The symbol of the contract, i.e. BAYC.", + "type": "string" + }, + "tokenType": { + "description": "The NFT standard used by the contract, i.e. ERC721 or ERC1155.", + "type": "string" + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "media": { + "type": "array", + "items": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + } + }, + "opensea": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + }, + "ownedContractv3": { + "type": "object", + "description": "The object that represents a smart contract and has all data corresponding to that contract", + "properties": { + "address": { + "type": "string", + "default": "0xe785E82358879F061BC3dcAC6f0444462D4b5330" + }, + "name": { + "description": "The name of the contract, i.e. \"Bored Ape Yacht Club\".", + "type": "string" + }, + "symbol": { + "description": "The symbol of the contract, i.e. BAYC.", + "type": "string" + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string" + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "totalBalance": { + "type": "number", + "description": "Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens." + }, + "numDistinctTokensOwned": { + "type": "number", + "description": "Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens." + }, + "isSpam": { + "type": "boolean", + "description": "`True` if the contract is detected as spam contract. `False` if it is not spam or has not been evaluated by our system yet" + }, + "displayNft": { + "type": "object", + "description": "Details of the display NFT for this contract. This NFT and its image can be used to represent the contract when displaying info about it.", + "properties": { + "tokenId": { + "description": "One of the tokens from this contract held by the owner.", + "type": "string" + }, + "name": { + "description": "The title of the token held by the owner i.e. \"Something #22\".", + "type": "string" + } + } + }, + "image": { + "type": "object", + "description": "Details of the image corresponding to this contract", + "properties": { + "cachedUrl": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "thumbnailUrl": { + "type": "string", + "description": "The Url that has the thumbnail version of the NFT" + }, + "pngUrl": { + "type": "string", + "description": "The Url that has the NFT image in png" + }, + "contentType": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "size": { + "type": "integer", + "description": "The size of the media asset in bytes." + }, + "originalUrl": { + "type": "string", + "description": "The original Url of the image coming straight from the smart contract" + } + } + } + } + }, + "ownedCollectionv3": { + "type": "object", + "description": "Metadata for an NFT collection held by an owner address. Includes general metadata about the collection, as well as information specific to the owner such as the total balance and the token ID of a random NFT for display purposes.", + "properties": { + "name": { + "description": "The name of the collection, i.e. \"Bored Ape Yacht Club\".", + "type": "string" + }, + "slug": { + "description": "The human-readable string used to identify the collection on OpenSea.", + "type": "string" + }, + "floorPrice": { + "type": "object", + "description": "Floor price data for the collection", + "properties": { + "marketplace": { + "description": "The marketplace the floor price is on", + "type": "string" + }, + "floorPrice": { + "description": "Floor price of the collection on the marketplace", + "type": "number" + }, + "priceCurrency": { + "description": "The currency of the floor price", + "type": "string" + } + } + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "contract": { + "type": "object", + "description": "Contract-level data for a collection, such as contract type, name, and symbol.", + "properties": { + "address": { + "description": "Address of the contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + } + } + }, + "totalBalance": { + "type": "number", + "description": "Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens." + }, + "numDistinctTokensOwned": { + "type": "number", + "description": "Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens." + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "displayNft": { + "type": "object", + "description": "Details of the display NFT for this contract. This NFT and its image can be used to represent the contract when displaying info about it.", + "properties": { + "tokenId": { + "description": "One of the tokens from this contract held by the owner.", + "type": "string" + }, + "name": { + "description": "The title of the token held by the owner i.e. \"Something #22\".", + "type": "string" + } + } + }, + "image": { + "type": "object", + "description": "Details of the image corresponding to this contract", + "properties": { + "cachedUrl": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "thumbnailUrl": { + "type": "string", + "description": "The Url that has the thumbnail version of the NFT" + }, + "pngUrl": { + "type": "string", + "description": "The Url that has the NFT image in png" + }, + "contentType": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "size": { + "type": "integer", + "description": "The size of the media asset in bytes." + }, + "originalUrl": { + "type": "string", + "description": "The original Url of the image coming straight from the smart contract" + } + } + } + } + }, + "ownedCollectionContract": { + "type": "object", + "description": "Contract-level data for a collection, such as contract type, name, and symbol.", + "properties": { + "address": { + "description": "Address of the contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + } + } + }, + "media": { + "type": "array", + "items": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + } + }, + "ownedNFT": { + "type": "object", + "description": "The object that represents an NFT and has all data corresponding to that NFT", + "properties": { + "contract": { + "description": "Object - Contract for returned NFT", + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "String - Address of NFT contract." + } + } + }, + "id": { + "type": "object", + "properties": { + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenMetadata": { + "type": "object", + "properties": { + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + } + } + } + } + }, + "balance": { + "type": "string", + "description": "String - Token balance" + }, + "title": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Brief human-readable description" + }, + "tokenUri": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + } + } + }, + "media": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "external_url": { + "type": "string", + "description": "String - The image URL that appears alongside the asset image on NFT platforms." + }, + "background_color": { + "type": "string", + "description": "String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + }, + "media": { + "type": "array", + "items": { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "gateway": { + "type": "string", + "description": "String - Public gateway uri for the raw uri above." + }, + "thumbnail": { + "type": "string", + "description": "URL for a resized thumbnail of the NFT media asset." + }, + "format": { + "type": "string", + "description": "The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets." + }, + "bytes": { + "type": "integer", + "description": "The size of the media asset in bytes." + } + } + } + } + } + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + }, + "contractMetadata": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "opensea": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + }, + "spamInfo": { + "type": "object", + "description": "Information about whether and why a contract was marked as spam.", + "properties": { + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "acquiredAt": { + "type": "object", + "description": "Only present if the request specified `orderBy=transferTime`.", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "Block timestamp of the block where the NFT was most recently acquired." + }, + "blockNumber": { + "type": "string", + "description": "Block number of the block where the NFT was most recently acquired." + } + } + } + } + }, + "ownedNFTv3": { + "type": "object", + "description": "The object that represents an NFT and has all data corresponding to that NFT", + "properties": { + "contract": { + "type": "object", + "description": "The contract object that has details of a contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenType": { + "type": "string" + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Brief human-readable description" + }, + "image": { + "type": "object", + "description": "Details of the image corresponding to this contract", + "properties": { + "cachedUrl": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "thumbnailUrl": { + "type": "string", + "description": "The Url that has the thumbnail version of the NFT" + }, + "pngUrl": { + "type": "string", + "description": "The Url that has the NFT image in png" + }, + "contentType": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "size": { + "type": "integer", + "description": "The size of the media asset in bytes." + }, + "originalUrl": { + "type": "string", + "description": "The original Url of the image coming straight from the smart contract" + } + } + }, + "raw": { + "type": "object", + "description": "Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract", + "properties": { + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + } + } + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + } + } + }, + "collection": { + "type": "object", + "description": "The collection object that has details of a collection", + "properties": { + "name": { + "type": "string", + "description": "String - Collection name" + }, + "slug": { + "type": "string", + "description": "String - OpenSea collection slug" + }, + "externalUrl": { + "type": "string", + "description": "String - URL for the external site of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "String - Banner image URL for the collection" + } + } + }, + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "acquiredAt": { + "type": "object", + "description": "Only present if the request specified `orderBy=transferTime`.", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "Block timestamp of the block where the NFT was most recently acquired." + }, + "blockNumber": { + "type": "string", + "description": "Block number of the block where the NFT was most recently acquired." + } + } + }, + "animation": { + "type": "object", + "properties": { + "cachedUrl": { + "type": "string" + }, + "contentType": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "orginalUrl": { + "type": "string" + } + } + }, + "mint": { + "type": "object", + "properties": { + "mintAddress": { + "type": "string", + "description": "Address that minted the NFT" + }, + "blockNumber": { + "type": "integer", + "description": "Block number when the NFT was minted" + }, + "timestamp": { + "type": "string", + "description": "Timestamp when the NFT was minted" + }, + "transactionHash": { + "type": "string", + "description": "Transaction hash of the mint transaction" + } + } + }, + "owners": { + "type": "array", + "description": "List of all addresses that own the given NFT.", + "items": { + "type": "string" + } + } + } + }, + "contractMetadata": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "opensea": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + }, + "contractMetadatav3": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "String - Contract address for the queried NFT collection" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + } + } + }, + "collectionMetadatav3": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "String - Name of the queried NFT Collection" + }, + "slug": { + "description": "The human-readable string used to identify the collection on OpenSea.", + "type": "string" + }, + "floorPrice": { + "type": "object", + "description": "Floor price data for the collection", + "properties": { + "marketplace": { + "description": "The marketplace the floor price is on", + "type": "string" + }, + "floorPrice": { + "description": "Floor price of the collection on the marketplace", + "type": "number" + }, + "priceCurrency": { + "description": "The currency of the floor price", + "type": "string" + } + } + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + } + } + }, + "contractv3": { + "type": "object", + "description": "The contract object that has details of a contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } +} diff --git a/packages/api-codegen/specs/portfolio.json b/packages/api-codegen/specs/portfolio.json new file mode 100644 index 0000000000..8a9d094209 --- /dev/null +++ b/packages/api-codegen/specs/portfolio.json @@ -0,0 +1,3210 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "🧠 Data", + "version": "1.0" + }, + "servers": [ + { + "url": "https://api.g.alchemy.com/data/v1" + } + ], + "paths": { + "/{apiKey}/assets/tokens/by-address": { + "post": { + "summary": "Tokens By Wallet", + "description": "Fetches fungible tokens (native, ERC-20 and SPL) for multiple wallet addresses and networks. Returns a list of tokens with balances, prices, and metadata for each wallet/network combination. This endpoint is supported on Ethereum, Solana, and 30+ EVM chains. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).\n", + "tags": [ + "Portfolio API Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "description": "Array of wallet addresses and the networks to query them on. Maximum 2 addresses and maximum 5 networks per address.\n", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address.", + "example": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "default": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152" + }, + "networks": { + "type": "array", + "default": [ + "eth-mainnet", + "base-mainnet", + "matic-mainnet" + ], + "items": { + "type": "string", + "default": "eth-mainnet" + }, + "description": "Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + } + }, + "required": [ + "address", + "networks" + ] + } + }, + "withMetadata": { + "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "type": "boolean", + "default": true + }, + "withPrices": { + "description": "Boolean - if set to `true`, returns token prices. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "type": "boolean", + "default": true + }, + "includeNativeTokens": { + "type": "boolean", + "description": "Whether to include each chain's native token in the response (e.g. ETH on Ethereum). The native token will have a null contract address.", + "example": true, + "default": true + }, + "includeErc20Tokens": { + "description": "Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "type": "boolean", + "default": true + } + }, + "required": [ + "addresses" + ] + }, + "example": { + "addresses": [ + { + "address": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "networks": [ + "eth-mainnet", + "base-mainnet", + "matic-mainnet" + ] + } + ], + "withMetadata": true, + "withPrices": true, + "includeNativeTokens": true, + "includeErc20Tokens": false + } + } + } + }, + "responses": { + "200": { + "description": "Successful response!", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "description": "List of tokens by address, with prices and metadata.", + "properties": { + "tokens": { + "type": "array", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address." + }, + "network": { + "type": "string", + "description": "Network identifier." + }, + "tokenAddress": { + "type": "string", + "description": "Token address." + }, + "tokenBalance": { + "type": "string", + "description": "Balance of that particular token." + }, + "tokenMetadata": { + "type": "object", + "properties": { + "decimals": { + "type": "integer", + "description": "Number of decimals the token uses" + }, + "logo": { + "type": "string", + "description": "URL of the token's logo image" + }, + "name": { + "type": "string", + "description": "Token's name" + }, + "symbol": { + "type": "string", + "description": "Token's symbol" + } + } + }, + "tokenPrices": { + "type": "array", + "description": "List of price information.", + "items": { + "type": "object", + "properties": { + "currency": { + "type": "string", + "example": "usd" + }, + "value": { + "type": "string", + "example": "4608.2208671202" + }, + "lastUpdatedAt": { + "type": "string", + "format": "date-time", + "example": "2025-08-26T20:17:27Z" + } + }, + "required": [ + "currency", + "value", + "lastUpdatedAt" + ] + } + }, + "error": { + "type": [ + "string", + "null" + ], + "description": "Error message if applicable." + } + }, + "required": [ + "network", + "address", + "tokenAddress", + "tokenBalance" + ] + } + }, + "pageKey": { + "type": "string" + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "description": "Bad Request: Invalid input (e.g., malformed JSON).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + }, + "429": { + "description": "Too Many Requests: Rate limit exceeded.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "get-tokens-by-address" + } + }, + "/{apiKey}/assets/tokens/balances/by-address": { + "post": { + "summary": "Token Balances By Wallet", + "description": "Fetches fungible tokens (native, ERC-20, and SPL) for multiple wallet addresses and networks. Returns a list of tokens with balances for each wallet/network combination. This endpoint is supported on Ethereum, Solana, and 30+ EVM chains. See the full list of supported networks [here](https://dashboard.alchemy.com/chains)\n", + "tags": [ + "Portfolio API Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "description": "Array of address and networks pairs (limit 3 pairs, max 20 networks). Networks should match network enums.\n", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address.", + "example": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "default": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152" + }, + "networks": { + "type": "array", + "default": [ + "eth-mainnet", + "base-mainnet", + "matic-mainnet" + ], + "items": { + "type": "string", + "default": "eth-mainnet" + }, + "description": "Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + } + }, + "required": [ + "address", + "networks" + ] + } + }, + "includeNativeTokens": { + "type": "boolean", + "description": "Whether to include each chain's native token in the response (e.g. ETH on Ethereum). The native token will have a null contract address.", + "example": true, + "default": true + }, + "includeErc20Tokens": { + "description": "Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "type": "boolean", + "default": true + } + }, + "required": [ + "addresses" + ] + }, + "example": { + "addresses": [ + { + "address": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "networks": [ + "eth-mainnet", + "base-mainnet", + "matic-mainnet" + ] + } + ], + "includeNativeTokens": true, + "includeErc20Tokens": false + } + } + } + }, + "responses": { + "200": { + "description": "Successful response!", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "tokens": { + "type": "array", + "description": "List of token balances by address.", + "items": { + "type": "object", + "properties": { + "network": { + "type": "string", + "description": "Network identifier." + }, + "address": { + "type": "string", + "description": "Wallet address." + }, + "tokenAddress": { + "type": "string", + "description": "Token address." + }, + "tokenBalance": { + "type": "string", + "description": "Balance of that particular token." + } + }, + "required": [ + "network", + "address", + "tokenAddress", + "tokenBalance" + ] + } + }, + "pageKey": { + "type": "string" + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "description": "Bad Request: Invalid input (e.g., malformed JSON).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + }, + "429": { + "description": "Too Many Requests: Rate limit exceeded.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "get-token-balances-by-address" + } + }, + "/{apiKey}/assets/nfts/by-address": { + "post": { + "summary": "NFTs By Wallet", + "description": "Fetches NFTs for multiple wallet addresses and networks. Returns a list of NFTs and metadata for each wallet/network combination. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).\n", + "tags": [ + "Portfolio API Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "description": "Array of address and networks pairs (limit 2 pairs, max 15 networks each). Networks should match network enums.\n", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address.", + "example": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "default": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152" + }, + "networks": { + "type": "array", + "default": [ + "eth-mainnet", + "base-mainnet", + "matic-mainnet" + ], + "items": { + "type": "string", + "enum": [ + "eth-mainnet", + "eth-sepolia", + "eth-holesky", + "avax-mainnet", + "avax-fuji", + "zksync-mainnet", + "opt-mainnet", + "polygon-mainnet", + "polygon-amoy", + "arb-mainnet", + "arb-sepolia", + "blast-mainnet", + "blast-sepolia", + "base-mainnet", + "base-sepolia", + "soneium-mainnet", + "soneium-minato", + "scroll-mainnet", + "scroll-sepolia", + "shape-mainnet", + "shape-sepolia", + "lens-mainnet", + "lens-sepolia", + "starknet-mainnet", + "starknet-sepolia", + "rootstock-mainnet", + "rootstock-testnet", + "linea-mainnet", + "linea-sepolia", + "settlus-septestnet", + "abstract-mainnet", + "abstract-testnet", + "apechain-mainnet" + ], + "default": "eth-mainnet" + }, + "description": "Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + }, + "excludeFilters": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "includeFilters": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "spamConfidenceLevel": { + "type": "string", + "enum": [ + "VERY_HIGH", + "HIGH", + "MEDIUM", + "LOW" + ] + } + }, + "required": [ + "address", + "networks" + ] + } + }, + "withMetadata": { + "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "type": "boolean", + "default": true + }, + "pageKey": { + "type": "string" + }, + "pageSize": { + "type": "integer", + "default": 100 + } + }, + "required": [ + "addresses" + ] + }, + { + "type": "object", + "properties": { + "orderBy": { + "type": "string", + "enum": [ + "transferTime" + ], + "description": "Field to order results by" + }, + "sortOrder": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort order for results" + } + } + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response!", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "description": "List of nfts by address with appropriate metadata.", + "properties": { + "ownedNfts": { + "type": "array", + "items": { + "type": "object", + "description": "The object that represents an NFT and has all data corresponding to that NFT", + "properties": { + "network": { + "type": "string", + "description": "Network identifier." + }, + "address": { + "type": "string", + "description": "Wallet address." + }, + "contract": { + "type": "object", + "description": "The contract object that has details of a contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenType": { + "type": "string" + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Brief human-readable description" + }, + "image": { + "type": "object", + "description": "Details of the image corresponding to this contract", + "properties": { + "cachedUrl": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "thumbnailUrl": { + "type": "string", + "description": "The Url that has the thumbnail version of the NFT" + }, + "pngUrl": { + "type": "string", + "description": "The Url that has the NFT image in png" + }, + "contentType": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "size": { + "type": "integer", + "description": "The size of the media asset in bytes." + }, + "originalUrl": { + "type": "string", + "description": "The original Url of the image coming straight from the smart contract" + } + } + }, + "raw": { + "type": "object", + "description": "Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract", + "properties": { + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + } + } + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + } + } + }, + "collection": { + "type": "object", + "description": "The collection object that has details of a collection", + "properties": { + "name": { + "type": "string", + "description": "String - Collection name" + }, + "slug": { + "type": "string", + "description": "String - OpenSea collection slug" + }, + "externalUrl": { + "type": "string", + "description": "String - URL for the external site of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "String - Banner image URL for the collection" + } + } + }, + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "acquiredAt": { + "type": "object", + "description": "Only present if the request specified `orderBy=transferTime`.", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "Block timestamp of the block where the NFT was most recently acquired." + }, + "blockNumber": { + "type": "string", + "description": "Block number of the block where the NFT was most recently acquired." + } + } + } + } + } + }, + "totalCount": { + "type": "integer", + "description": "Integer - Total number of NFTs (distinct `tokenIds`) owned by the given address." + }, + "pageKey": { + "type": "string" + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "description": "Bad Request: Invalid input (e.g., malformed JSON).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + }, + "429": { + "description": "Too Many Requests: Rate limit exceeded.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "get-nfts-by-address" + } + }, + "/{apiKey}/assets/nfts/contracts/by-address": { + "post": { + "summary": "NFT Collections By Wallet", + "description": "Fetches NFT collections (contracts) for multiple wallet addresses and networks. Returns a list of collections and metadata for each wallet/network combination. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains).\n", + "tags": [ + "Portfolio API Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "description": "Array of address and networks pairs (limit 2 pairs, max 15 networks each). Networks should match network enums.\n", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address.", + "example": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "default": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152" + }, + "networks": { + "type": "array", + "default": [ + "eth-mainnet", + "base-mainnet", + "matic-mainnet" + ], + "items": { + "type": "string", + "enum": [ + "eth-mainnet", + "eth-sepolia", + "eth-holesky", + "avax-mainnet", + "avax-fuji", + "zksync-mainnet", + "opt-mainnet", + "polygon-mainnet", + "polygon-amoy", + "arb-mainnet", + "arb-sepolia", + "blast-mainnet", + "blast-sepolia", + "base-mainnet", + "base-sepolia", + "soneium-mainnet", + "soneium-minato", + "scroll-mainnet", + "scroll-sepolia", + "shape-mainnet", + "shape-sepolia", + "lens-mainnet", + "lens-sepolia", + "starknet-mainnet", + "starknet-sepolia", + "rootstock-mainnet", + "rootstock-testnet", + "linea-mainnet", + "linea-sepolia", + "settlus-septestnet", + "abstract-mainnet", + "abstract-testnet", + "apechain-mainnet" + ], + "default": "eth-mainnet" + }, + "description": "Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + }, + "excludeFilters": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "includeFilters": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "spamConfidenceLevel": { + "type": "string", + "enum": [ + "VERY_HIGH", + "HIGH", + "MEDIUM", + "LOW" + ] + } + }, + "required": [ + "address", + "networks" + ] + } + }, + "withMetadata": { + "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "type": "boolean", + "default": true + }, + "pageKey": { + "type": "string" + }, + "pageSize": { + "type": "integer", + "default": 100 + } + }, + "required": [ + "addresses" + ] + }, + { + "type": "object", + "properties": { + "orderBy": { + "type": "string", + "enum": [ + "transferTime" + ], + "description": "Field to order results by" + }, + "sortOrder": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "Sort order for results" + } + } + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response!", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "description": "List of nft collections.", + "properties": { + "contracts": { + "type": "array", + "items": { + "type": "object", + "description": "The object that represents an NFT collection", + "properties": { + "network": { + "type": "string", + "description": "Network identifier." + }, + "address": { + "type": "string", + "description": "Wallet address." + }, + "contract": { + "type": "object", + "description": "The contract object that has details of a contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "totalCount": { + "type": "integer", + "description": "Integer - Total number of NFTs (distinct `tokenIds`) owned by the given address." + }, + "pageKey": { + "type": "string" + } + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "description": "Bad Request: Invalid input (e.g., malformed JSON).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + }, + "429": { + "description": "Too Many Requests: Rate limit exceeded.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "get-nft-contracts-by-address" + } + } + }, + "components": { + "schemas": { + "ByAddressRequest": { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "description": "Array of wallet addresses and the networks to query them on. Maximum 2 addresses and maximum 5 networks per address.\n", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address.", + "example": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "default": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152" + }, + "networks": { + "type": "array", + "default": [ + "eth-mainnet", + "base-mainnet", + "matic-mainnet" + ], + "items": { + "type": "string", + "default": "eth-mainnet" + }, + "description": "Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + } + }, + "required": [ + "address", + "networks" + ] + } + } + }, + "required": [ + "addresses" + ] + }, + "ByAddressRequestWith1AddressAnd2Networks": { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "description": "Array of address and networks pairs (limit 1 pairs, max 2 networks). Networks should match network enums.\n", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address.", + "example": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "default": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152" + }, + "networks": { + "type": "array", + "default": [ + "eth-mainnet", + "base-mainnet" + ], + "items": { + "type": "string", + "default": "eth-mainnet" + }, + "description": "(In BETA and only accepts ETH & BASE mainnets). Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + } + }, + "required": [ + "address", + "networks" + ] + } + }, + "before": { + "type": "string", + "description": "The cursor that points to the previous set of results." + }, + "after": { + "type": "string", + "description": "The cursor that points to the end of the current set of results." + }, + "limit": { + "type": "integer", + "description": "Sets the maximum number of items per page (Max: 50)", + "default": 25 + }, + "pageKey": { + "type": "string" + } + }, + "required": [ + "addresses" + ] + }, + "ByAddressRequestWith3PairsAnd20Networks": { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "description": "Array of address and networks pairs (limit 3 pairs, max 20 networks). Networks should match network enums.\n", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address.", + "example": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "default": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152" + }, + "networks": { + "type": "array", + "default": [ + "eth-mainnet", + "base-mainnet", + "matic-mainnet" + ], + "items": { + "type": "string", + "default": "eth-mainnet" + }, + "description": "Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + } + }, + "required": [ + "address", + "networks" + ] + } + }, + "includeNativeTokens": { + "type": "boolean", + "description": "Whether to include each chain's native token in the response (e.g. ETH on Ethereum). The native token will have a null contract address.", + "example": true, + "default": true + }, + "includeErc20Tokens": { + "description": "Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "type": "boolean", + "default": true + } + }, + "required": [ + "addresses" + ] + }, + "ByAddressRequestWithOptions": { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "description": "Array of wallet addresses and the networks to query them on. Maximum 2 addresses and maximum 5 networks per address.\n", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address.", + "example": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "default": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152" + }, + "networks": { + "type": "array", + "default": [ + "eth-mainnet", + "base-mainnet", + "matic-mainnet" + ], + "items": { + "type": "string", + "default": "eth-mainnet" + }, + "description": "Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + } + }, + "required": [ + "address", + "networks" + ] + } + }, + "withMetadata": { + "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "type": "boolean", + "default": true + }, + "withPrices": { + "description": "Boolean - if set to `true`, returns token prices. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "type": "boolean", + "default": true + }, + "includeNativeTokens": { + "type": "boolean", + "description": "Whether to include each chain's native token in the response (e.g. ETH on Ethereum). The native token will have a null contract address.", + "example": true, + "default": true + }, + "includeErc20Tokens": { + "description": "Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "type": "boolean", + "default": true + } + }, + "required": [ + "addresses" + ] + }, + "ByAddressRequestWithNFTOptionsAndPaging": { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "description": "Array of address and networks pairs (limit 2 pairs, max 15 networks each). Networks should match network enums.\n", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address.", + "example": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "default": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152" + }, + "networks": { + "type": "array", + "default": [ + "eth-mainnet", + "base-mainnet", + "matic-mainnet" + ], + "items": { + "type": "string", + "enum": [ + "eth-mainnet", + "eth-sepolia", + "eth-holesky", + "avax-mainnet", + "avax-fuji", + "zksync-mainnet", + "opt-mainnet", + "polygon-mainnet", + "polygon-amoy", + "arb-mainnet", + "arb-sepolia", + "blast-mainnet", + "blast-sepolia", + "base-mainnet", + "base-sepolia", + "soneium-mainnet", + "soneium-minato", + "scroll-mainnet", + "scroll-sepolia", + "shape-mainnet", + "shape-sepolia", + "lens-mainnet", + "lens-sepolia", + "starknet-mainnet", + "starknet-sepolia", + "rootstock-mainnet", + "rootstock-testnet", + "linea-mainnet", + "linea-sepolia", + "settlus-septestnet", + "abstract-mainnet", + "abstract-testnet", + "apechain-mainnet" + ], + "default": "eth-mainnet" + }, + "description": "Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + }, + "excludeFilters": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "includeFilters": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "spamConfidenceLevel": { + "type": "string", + "enum": [ + "VERY_HIGH", + "HIGH", + "MEDIUM", + "LOW" + ] + } + }, + "required": [ + "address", + "networks" + ] + } + }, + "withMetadata": { + "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "type": "boolean", + "default": true + }, + "pageKey": { + "type": "string" + }, + "pageSize": { + "type": "integer", + "default": 100 + } + }, + "required": [ + "addresses" + ] + }, + "ByAddressRequestWithNFTOptions": { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "description": "Array of wallet addresses and the networks to query them on. Maximum 2 addresses and maximum 5 networks per address.\n", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address.", + "example": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "default": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152" + }, + "networks": { + "type": "array", + "default": [ + "eth-mainnet", + "base-mainnet", + "matic-mainnet" + ], + "items": { + "type": "string", + "default": "eth-mainnet" + }, + "description": "Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + } + }, + "required": [ + "address", + "networks" + ] + } + }, + "withMetadata": { + "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "type": "boolean", + "default": true + } + }, + "required": [ + "addresses" + ] + }, + "AddressItemForNFTOwnership": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address.", + "example": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + "default": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152" + }, + "networks": { + "type": "array", + "default": [ + "eth-mainnet", + "base-mainnet", + "matic-mainnet" + ], + "items": { + "type": "string", + "enum": [ + "eth-mainnet", + "eth-sepolia", + "eth-holesky", + "avax-mainnet", + "avax-fuji", + "zksync-mainnet", + "opt-mainnet", + "polygon-mainnet", + "polygon-amoy", + "arb-mainnet", + "arb-sepolia", + "blast-mainnet", + "blast-sepolia", + "base-mainnet", + "base-sepolia", + "soneium-mainnet", + "soneium-minato", + "scroll-mainnet", + "scroll-sepolia", + "shape-mainnet", + "shape-sepolia", + "lens-mainnet", + "lens-sepolia", + "starknet-mainnet", + "starknet-sepolia", + "rootstock-mainnet", + "rootstock-testnet", + "linea-mainnet", + "linea-sepolia", + "settlus-septestnet", + "abstract-mainnet", + "abstract-testnet", + "apechain-mainnet" + ], + "default": "eth-mainnet" + }, + "description": "Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + }, + "excludeFilters": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "includeFilters": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "SPAM", + "AIRDROPS" + ], + "default": "SPAM" + } + }, + "spamConfidenceLevel": { + "type": "string", + "enum": [ + "VERY_HIGH", + "HIGH", + "MEDIUM", + "LOW" + ] + } + }, + "required": [ + "address", + "networks" + ] + }, + "TokensResponse": { + "type": "object", + "properties": { + "data": { + "type": "object", + "description": "List of tokens by address, with prices and metadata.", + "properties": { + "tokens": { + "type": "array", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Wallet address." + }, + "network": { + "type": "string", + "description": "Network identifier." + }, + "tokenAddress": { + "type": "string", + "description": "Token address." + }, + "tokenBalance": { + "type": "string", + "description": "Balance of that particular token." + }, + "tokenMetadata": { + "type": "object", + "properties": { + "decimals": { + "type": "integer", + "description": "Number of decimals the token uses" + }, + "logo": { + "type": "string", + "description": "URL of the token's logo image" + }, + "name": { + "type": "string", + "description": "Token's name" + }, + "symbol": { + "type": "string", + "description": "Token's symbol" + } + } + }, + "tokenPrices": { + "type": "array", + "description": "List of price information.", + "items": { + "type": "object", + "properties": { + "currency": { + "type": "string", + "example": "usd" + }, + "value": { + "type": "string", + "example": "4608.2208671202" + }, + "lastUpdatedAt": { + "type": "string", + "format": "date-time", + "example": "2025-08-26T20:17:27Z" + } + }, + "required": [ + "currency", + "value", + "lastUpdatedAt" + ] + } + }, + "error": { + "type": [ + "string", + "null" + ], + "description": "Error message if applicable." + } + }, + "required": [ + "network", + "address", + "tokenAddress", + "tokenBalance" + ] + } + }, + "pageKey": { + "type": "string" + } + } + } + }, + "required": [ + "data" + ] + }, + "TokenBalancesResponse": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "tokens": { + "type": "array", + "description": "List of token balances by address.", + "items": { + "type": "object", + "properties": { + "network": { + "type": "string", + "description": "Network identifier." + }, + "address": { + "type": "string", + "description": "Wallet address." + }, + "tokenAddress": { + "type": "string", + "description": "Token address." + }, + "tokenBalance": { + "type": "string", + "description": "Balance of that particular token." + } + }, + "required": [ + "network", + "address", + "tokenAddress", + "tokenBalance" + ] + } + }, + "pageKey": { + "type": "string" + } + } + } + }, + "required": [ + "data" + ] + }, + "NFTByOwnerResponse": { + "type": "object", + "properties": { + "data": { + "type": "object", + "description": "List of nfts by address with appropriate metadata.", + "properties": { + "ownedNfts": { + "type": "array", + "items": { + "type": "object", + "description": "The object that represents an NFT and has all data corresponding to that NFT", + "properties": { + "network": { + "type": "string", + "description": "Network identifier." + }, + "address": { + "type": "string", + "description": "Wallet address." + }, + "contract": { + "type": "object", + "description": "The contract object that has details of a contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenType": { + "type": "string" + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Brief human-readable description" + }, + "image": { + "type": "object", + "description": "Details of the image corresponding to this contract", + "properties": { + "cachedUrl": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "thumbnailUrl": { + "type": "string", + "description": "The Url that has the thumbnail version of the NFT" + }, + "pngUrl": { + "type": "string", + "description": "The Url that has the NFT image in png" + }, + "contentType": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "size": { + "type": "integer", + "description": "The size of the media asset in bytes." + }, + "originalUrl": { + "type": "string", + "description": "The original Url of the image coming straight from the smart contract" + } + } + }, + "raw": { + "type": "object", + "description": "Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract", + "properties": { + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + } + } + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + } + } + }, + "collection": { + "type": "object", + "description": "The collection object that has details of a collection", + "properties": { + "name": { + "type": "string", + "description": "String - Collection name" + }, + "slug": { + "type": "string", + "description": "String - OpenSea collection slug" + }, + "externalUrl": { + "type": "string", + "description": "String - URL for the external site of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "String - Banner image URL for the collection" + } + } + }, + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "acquiredAt": { + "type": "object", + "description": "Only present if the request specified `orderBy=transferTime`.", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "Block timestamp of the block where the NFT was most recently acquired." + }, + "blockNumber": { + "type": "string", + "description": "Block number of the block where the NFT was most recently acquired." + } + } + } + } + } + }, + "totalCount": { + "type": "integer", + "description": "Integer - Total number of NFTs (distinct `tokenIds`) owned by the given address." + }, + "pageKey": { + "type": "string" + } + } + } + }, + "required": [ + "data" + ] + }, + "NFTCollectionsByOwnerResponse": { + "type": "object", + "properties": { + "data": { + "type": "object", + "description": "List of nft collections.", + "properties": { + "contracts": { + "type": "array", + "items": { + "type": "object", + "description": "The object that represents an NFT collection", + "properties": { + "network": { + "type": "string", + "description": "Network identifier." + }, + "address": { + "type": "string", + "description": "Wallet address." + }, + "contract": { + "type": "object", + "description": "The contract object that has details of a contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "totalCount": { + "type": "integer", + "description": "Integer - Total number of NFTs (distinct `tokenIds`) owned by the given address." + }, + "pageKey": { + "type": "string" + } + } + } + }, + "required": [ + "data" + ] + }, + "NFTResponseItem": { + "type": "object", + "description": "The object that represents an NFT and has all data corresponding to that NFT", + "properties": { + "network": { + "type": "string", + "description": "Network identifier." + }, + "address": { + "type": "string", + "description": "Wallet address." + }, + "contract": { + "type": "object", + "description": "The contract object that has details of a contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "tokenId": { + "type": "string", + "default": "44" + }, + "tokenType": { + "type": "string" + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Brief human-readable description" + }, + "image": { + "type": "object", + "description": "Details of the image corresponding to this contract", + "properties": { + "cachedUrl": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "thumbnailUrl": { + "type": "string", + "description": "The Url that has the thumbnail version of the NFT" + }, + "pngUrl": { + "type": "string", + "description": "The Url that has the NFT image in png" + }, + "contentType": { + "type": "string", + "description": "The Url of the image stored in Alchemy cache" + }, + "size": { + "type": "integer", + "description": "The size of the media asset in bytes." + }, + "originalUrl": { + "type": "string", + "description": "The original Url of the image coming straight from the smart contract" + } + } + }, + "raw": { + "type": "object", + "description": "Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract", + "properties": { + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "metadata": { + "type": "object", + "description": "Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually.", + "properties": { + "image": { + "type": "string", + "description": "String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces." + }, + "name": { + "type": "string", + "description": "String - Name of the NFT asset." + }, + "description": { + "type": "string", + "description": "String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms)" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "trait_type": { + "type": "string" + } + } + }, + "description": "Object - Traits/attributes/characteristics for each NFT asset." + } + } + }, + "error": { + "type": "string", + "description": "String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT." + } + } + }, + "collection": { + "type": "object", + "description": "The collection object that has details of a collection", + "properties": { + "name": { + "type": "string", + "description": "String - Collection name" + }, + "slug": { + "type": "string", + "description": "String - OpenSea collection slug" + }, + "externalUrl": { + "type": "string", + "description": "String - URL for the external site of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "String - Banner image URL for the collection" + } + } + }, + "tokenUri": { + "type": "string", + "description": "String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated." + }, + "timeLastUpdated": { + "type": "string", + "description": "String - ISO timestamp of the last cache refresh for the information returned in the metadata field." + }, + "acquiredAt": { + "type": "object", + "description": "Only present if the request specified `orderBy=transferTime`.", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "Block timestamp of the block where the NFT was most recently acquired." + }, + "blockNumber": { + "type": "string", + "description": "Block number of the block where the NFT was most recently acquired." + } + } + } + } + }, + "NFTCollectionResponseItem": { + "type": "object", + "description": "The object that represents an NFT collection", + "properties": { + "network": { + "type": "string", + "description": "Network identifier." + }, + "address": { + "type": "string", + "description": "Wallet address." + }, + "contract": { + "type": "object", + "description": "The contract object that has details of a contract", + "properties": { + "address": { + "description": "Address of the held contract", + "type": "string" + }, + "name": { + "type": "string", + "description": "String - NFT contract name." + }, + "symbol": { + "type": "string", + "description": "String - NFT contract symbol abbreviation." + }, + "totalSupply": { + "type": "string", + "description": "String - Total number of NFTs in a given NFT collection." + }, + "tokenType": { + "type": "string", + "enum": [ + "ERC721", + "ERC1155", + "NO_SUPPORTED_NFT_STANDARD", + "NOT_A_CONTRACT" + ], + "description": "String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address." + }, + "contractDeployer": { + "type": "string", + "description": "String - Address that deployed the smart contract" + }, + "deployedBlockNumber": { + "type": "number", + "description": "Number - The Block Number when the deployment transaction is successfully mined" + }, + "openseaMetadata": { + "type": "object", + "description": "Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks.", + "properties": { + "floorPrice": { + "type": "number", + "description": "NFT floor price" + }, + "collectionName": { + "type": "string", + "description": "OpenSea collection name" + }, + "safelistRequestStatus": { + "type": "string", + "description": "Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model" + }, + "imageUrl": { + "type": "string", + "description": "OpenSea CDN image URL" + }, + "description": { + "type": "string", + "description": "OpenSea collection description. Note: this value is truncated to 255 characters." + }, + "externalUrl": { + "type": "string", + "description": "Collection homepage" + }, + "twitterUsername": { + "type": "string", + "description": "The twitter username of the collection" + }, + "discordUrl": { + "type": "string", + "description": "The discord URL of the collection" + }, + "bannerImageUrl": { + "type": "string", + "description": "The banner image URL of the collection" + }, + "lastIngestedAt": { + "type": "string", + "description": "The timestamp when the collection was last ingested by us" + } + } + }, + "isSpam": { + "type": "string", + "description": "\"true\" if contract is spam, else \"false\". **Only available on paid tiers.**" + }, + "spamClassifications": { + "description": "List of reasons why a contract was classified as spam. **Only available on paid tiers.**", + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "TransactionHistoryResponse": { + "type": "object", + "properties": { + "before": { + "type": "string", + "description": "The cursor that points to the previous set of results." + }, + "after": { + "type": "string", + "description": "The cursor that points to the end of the current set of results." + }, + "totalCount": { + "type": "integer", + "description": "Total count of the response items." + }, + "transactions": { + "type": "array", + "description": "List of transactions by address.", + "items": { + "type": "object", + "properties": { + "network": { + "type": "string", + "description": "Network associated with an individual transaction" + }, + "hash": { + "type": "string", + "description": "Transaction hash" + }, + "timeStamp": { + "type": "string", + "description": "(ISO 8601) Timestamp of transaction mining / confirmation" + }, + "blockNumber": { + "type": "integer", + "description": "Block number of transaction mining / confirmation" + }, + "blockHash": { + "type": "string", + "description": "Block hash of transaction mining / confirmation" + }, + "nonce": { + "type": "integer", + "description": "Transaction nonce" + }, + "transactionIndex": { + "type": "integer", + "description": "Position of transaction within a block" + }, + "fromAddress": { + "type": "string", + "description": "From address of transaction (hex string)." + }, + "toAddress": { + "type": "string", + "description": "To address of transaction (hex string). null if contract creation." + }, + "contractAddress": { + "type": "string", + "description": "20 Bytes - The contract address created, if the transaction was a contract creation, otherwise null" + }, + "value": { + "type": "string", + "description": "(uint8) Value of native token value moved within the external transaction" + }, + "cumulativeGasUsed": { + "type": "string", + "description": "The total amount of gas used when this transaction was executed in the block." + }, + "effectiveGasPrice": { + "type": "string", + "description": "Gas price parameter" + }, + "gasUsed": { + "type": "string", + "description": "The amount of gas used by this specific transaction alone" + }, + "logs": { + "type": "array", + "description": "Array of log objects, which this transaction generated", + "items": { + "type": "object", + "properties": { + "contractAddress": { + "type": "string", + "description": "20 Bytes - contract address from which this log originated." + }, + "logIndex": { + "type": "string", + "description": "Integer of the log index position in the block. null when its pending log." + }, + "data": { + "type": "string", + "description": "Contains one or more 32 Bytes non-indexed arguments of the log." + }, + "removed": { + "type": "boolean", + "description": "true when the log was removed, due to a chain reorganization. false if its a valid log." + }, + "topics": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of zero to four 32 Bytes DATA of indexed log arguments" + } + } + } + }, + "internalTxns": { + "type": "array", + "description": "Array of internal transaction objects, which this transaction generated", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "CALL or CREATE" + }, + "fromAddress": { + "type": "string", + "description": "20 Bytes - address of the sender" + }, + "toAddress": { + "type": "string", + "description": "20 Bytes - address of the receiver. null when its a contract creation transaction" + }, + "value": { + "type": "string", + "description": "amount of value for transfer (in hex)" + }, + "gas": { + "type": "string", + "description": "amount of gas provided for the call (in hex)" + }, + "gasUsed": { + "type": "string", + "description": "amount of gas used during the call (in hex)" + }, + "input": { + "type": "string", + "description": "call data" + }, + "output": { + "type": "string", + "description": "return data" + }, + "error": { + "type": "string", + "description": "error, if any" + }, + "revertReason": { + "type": "string", + "description": "solidity revert reason, if any" + } + } + } + } + } + } + } + }, + "required": [ + "transactions" + ] + }, + "TransactionHistoryResponseItem": { + "type": "object", + "properties": { + "network": { + "type": "string", + "description": "Network associated with an individual transaction" + }, + "hash": { + "type": "string", + "description": "Transaction hash" + }, + "timeStamp": { + "type": "string", + "description": "(ISO 8601) Timestamp of transaction mining / confirmation" + }, + "blockNumber": { + "type": "integer", + "description": "Block number of transaction mining / confirmation" + }, + "blockHash": { + "type": "string", + "description": "Block hash of transaction mining / confirmation" + }, + "nonce": { + "type": "integer", + "description": "Transaction nonce" + }, + "transactionIndex": { + "type": "integer", + "description": "Position of transaction within a block" + }, + "fromAddress": { + "type": "string", + "description": "From address of transaction (hex string)." + }, + "toAddress": { + "type": "string", + "description": "To address of transaction (hex string). null if contract creation." + }, + "contractAddress": { + "type": "string", + "description": "20 Bytes - The contract address created, if the transaction was a contract creation, otherwise null" + }, + "value": { + "type": "string", + "description": "(uint8) Value of native token value moved within the external transaction" + }, + "cumulativeGasUsed": { + "type": "string", + "description": "The total amount of gas used when this transaction was executed in the block." + }, + "effectiveGasPrice": { + "type": "string", + "description": "Gas price parameter" + }, + "gasUsed": { + "type": "string", + "description": "The amount of gas used by this specific transaction alone" + }, + "logs": { + "type": "array", + "description": "Array of log objects, which this transaction generated", + "items": { + "type": "object", + "properties": { + "contractAddress": { + "type": "string", + "description": "20 Bytes - contract address from which this log originated." + }, + "logIndex": { + "type": "string", + "description": "Integer of the log index position in the block. null when its pending log." + }, + "data": { + "type": "string", + "description": "Contains one or more 32 Bytes non-indexed arguments of the log." + }, + "removed": { + "type": "boolean", + "description": "true when the log was removed, due to a chain reorganization. false if its a valid log." + }, + "topics": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of zero to four 32 Bytes DATA of indexed log arguments" + } + } + } + }, + "internalTxns": { + "type": "array", + "description": "Array of internal transaction objects, which this transaction generated", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "CALL or CREATE" + }, + "fromAddress": { + "type": "string", + "description": "20 Bytes - address of the sender" + }, + "toAddress": { + "type": "string", + "description": "20 Bytes - address of the receiver. null when its a contract creation transaction" + }, + "value": { + "type": "string", + "description": "amount of value for transfer (in hex)" + }, + "gas": { + "type": "string", + "description": "amount of gas provided for the call (in hex)" + }, + "gasUsed": { + "type": "string", + "description": "amount of gas used during the call (in hex)" + }, + "input": { + "type": "string", + "description": "call data" + }, + "output": { + "type": "string", + "description": "return data" + }, + "error": { + "type": "string", + "description": "error, if any" + }, + "revertReason": { + "type": "string", + "description": "solidity revert reason, if any" + } + } + } + } + } + }, + "TokenPricesResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "description": "List of token price data.", + "items": { + "type": "object", + "properties": { + "symbol": { + "type": "string", + "description": "Token symbol." + }, + "prices": { + "type": "array", + "description": "List of price information.", + "items": { + "type": "object", + "properties": { + "currency": { + "type": "string", + "description": "Currency code (e.g., USD)." + }, + "value": { + "type": "string", + "description": "Price value as a string." + }, + "lastUpdatedAt": { + "type": "string", + "format": "date-time", + "description": "Time when the price was last updated." + } + }, + "required": [ + "currency", + "value", + "lastUpdatedAt" + ] + } + }, + "error": { + "type": [ + "string", + "null" + ], + "description": "Error message if applicable." + } + }, + "required": [ + "symbol", + "prices", + "error" + ] + } + } + }, + "required": [ + "data" + ] + }, + "TokenPriceResponseItem": { + "type": "object", + "properties": { + "symbol": { + "type": "string", + "description": "Token symbol." + }, + "prices": { + "type": "array", + "description": "List of price information.", + "items": { + "type": "object", + "properties": { + "currency": { + "type": "string", + "description": "Currency code (e.g., USD)." + }, + "value": { + "type": "string", + "description": "Price value as a string." + }, + "lastUpdatedAt": { + "type": "string", + "format": "date-time", + "description": "Time when the price was last updated." + } + }, + "required": [ + "currency", + "value", + "lastUpdatedAt" + ] + } + }, + "error": { + "type": [ + "string", + "null" + ], + "description": "Error message if applicable." + } + }, + "required": [ + "symbol", + "prices", + "error" + ] + }, + "BlockTimestampResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "description": "List of blocks", + "items": { + "type": "object", + "properties": { + "network": { + "type": "string", + "description": "Network identifier" + }, + "block": { + "type": "object", + "properties": { + "number": { + "type": "integer", + "description": "Block number" + }, + "timestamp": { + "type": "string", + "description": "ISO timestamp of the block" + } + }, + "required": [ + "number", + "timestamp" + ] + } + }, + "required": [ + "network", + "block" + ] + } + } + }, + "required": [ + "data" + ] + } + } + } +} diff --git a/packages/api-codegen/specs/specs.lock.json b/packages/api-codegen/specs/specs.lock.json new file mode 100644 index 0000000000..c6953038ec --- /dev/null +++ b/packages/api-codegen/specs/specs.lock.json @@ -0,0 +1,12 @@ +{ + "docs": { + "repository": "alchemyplatform/docs", + "sha": "374030e02046972b4158612036af3aed9130b675", + "branch": "codex/improve-agent-wallet-docs" + }, + "specs": { + "nft.json": "c90282cfc4959f48534aaccceccdd31202ca1410888d7d0daafdd20e0f8c4885", + "portfolio.json": "16bad2001183fada4147f445640555e22a0c58ad44844eefdf0a06e3f794774c", + "transfers.json": "17626a215d7453b41a868e434d5d86a9a1505d8e648ca91c7df1b44c97af9cf5" + } +} diff --git a/packages/api-codegen/specs/transfers.json b/packages/api-codegen/specs/transfers.json new file mode 100644 index 0000000000..b4b6ef8339 --- /dev/null +++ b/packages/api-codegen/specs/transfers.json @@ -0,0 +1,566 @@ +{ + "$schema": "https://meta.open-rpc.org/", + "openrpc": "1.2.4", + "info": { + "title": "Alchemy Transfers API JSON-RPC Specification", + "description": "A specification of the standard JSON-RPC methods for Transfers API.", + "version": "0.0.0" + }, + "servers": [ + { + "url": "https://eth-mainnet.g.alchemy.com/v2", + "name": "Ethereum Mainnet" + }, + { + "url": "https://eth-mainnetbeacon.g.alchemy.com/v2", + "name": "Ethereum Mainnet Beacon" + }, + { + "url": "https://eth-hoodi.g.alchemy.com/v2", + "name": "Ethereum Hoodi" + }, + { + "url": "https://eth-hoodibeacon.g.alchemy.com/v2", + "name": "Ethereum Hoodi Beacon" + }, + { + "url": "https://eth-sepolia.g.alchemy.com/v2", + "name": "Ethereum Sepolia" + }, + { + "url": "https://eth-sepoliabeacon.g.alchemy.com/v2", + "name": "Ethereum Sepolia Beacon" + }, + { + "url": "https://abstract-mainnet.g.alchemy.com/v2", + "name": "Abstract Mainnet" + }, + { + "url": "https://abstract-testnet.g.alchemy.com/v2", + "name": "Abstract Testnet" + }, + { + "url": "https://anime-mainnet.g.alchemy.com/v2", + "name": "Anime Mainnet" + }, + { + "url": "https://anime-sepolia.g.alchemy.com/v2", + "name": "Anime Sepolia" + }, + { + "url": "https://apechain-mainnet.g.alchemy.com/v2", + "name": "ApeChain Mainnet" + }, + { + "url": "https://apechain-curtis.g.alchemy.com/v2", + "name": "ApeChain Curtis" + }, + { + "url": "https://arb-mainnet.g.alchemy.com/v2", + "name": "Arbitrum Mainnet" + }, + { + "url": "https://arb-sepolia.g.alchemy.com/v2", + "name": "Arbitrum Sepolia" + }, + { + "url": "https://avax-mainnet.g.alchemy.com/v2", + "name": "Avalanche Mainnet" + }, + { + "url": "https://avax-fuji.g.alchemy.com/v2", + "name": "Avalanche Fuji" + }, + { + "url": "https://base-mainnet.g.alchemy.com/v2", + "name": "Base Mainnet" + }, + { + "url": "https://base-sepolia.g.alchemy.com/v2", + "name": "Base Sepolia" + }, + { + "url": "https://berachain-mainnet.g.alchemy.com/v2", + "name": "Berachain Mainnet" + }, + { + "url": "https://berachain-bepolia.g.alchemy.com/v2", + "name": "Berachain Bepolia" + }, + { + "url": "https://blast-mainnet.g.alchemy.com/v2", + "name": "Blast Mainnet" + }, + { + "url": "https://blast-sepolia.g.alchemy.com/v2", + "name": "Blast Sepolia" + }, + { + "url": "https://bnb-mainnet.g.alchemy.com/v2", + "name": "BNB Smart Chain Mainnet" + }, + { + "url": "https://bnb-testnet.g.alchemy.com/v2", + "name": "BNB Smart Chain Testnet" + }, + { + "url": "https://celo-mainnet.g.alchemy.com/v2", + "name": "Celo Mainnet" + }, + { + "url": "https://celo-sepolia.g.alchemy.com/v2", + "name": "Celo Sepolia" + }, + { + "url": "https://gensyn-mainnet.g.alchemy.com/v2", + "name": "Gensyn Mainnet" + }, + { + "url": "https://gensyn-testnet.g.alchemy.com/v2", + "name": "Gensyn Testnet" + }, + { + "url": "https://gnosis-mainnet.g.alchemy.com/v2", + "name": "Gnosis Mainnet" + }, + { + "url": "https://gnosis-chiado.g.alchemy.com/v2", + "name": "Gnosis Chiado" + }, + { + "url": "https://hyperliquid-mainnet.g.alchemy.com/v2", + "name": "Hyperliquid Mainnet" + }, + { + "url": "https://hyperliquid-testnet.g.alchemy.com/v2", + "name": "Hyperliquid Testnet" + }, + { + "url": "https://ink-mainnet.g.alchemy.com/v2", + "name": "Ink Mainnet" + }, + { + "url": "https://ink-sepolia.g.alchemy.com/v2", + "name": "Ink Sepolia" + }, + { + "url": "https://lens-mainnet.g.alchemy.com/v2", + "name": "Lens Mainnet" + }, + { + "url": "https://lens-sepolia.g.alchemy.com/v2", + "name": "Lens Sepolia" + }, + { + "url": "https://linea-mainnet.g.alchemy.com/v2", + "name": "Linea Mainnet" + }, + { + "url": "https://linea-sepolia.g.alchemy.com/v2", + "name": "Linea Sepolia" + }, + { + "url": "https://polygon-mainnet.g.alchemy.com/v2", + "name": "Polygon Mainnet" + }, + { + "url": "https://polygon-amoy.g.alchemy.com/v2", + "name": "Polygon Amoy" + }, + { + "url": "https://monad-mainnet.g.alchemy.com/v2", + "name": "Monad Mainnet" + }, + { + "url": "https://monad-testnet.g.alchemy.com/v2", + "name": "Monad Testnet" + }, + { + "url": "https://mythos-mainnet.g.alchemy.com/v2", + "name": "Mythos Mainnet" + }, + { + "url": "https://opt-mainnet.g.alchemy.com/v2", + "name": "OP Mainnet Mainnet" + }, + { + "url": "https://opt-sepolia.g.alchemy.com/v2", + "name": "OP Mainnet Sepolia" + }, + { + "url": "https://robinhood-testnet.g.alchemy.com/v2", + "name": "Robinhood Chain Testnet" + }, + { + "url": "https://ronin-mainnet.g.alchemy.com/v2", + "name": "Ronin Mainnet" + }, + { + "url": "https://ronin-saigon.g.alchemy.com/v2", + "name": "Ronin Saigon" + }, + { + "url": "https://rootstock-mainnet.g.alchemy.com/v2", + "name": "Rootstock Mainnet" + }, + { + "url": "https://rootstock-testnet.g.alchemy.com/v2", + "name": "Rootstock Testnet" + }, + { + "url": "https://scroll-mainnet.g.alchemy.com/v2", + "name": "Scroll Mainnet" + }, + { + "url": "https://scroll-sepolia.g.alchemy.com/v2", + "name": "Scroll Sepolia" + }, + { + "url": "https://settlus-mainnet.g.alchemy.com/v2", + "name": "Settlus Mainnet" + }, + { + "url": "https://settlus-septestnet.g.alchemy.com/v2", + "name": "Settlus Sepolia" + }, + { + "url": "https://shape-mainnet.g.alchemy.com/v2", + "name": "Shape Mainnet" + }, + { + "url": "https://shape-sepolia.g.alchemy.com/v2", + "name": "Shape Sepolia" + }, + { + "url": "https://soneium-mainnet.g.alchemy.com/v2", + "name": "Soneium Mainnet" + }, + { + "url": "https://soneium-minato.g.alchemy.com/v2", + "name": "Soneium Minato" + }, + { + "url": "https://story-mainnet.g.alchemy.com/v2", + "name": "Story Mainnet" + }, + { + "url": "https://story-aeneid.g.alchemy.com/v2", + "name": "Story Aeneid" + }, + { + "url": "https://unichain-mainnet.g.alchemy.com/v2", + "name": "Unichain Mainnet" + }, + { + "url": "https://unichain-sepolia.g.alchemy.com/v2", + "name": "Unichain Sepolia" + }, + { + "url": "https://worldchain-mainnet.g.alchemy.com/v2", + "name": "World Chain Mainnet" + }, + { + "url": "https://worldchain-sepolia.g.alchemy.com/v2", + "name": "World Chain Sepolia" + }, + { + "url": "https://zetachain-mainnet.g.alchemy.com/v2", + "name": "ZetaChain Mainnet" + }, + { + "url": "https://zetachain-testnet.g.alchemy.com/v2", + "name": "ZetaChain Testnet" + }, + { + "url": "https://zksync-mainnet.g.alchemy.com/v2", + "name": "ZKsync Mainnet" + }, + { + "url": "https://zksync-sepolia.g.alchemy.com/v2", + "name": "ZKsync Sepolia" + }, + { + "url": "https://zora-mainnet.g.alchemy.com/v2", + "name": "Zora Mainnet" + }, + { + "url": "https://zora-sepolia.g.alchemy.com/v2", + "name": "Zora Sepolia" + } + ], + "methods": [ + { + "name": "alchemy_getAssetTransfers", + "description": "The Transfers API allows you to easily fetch historical transactions for any address across Ethereum and supported L2s including Base, Polygon, Arbitrum, and Optimism (internal transfer data is only available on Ethereum Mainnet and Polygon Mainnet).", + "x-compute-units": 120, + "params": [ + { + "name": "assetTransferParams", + "required": true, + "schema": { + "type": "object", + "properties": { + "fromBlock": { + "type": "string", + "default": "0x0" + }, + "toBlock": { + "type": "string", + "default": "latest" + }, + "fromAddress": { + "type": "string", + "default": "0x0000000000000000000000000000000000000000" + }, + "toAddress": { + "type": "string", + "default": "0x0000000000000000000000000000000000000000" + }, + "excludeZeroValue": { + "type": "boolean", + "default": true + }, + "category": { + "type": "array", + "description": "Array of transfer categories to include. Note: 'internal' category is not supported on Base, it is only available on Ethereum Mainnet and Polygon Mainnet.", + "items": { + "type": "string", + "enum": [ + "external", + "internal", + "erc20", + "erc721", + "erc1155", + "specialnft" + ] + } + }, + "contractAddresses": { + "type": "array", + "description": "An array of contract addresses to filter for.", + "items": { + "type": "string" + } + }, + "order": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "withMetadata": { + "type": "boolean", + "description": "Available only on ETH, Base, Polygon, Arbitrum, Optimism", + "default": false + }, + "maxCount": { + "type": "string", + "default": "0x3e8" + }, + "pageKey": { + "type": "string", + "default": "0x0" + } + } + } + } + ], + "result": { + "name": "Asset transfers", + "description": "Returns the list of transfers, and a pageKey if additional results remain.", + "schema": { + "oneOf": [ + { + "title": "Not Found (null)", + "type": "string" + }, + { + "type": "object", + "properties": { + "pageKey": { + "type": "string", + "description": "Uuid of next page of results (if exists, else blank)." + }, + "transfers": { + "type": "array", + "description": "Array of transfer objects sorted in ascending or descending order by block number.", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "'external', 'internal', 'token', 'erc20', 'erc721', 'erc1155', 'specialnft'" + }, + "blockNum": { + "type": "string", + "description": "Block number of the transfer (hex string)." + }, + "from": { + "type": "string", + "description": "From address (hex string)." + }, + "to": { + "type": "string", + "description": "To address (hex string). null if contract creation." + }, + "value": { + "type": "number", + "nullable": true, + "description": "Asset transfer value. null if it's an ERC721 or unknown decimals." + }, + "erc721TokenId": { + "type": "string", + "nullable": true, + "description": "(Deprecated) Legacy token ID field for ERC721 tokens. Use `tokenId` instead." + }, + "erc1155Metadata": { + "type": "array", + "nullable": true, + "description": "Array of objects with (tokenId, value) for ERC1155 transfers, null otherwise.", + "items": { + "type": "object", + "properties": { + "tokenId": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + }, + "tokenId": { + "type": "string", + "description": "Token ID for NFT tokens (ERC721, ERC1155, etc.)." + }, + "asset": { + "type": "string", + "nullable": true, + "description": "ETH or the token's symbol. null if unavailable." + }, + "uniqueId": { + "type": "string", + "description": "Unique identifier for the transfer object." + }, + "hash": { + "type": "string", + "description": "Transaction hash (hex string)." + }, + "rawContract": { + "type": "object", + "properties": { + "value": { + "type": "string", + "nullable": true, + "description": "Raw hex transfer value. null for NFT transfers." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Contract address (hex string). null for external or internal transfers." + }, + "decimal": { + "type": "string", + "nullable": true, + "description": "Contract decimal in hex. null if not known." + } + } + }, + "metadata": { + "type": "object", + "properties": { + "blockTimestamp": { + "type": "string", + "description": "ISO-formatted timestamp of the block containing this transfer. (Available only on ETH, Base, Polygon, Arbitrum, Optimism)" + } + } + } + } + } + } + } + } + ] + } + }, + "examples": [ + { + "name": "alchemy_getAssetTransfers example", + "params": [ + { + "name": "assetTransferParams", + "value": { + "fromBlock": "0x0", + "fromAddress": "0x0000000000000000000000000000000000000000", + "toAddress": "0x5c43B1eD97e52d009611D89b74fA829FE4ac56b1", + "excludeZeroValue": true, + "withMetadata": true, + "category": [ + "erc721", + "erc1155" + ] + } + } + ], + "result": { + "name": "Asset transfers", + "value": { + "transfers": [ + { + "blockNum": "0xb0eadc", + "uniqueId": "0x3847245c01829b043431067fb2bfa95f7b5bdc7e4246c843e7a573ab6f26f5ff:external", + "hash": "0x3847245c01829b043431067fb2bfa95f7b5bdc7e4246c843e7a573ab6f26f5ff", + "from": "0xef4396d9ff8107086d215a1c9f8866c54795d7c7", + "to": "0x5c43b1ed97e52d009611d89b74fa829fe4ac56b1", + "value": 0.5, + "erc721TokenId": null, + "erc1155Metadata": null, + "tokenId": null, + "asset": "ETH", + "category": "external", + "rawContract": { + "value": "0x6f05b59d3b20000", + "address": null, + "decimal": "0x12" + } + }, + { + "blockNum": "0xb96042", + "uniqueId": "0x5c88806ce2e4a42c5fbd5804f340ed887995914546cf92ec39eb5472cf22c88c:external", + "hash": "0x5c88806ce2e4a42c5fbd5804f340ed887995914546cf92ec39eb5472cf22c88c", + "from": "0xef4396d9ff8107086d215a1c9f8866c54795d7c7", + "to": "0x5c43b1ed97e52d009611d89b74fa829fe4ac56b1", + "value": 0.27, + "erc721TokenId": null, + "erc1155Metadata": null, + "tokenId": null, + "asset": "ETH", + "category": "external", + "rawContract": { + "value": "0x3bf3b91c95b0000", + "address": null, + "decimal": "0x12" + } + } + ], + "pageKey": "" + } + } + } + ] + } + ], + "x-auth-params": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ] +} diff --git a/packages/api-codegen/src/cli.ts b/packages/api-codegen/src/cli.ts new file mode 100644 index 0000000000..bc273cd62a --- /dev/null +++ b/packages/api-codegen/src/cli.ts @@ -0,0 +1,37 @@ +import { generate } from "./generate.js"; +import { snapshot } from "./snapshot.js"; + +/** + * CLI entrypoint. + * + * tsx src/cli.ts snapshot [--docs ] [--allow-dirty] + * tsx src/cli.ts --target + */ +async function main(): Promise { + const args = process.argv.slice(2); + + if (args[0] === "snapshot") { + const docsFlag = args.indexOf("--docs"); + await snapshot({ + docsDir: docsFlag !== -1 ? args[docsFlag + 1] : undefined, + allowDirty: args.includes("--allow-dirty"), + }); + return; + } + + const targetFlag = args.indexOf("--target"); + if (targetFlag !== -1 && args[targetFlag + 1]) { + await generate(args[targetFlag + 1]); + return; + } + + console.error( + "Usage: cli.ts snapshot [--docs ] [--allow-dirty] | cli.ts --target ", + ); + process.exit(1); +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : error); + process.exit(1); +}); diff --git a/packages/api-codegen/src/errors.ts b/packages/api-codegen/src/errors.ts new file mode 100644 index 0000000000..6a5bee6a7d --- /dev/null +++ b/packages/api-codegen/src/errors.ts @@ -0,0 +1,7 @@ +/** + * Error type for all failures raised by the codegen tool (manifest/spec + * mismatches, lockfile checksum failures, malformed specs). + */ +export class CodegenError extends Error { + override name = "CodegenError"; +} diff --git a/packages/api-codegen/src/format.ts b/packages/api-codegen/src/format.ts new file mode 100644 index 0000000000..ef07525d3e --- /dev/null +++ b/packages/api-codegen/src/format.ts @@ -0,0 +1,29 @@ +import prettier from "prettier"; + +const BANNER = `/* eslint-disable -- machine-generated file */ +// AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. +// Regenerate with \`pnpm generate\`. Spec provenance (docs repo commit + checksums) +// is recorded in packages/api-codegen/specs/specs.lock.json. + +`; + +/** + * Prepends the do-not-edit banner and formats generated TypeScript with the + * repo's prettier config (resolved from the output file's location, exactly + * as `prettier --write` would). Generated files are excluded from lint, so + * the generator is responsible for producing stable, formatted output. + * + * @param {string} source Generated TypeScript source (without banner) + * @param {string} outputPath Absolute output path used to resolve prettier config + * @returns {Promise} Banner + formatted source + */ +export async function formatGenerated( + source: string, + outputPath: string, +): Promise { + const config = await prettier.resolveConfig(outputPath); + return prettier.format(BANNER + source, { + ...config, + parser: "typescript", + }); +} diff --git a/packages/api-codegen/src/generate.ts b/packages/api-codegen/src/generate.ts new file mode 100644 index 0000000000..c9788c2433 --- /dev/null +++ b/packages/api-codegen/src/generate.ts @@ -0,0 +1,81 @@ +import { resolve } from "node:path"; +import { CodegenError } from "./errors.js"; +import { formatGenerated } from "./format.js"; +import { + loadManifest, + validateRestManifest, + validateRpcManifest, +} from "./manifest.js"; +import { REPO_ROOT } from "./paths.js"; +import { emitRestSchema } from "./rest/schemaEmitter.js"; +import { generateOpenapiTypes } from "./rest/openapiTypes.js"; +import { emitRpcSchema } from "./rpc/rpcEmitter.js"; +import { readSnapshot } from "./snapshot.js"; +import { TARGETS } from "./targets.js"; +import { writeIfChanged } from "./write.js"; + +/** + * Formats and writes one generated file, logging the outcome. + * + * @param {string} outputPath Absolute output path + * @param {string} source Generated source (unformatted, no banner) + */ +async function emitFile(outputPath: string, source: string): Promise { + const formatted = await formatGenerated(source, outputPath); + const wrote = writeIfChanged(outputPath, formatted); + console.log( + ` ${outputPath.replace(REPO_ROOT + "/", "")} ${wrote ? "written" : "unchanged"}`, + ); +} + +/** + * Generates a target's `src/generated/` internals from the committed spec + * snapshots (offline + deterministic). Hard-errors if the target's manifest + * references operations/methods missing from the snapshots. + * + * @param {string} targetName A key of TARGETS (e.g. "data-apis") + */ +export async function generate(targetName: string): Promise { + const target = TARGETS[targetName]; + if (!target) { + throw new CodegenError( + `Unknown target "${targetName}". Registered: ${Object.keys(TARGETS).join(", ")}.`, + ); + } + const packageDir = resolve(REPO_ROOT, target.packageDir); + const outputDir = resolve(packageDir, target.outputDir); + const manifest = await loadManifest(resolve(packageDir, target.manifestPath)); + console.log(`Generating ${target.packageDir}/${target.outputDir} ...`); + + for (const restConfig of manifest.rest) { + const spec = readSnapshot(restConfig.spec); + const uncovered = validateRestManifest(restConfig, spec); + if (uncovered.length > 0) { + console.warn( + ` note: spec "${restConfig.spec}" has ${uncovered.length} operation(s) not in the manifest: ${uncovered.join(", ")}`, + ); + } + await emitFile( + resolve(outputDir, `rest/${restConfig.spec}.types.ts`), + await generateOpenapiTypes(spec), + ); + await emitFile( + resolve(outputDir, `rest/${restConfig.spec}.schema.ts`), + emitRestSchema(restConfig, spec), + ); + } + + for (const rpcConfig of manifest.rpc) { + const spec = readSnapshot(rpcConfig.spec); + const uncovered = validateRpcManifest(rpcConfig, spec); + if (uncovered.length > 0) { + console.warn( + ` note: spec "${rpcConfig.spec}" has ${uncovered.length} method(s) not in the manifest: ${uncovered.join(", ")}`, + ); + } + await emitFile( + resolve(outputDir, `rpc/${rpcConfig.spec}.ts`), + await emitRpcSchema(rpcConfig, spec), + ); + } +} diff --git a/packages/api-codegen/src/hash.ts b/packages/api-codegen/src/hash.ts new file mode 100644 index 0000000000..679abcf29b --- /dev/null +++ b/packages/api-codegen/src/hash.ts @@ -0,0 +1,12 @@ +import { createHash } from "node:crypto"; + +/** + * Computes the sha256 hex digest of a string, used to pin spec snapshots in + * specs.lock.json and verify them at generate time. + * + * @param {string} content The content to hash + * @returns {string} The sha256 hex digest + */ +export function sha256(content: string): string { + return createHash("sha256").update(content, "utf8").digest("hex"); +} diff --git a/packages/api-codegen/src/index.ts b/packages/api-codegen/src/index.ts new file mode 100644 index 0000000000..ed41b98c0f --- /dev/null +++ b/packages/api-codegen/src/index.ts @@ -0,0 +1,10 @@ +// Public (workspace-internal) surface: the manifest types targets use to +// author their codegen.manifest.ts. +export type { + CodegenManifest, + PathRules, + RestOperationConfig, + RestSpecConfig, + RpcMethodConfig, + RpcSpecConfig, +} from "./manifest.js"; diff --git a/packages/api-codegen/src/manifest.ts b/packages/api-codegen/src/manifest.ts new file mode 100644 index 0000000000..f4b1f09d09 --- /dev/null +++ b/packages/api-codegen/src/manifest.ts @@ -0,0 +1,152 @@ +import { pathToFileURL } from "node:url"; +import { CodegenError } from "./errors.js"; + +/** Path → Route normalization rules for a REST spec (see normalizePath). */ +export type PathRules = { + /** Drop `{apiKey}` path segments (runtime auth is header-based). Default true. */ + stripApiKeySegment?: boolean; + /** Prefix already present in the runtime base URL (e.g. "/v3" for NFT). */ + stripPrefix?: string; +}; + +/** A single OpenAPI operation the target consumes. */ +export type RestOperationConfig = { + /** operationId as it appears in the spec (e.g. "getNFTsForOwner-v3"). */ + operationId: string; + /** Base for emitted type names (e.g. "GetNftsForOwner" → GetNftsForOwnerResponse). */ + exportBaseName: string; + /** Also emit a named query-params type (GET endpoints; RestRequestSchema has no query channel). */ + emitQueryType?: boolean; +}; + +/** One REST spec consumed by the target. */ +export type RestSpecConfig = { + /** Snapshot basename without extension (specs/.json). */ + spec: string; + /** Name of the emitted RestRequestSchema tuple type (e.g. "PortfolioRestSchema"). */ + schemaTypeName: string; + pathRules?: PathRules; + operations: RestOperationConfig[]; +}; + +/** A single OpenRPC method the target consumes. */ +export type RpcMethodConfig = { + /** JSON-RPC method name as it appears in the spec (e.g. "alchemy_getAssetTransfers"). */ + method: string; + /** Base for emitted type names (e.g. "AlchemyGetAssetTransfers"). */ + exportBaseName: string; +}; + +/** One OpenRPC spec consumed by the target. */ +export type RpcSpecConfig = { + /** Snapshot basename without extension (specs/.json). */ + spec: string; + /** Name of the emitted viem RpcSchema tuple type (e.g. "TransfersRpcSchema"). */ + schemaTypeName: string; + methods: RpcMethodConfig[]; +}; + +/** + * The per-target overlay manifest: the hand-maintained mapping from spec + * operations to the SDK's generated type surface. Validated against the spec + * snapshots on every generate run — a referenced operation missing from the + * snapshot is a hard error (the drift alarm). + */ +export type CodegenManifest = { + rest: RestSpecConfig[]; + rpc: RpcSpecConfig[]; +}; + +/** + * Loads a target's codegen.manifest.ts (default export). + * + * @param {string} manifestPath Absolute path to the manifest module + * @returns {Promise} The manifest + */ +export async function loadManifest( + manifestPath: string, +): Promise { + const mod = await import(pathToFileURL(manifestPath).href); + const manifest = mod.default as CodegenManifest | undefined; + if ( + !manifest || + !Array.isArray(manifest.rest) || + !Array.isArray(manifest.rpc) + ) { + throw new CodegenError( + `Manifest at ${manifestPath} must default-export a CodegenManifest ({ rest, rpc }).`, + ); + } + return manifest; +} + +/** + * Validates that every operationId a REST manifest entry references exists in + * the spec snapshot, and reports spec operations the manifest doesn't cover. + * + * @param {RestSpecConfig} config The manifest entry for this spec + * @param {Record} spec The parsed OpenAPI snapshot + * @returns {string[]} operationIds present in the spec but not in the manifest (new-endpoint visibility) + */ +export function validateRestManifest( + config: RestSpecConfig, + spec: Record, +): string[] { + const specOperationIds = new Set(); + const paths = (spec.paths ?? {}) as Record< + string, + Record + >; + for (const methods of Object.values(paths)) { + for (const op of Object.values(methods)) { + if (op && typeof op === "object" && op.operationId) { + specOperationIds.add(op.operationId); + } + } + } + + const missing = config.operations + .map((o) => o.operationId) + .filter((id) => !specOperationIds.has(id)); + if (missing.length > 0) { + throw new CodegenError( + `Spec "${config.spec}" no longer contains operationId(s) referenced by the manifest: ${missing.join(", ")}. ` + + `The upstream spec was likely renamed or removed — update the manifest (and the SDK surface) deliberately.`, + ); + } + + const covered = new Set(config.operations.map((o) => o.operationId)); + return [...specOperationIds].filter((id) => !covered.has(id)).sort(); +} + +/** + * Validates that every RPC method a manifest entry references exists in the + * OpenRPC snapshot, and reports spec methods the manifest doesn't cover. + * + * @param {RpcSpecConfig} config The manifest entry for this spec + * @param {Record} spec The parsed OpenRPC snapshot + * @returns {string[]} methods present in the spec but not in the manifest + */ +export function validateRpcManifest( + config: RpcSpecConfig, + spec: Record, +): string[] { + const specMethods = new Set( + ((spec.methods ?? []) as Array<{ name?: string }>) + .map((m) => m.name) + .filter((name): name is string => typeof name === "string"), + ); + + const missing = config.methods + .map((m) => m.method) + .filter((name) => !specMethods.has(name)); + if (missing.length > 0) { + throw new CodegenError( + `Spec "${config.spec}" no longer contains RPC method(s) referenced by the manifest: ${missing.join(", ")}. ` + + `The upstream spec was likely renamed or removed — update the manifest (and the SDK surface) deliberately.`, + ); + } + + const covered = new Set(config.methods.map((m) => m.method)); + return [...specMethods].filter((name) => !covered.has(name)).sort(); +} diff --git a/packages/api-codegen/src/paths.ts b/packages/api-codegen/src/paths.ts new file mode 100644 index 0000000000..8e17b3db14 --- /dev/null +++ b/packages/api-codegen/src/paths.ts @@ -0,0 +1,17 @@ +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +/** Absolute path of the api-codegen package directory. */ +export const PACKAGE_DIR = resolve( + dirname(fileURLToPath(import.meta.url)), + "..", +); + +/** Absolute path of the aa-sdk repo root. */ +export const REPO_ROOT = resolve(PACKAGE_DIR, "../.."); + +/** Absolute path of the committed spec snapshots directory. */ +export const SPECS_DIR = resolve(PACKAGE_DIR, "specs"); + +/** Absolute path of the snapshot lockfile. */ +export const LOCKFILE_PATH = resolve(SPECS_DIR, "specs.lock.json"); diff --git a/packages/api-codegen/src/rest/normalizePath.ts b/packages/api-codegen/src/rest/normalizePath.ts new file mode 100644 index 0000000000..805a5c92d7 --- /dev/null +++ b/packages/api-codegen/src/rest/normalizePath.ts @@ -0,0 +1,45 @@ +import type { PathRules } from "../manifest.js"; +import { CodegenError } from "../errors.js"; + +/** + * Normalizes an OpenAPI spec path into the Route literal the SDK's REST + * client uses at runtime. Docs specs embed auth and version prefixes in the + * path (e.g. "/v3/{apiKey}/getNFTsForOwner"); the runtime uses header auth + * against base URLs that already carry the prefix, so Routes are relative + * (e.g. "getNFTsForOwner"). + * + * Rules, in order: drop `{apiKey}` segments (default on), strip the + * configured prefix, strip leading slashes. + * + * @param {string} specPath The path as it appears in the spec + * @param {PathRules} [rules] Per-spec normalization rules from the manifest + * @returns {string} The normalized Route literal + */ +export function normalizePath(specPath: string, rules?: PathRules): string { + let path = specPath; + + if (rules?.stripApiKeySegment !== false) { + path = path + .split("/") + .filter((segment) => segment !== "{apiKey}") + .join("/"); + } + + if (rules?.stripPrefix) { + if (!path.startsWith(rules.stripPrefix)) { + throw new CodegenError( + `Path "${specPath}" does not start with configured stripPrefix "${rules.stripPrefix}" ` + + `(after apiKey-segment removal: "${path}"). The upstream path layout changed — update the manifest.`, + ); + } + path = path.slice(rules.stripPrefix.length); + } + + const route = path.replace(/^\/+/, ""); + if (route.length === 0) { + throw new CodegenError( + `Path "${specPath}" normalized to an empty route with rules ${JSON.stringify(rules ?? {})}.`, + ); + } + return route; +} diff --git a/packages/api-codegen/src/rest/openapiTypes.ts b/packages/api-codegen/src/rest/openapiTypes.ts new file mode 100644 index 0000000000..5d302ab009 --- /dev/null +++ b/packages/api-codegen/src/rest/openapiTypes.ts @@ -0,0 +1,17 @@ +import openapiTS, { astToString } from "openapi-typescript"; + +/** + * Runs openapi-typescript over a bundled (dereferenced) OpenAPI document and + * returns the generated source (paths/components/operations interfaces). + * + * @param {Record} spec The parsed OpenAPI snapshot + * @returns {Promise} Generated TypeScript source (unformatted, no banner) + */ +export async function generateOpenapiTypes( + spec: Record, +): Promise { + // openapi-typescript accepts an in-memory document; snapshots are already + // dereferenced by the docs repo's bundling, so no $ref resolution happens here. + const ast = await openapiTS(spec as Parameters[0]); + return astToString(ast); +} diff --git a/packages/api-codegen/src/rest/schemaEmitter.ts b/packages/api-codegen/src/rest/schemaEmitter.ts new file mode 100644 index 0000000000..ffd16f2ecc --- /dev/null +++ b/packages/api-codegen/src/rest/schemaEmitter.ts @@ -0,0 +1,176 @@ +import { CodegenError } from "../errors.js"; +import { normalizePath } from "./normalizePath.js"; +import type { RestSpecConfig } from "../manifest.js"; + +const REST_METHODS = [ + "get", + "post", + "put", + "delete", + "patch", + "options", + "head", +] as const; + +type SpecOperation = { + operationId?: string; + requestBody?: { content?: Record }; + responses?: Record }>; + parameters?: Array<{ in?: string }>; +}; + +type LocatedOperation = { + path: string; + method: (typeof REST_METHODS)[number]; + operation: SpecOperation; +}; + +/** + * Finds an operation by operationId across all paths/methods of a bundled + * OpenAPI spec. + * + * @param {Record} spec The parsed OpenAPI snapshot + * @param {string} operationId The operationId to locate + * @returns {LocatedOperation} The operation with its path and method + */ +function locateOperation( + spec: Record, + operationId: string, +): LocatedOperation { + const paths = (spec.paths ?? {}) as Record< + string, + Record + >; + for (const [path, methods] of Object.entries(paths)) { + for (const method of REST_METHODS) { + const operation = methods[method]; + if (operation?.operationId === operationId) { + return { path, method, operation }; + } + } + } + // validateRestManifest runs first, so this indicates a walker bug. + throw new CodegenError(`Operation "${operationId}" not found in spec.`); +} + +/** + * Picks the success response status key for an operation: "200" if present, + * otherwise the first 2xx key. + * + * @param {SpecOperation} operation The spec operation + * @param {string} operationId For error messages + * @returns {{ status: string; contentType: string }} Status code and content type to index with + */ +function pickSuccessResponse( + operation: SpecOperation, + operationId: string, +): { status: string; contentType: string } { + const responses = operation.responses ?? {}; + const status = + "200" in responses + ? "200" + : Object.keys(responses).find((key) => /^2\d\d$/.test(key)); + if (!status) { + throw new CodegenError( + `Operation "${operationId}" has no 2xx response in the spec.`, + ); + } + const content = responses[status]?.content ?? {}; + const contentType = + "application/json" in content + ? "application/json" + : Object.keys(content)[0]; + if (!contentType) { + throw new CodegenError( + `Operation "${operationId}" response ${status} has no content types.`, + ); + } + return { status, contentType }; +} + +/** + * Emits the .schema.ts source for a REST spec: named Body/Response + * (and optional Query) aliases indexed into the openapi-typescript output, + * plus the RestRequestSchema tuple consumed by AlchemyRestClient. + * + * @param {RestSpecConfig} config The manifest entry for this spec + * @param {Record} spec The parsed OpenAPI snapshot + * @returns {string} TypeScript source (unformatted, no banner) + */ +export function emitRestSchema( + config: RestSpecConfig, + spec: Record, +): string { + const aliasBlocks: string[] = []; + const tupleEntries: string[] = []; + + for (const opConfig of config.operations) { + const { operationId, exportBaseName, emitQueryType } = opConfig; + const { path, method, operation } = locateOperation(spec, operationId); + const route = normalizePath(path, config.pathRules); + const hasBody = operation.requestBody != null; + const { status, contentType } = pickSuccessResponse(operation, operationId); + const opIndex = JSON.stringify(operationId); + + if (hasBody) { + const bodyContentType = Object.keys( + operation.requestBody?.content ?? {}, + ).includes("application/json") + ? "application/json" + : Object.keys(operation.requestBody?.content ?? {})[0]; + aliasBlocks.push( + `/** Request body for ${operationId}. */\n` + + `export type ${exportBaseName}Body = NonNullable<\n` + + ` operations[${opIndex}]["requestBody"]\n` + + `>["content"][${JSON.stringify(bodyContentType)}];`, + ); + } + + aliasBlocks.push( + `/** ${status} response for ${operationId}. */\n` + + `export type ${exportBaseName}Response =\n` + + ` operations[${opIndex}]["responses"][${JSON.stringify(status)}]["content"][${JSON.stringify(contentType)}];`, + ); + + if (emitQueryType) { + aliasBlocks.push( + `/**\n` + + ` * Query params for ${operationId}. Sent via the URL at runtime;\n` + + ` * RestRequestSchema has no query channel, so this is exposed as a\n` + + ` * standalone type for the SDK's params layer.\n` + + ` */\n` + + `export type ${exportBaseName}Query = NonNullable<\n` + + ` operations[${opIndex}]["parameters"]["query"]\n` + + `>;`, + ); + } + + tupleEntries.push( + ` {\n` + + ` /** ${method.toUpperCase()} ${path} (operationId: ${operationId}) */\n` + + ` Route: ${JSON.stringify(route)};\n` + + ` Method: ${JSON.stringify(method.toUpperCase())};\n` + + (hasBody + ? ` Body: ${exportBaseName}Body;\n` + : ` Body?: undefined;\n`) + + ` Response: ${exportBaseName}Response;\n` + + ` },`, + ); + } + + return [ + `import type { RestRequestSchema } from "@alchemy/common";`, + `import type { operations } from "./${config.spec}.types.js";`, + ``, + ...aliasBlocks.map((block) => block + "\n"), + `/** RestRequestSchema entries for the ${config.spec} REST API. */`, + `export type ${config.schemaTypeName} = readonly [`, + ...tupleEntries, + `];`, + ``, + `/** Compile-time guard that the emitted tuple satisfies the shared constraint. */`, + `export type _Assert${config.schemaTypeName} =`, + ` ${config.schemaTypeName} extends RestRequestSchema ? true : never;`, + ``, + ].join("\n"); +} diff --git a/packages/api-codegen/src/rpc/openrpcWalker.ts b/packages/api-codegen/src/rpc/openrpcWalker.ts new file mode 100644 index 0000000000..481b8985cf --- /dev/null +++ b/packages/api-codegen/src/rpc/openrpcWalker.ts @@ -0,0 +1,89 @@ +import { CodegenError } from "../errors.js"; + +/** JSON Schema node (loose — specs are already dereferenced). */ +export type JsonSchema = Record; + +/** An OpenRPC method's extracted schemas, ready for type compilation. */ +export type ExtractedMethod = { + /** JSON-RPC method name (e.g. "alchemy_getAssetTransfers"). */ + name: string; + /** Positional params: name, JSON Schema, and OpenRPC `required` flag (defaults false). */ + params: Array<{ name: string; required: boolean; schema: JsonSchema }>; + /** Result JSON Schema. */ + result: JsonSchema; +}; + +/** + * Recursively closes object schemas: sets `additionalProperties: false` + * wherever it's unspecified so json-schema-to-typescript emits closed + * interfaces instead of `[k: string]: unknown` index signatures. Returns a + * deep copy; the input is not mutated. + * + * @param {unknown} node A JSON Schema node (or fragment) + * @returns {unknown} The closed copy + */ +export function closeObjectSchemas(node: unknown): unknown { + if (Array.isArray(node)) { + return node.map(closeObjectSchemas); + } + if (node === null || typeof node !== "object") { + return node; + } + const copy: Record = {}; + for (const [key, value] of Object.entries(node)) { + copy[key] = closeObjectSchemas(value); + } + const isObjectSchema = + copy.type === "object" || copy.properties !== undefined; + if (isObjectSchema && copy.additionalProperties === undefined) { + copy.additionalProperties = false; + } + return copy; +} + +/** + * Extracts a method's param and result schemas from a bundled (dereferenced) + * OpenRPC document, with object schemas closed for clean type compilation. + * + * @param {Record} spec The parsed OpenRPC snapshot + * @param {string} methodName The JSON-RPC method to extract + * @returns {ExtractedMethod} The extracted schemas + */ +export function extractMethod( + spec: Record, + methodName: string, +): ExtractedMethod { + const methods = (spec.methods ?? []) as Array<{ + name?: string; + params?: Array<{ name?: string; required?: boolean; schema?: JsonSchema }>; + result?: { schema?: JsonSchema }; + }>; + const method = methods.find((m) => m.name === methodName); + if (!method) { + // validateRpcManifest runs first, so this indicates a walker bug. + throw new CodegenError(`Method "${methodName}" not found in spec.`); + } + + const params = (method.params ?? []).map((param, index) => { + if (!param.schema) { + throw new CodegenError( + `Method "${methodName}" param ${param.name ?? index} has no schema.`, + ); + } + return { + name: param.name ?? `param${index}`, + required: param.required === true, + schema: closeObjectSchemas(param.schema) as JsonSchema, + }; + }); + + if (!method.result?.schema) { + throw new CodegenError(`Method "${methodName}" has no result schema.`); + } + + return { + name: methodName, + params, + result: closeObjectSchemas(method.result.schema) as JsonSchema, + }; +} diff --git a/packages/api-codegen/src/rpc/rpcEmitter.ts b/packages/api-codegen/src/rpc/rpcEmitter.ts new file mode 100644 index 0000000000..6ce21a2540 --- /dev/null +++ b/packages/api-codegen/src/rpc/rpcEmitter.ts @@ -0,0 +1,81 @@ +import { compile } from "json-schema-to-typescript"; +import { extractMethod } from "./openrpcWalker.js"; +import type { RpcSpecConfig } from "../manifest.js"; + +/** + * Converts a param name to PascalCase for type naming. + * + * @param {string} name The param name (e.g. "assetTransferParams") + * @returns {string} PascalCase form + */ +function pascal(name: string): string { + return name.charAt(0).toUpperCase() + name.slice(1); +} + +/** + * Emits the .ts source for an OpenRPC spec: per-method param/result + * types compiled from their JSON Schemas (json-schema-to-typescript), plus a + * viem-shaped RpcSchema tuple so `client.request()` is fully typed. + * + * @param {RpcSpecConfig} config The manifest entry for this spec + * @param {Record} spec The parsed OpenRPC snapshot + * @returns {Promise} TypeScript source (unformatted, no banner) + */ +export async function emitRpcSchema( + config: RpcSpecConfig, + spec: Record, +): Promise { + const typeBlocks: string[] = []; + const tupleEntries: string[] = []; + + for (const methodConfig of config.methods) { + const { method, exportBaseName } = methodConfig; + const extracted = extractMethod(spec, method); + + // Single-param methods get the plain "Params" name; multi-param + // methods are disambiguated by param name. + const paramTupleMembers: string[] = []; + for (const param of extracted.params) { + const typeName = + extracted.params.length === 1 + ? `${exportBaseName}Params` + : `${exportBaseName}${pascal(param.name)}Param`; + const compiled = await compile( + // json-schema-to-typescript names the root type from `title` when + // present; pin the name we reference in the tuple instead. + { ...param.schema, title: typeName }, + typeName, + { bannerComment: "", format: false }, + ); + typeBlocks.push(compiled.trimEnd()); + paramTupleMembers.push( + `${param.name}${param.required ? "" : "?"}: ${typeName}`, + ); + } + + const resultTypeName = `${exportBaseName}Result`; + const compiledResult = await compile( + { ...extracted.result, title: resultTypeName }, + resultTypeName, + { bannerComment: "", format: false }, + ); + typeBlocks.push(compiledResult.trimEnd()); + + tupleEntries.push( + ` {\n` + + ` Method: ${JSON.stringify(method)};\n` + + ` Parameters: [${paramTupleMembers.join(", ")}];\n` + + ` ReturnType: ${resultTypeName};\n` + + ` },`, + ); + } + + return [ + ...typeBlocks.map((block) => block + "\n"), + `/** viem RpcSchema entries for the ${config.spec} JSON-RPC methods. */`, + `export type ${config.schemaTypeName} = [`, + ...tupleEntries, + `];`, + ``, + ].join("\n"); +} diff --git a/packages/api-codegen/src/snapshot.ts b/packages/api-codegen/src/snapshot.ts new file mode 100644 index 0000000000..01a25842f6 --- /dev/null +++ b/packages/api-codegen/src/snapshot.ts @@ -0,0 +1,194 @@ +import { execFileSync } from "node:child_process"; +import { existsSync, readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { CodegenError } from "./errors.js"; +import { sha256 } from "./hash.js"; +import { loadManifest } from "./manifest.js"; +import { LOCKFILE_PATH, REPO_ROOT, SPECS_DIR } from "./paths.js"; +import { TARGETS } from "./targets.js"; +import { writeIfChanged } from "./write.js"; + +/** The shape of specs.lock.json: docs provenance + per-snapshot checksums. */ +export type SpecsLockfile = { + docs: { repository: string; sha: string; branch: string }; + specs: Record; +}; + +type SnapshotOptions = { + /** Local docs checkout. Falls back to ALCHEMY_DOCS_DIR, then ../docs next to the repo. */ + docsDir?: string; + /** Skip the clean-working-tree requirement on the docs checkout. */ + allowDirty?: boolean; +}; + +/** + * Runs a git command in the docs checkout and returns trimmed stdout. + * + * @param {string} docsDir The docs checkout directory + * @param {string[]} args git arguments + * @returns {string} Trimmed stdout + */ +function git(docsDir: string, args: string[]): string { + return execFileSync("git", ["-C", docsDir, ...args], { + encoding: "utf8", + }).trim(); +} + +/** + * Collects the union of spec names (by channel) referenced by all registered + * targets' manifests — the single source of truth for what gets snapshotted. + * + * @returns {Promise<{ rest: string[]; rpc: string[] }>} Spec basenames per channel + */ +async function collectSpecNames(): Promise<{ rest: string[]; rpc: string[] }> { + const rest = new Set(); + const rpc = new Set(); + for (const target of Object.values(TARGETS)) { + const manifest = await loadManifest( + resolve(REPO_ROOT, target.packageDir, target.manifestPath), + ); + for (const entry of manifest.rest) rest.add(entry.spec); + for (const entry of manifest.rpc) rpc.add(entry.spec); + } + return { rest: [...rest].sort(), rpc: [...rpc].sort() }; +} + +/** + * Snapshots bundled specs from a local docs checkout into specs/ and writes + * specs.lock.json. Bundling uses the docs repo's own tooling: redocly per + * OpenAPI spec (mirroring its generate-open-api.sh invocation) and its + * generate:rpc script for OpenRPC. + * + * @param {SnapshotOptions} options Docs checkout location and dirty-tree override + */ +export async function snapshot(options: SnapshotOptions = {}): Promise { + const docsDir = resolve( + options.docsDir ?? + process.env.ALCHEMY_DOCS_DIR ?? + resolve(REPO_ROOT, "../docs"), + ); + if (!existsSync(resolve(docsDir, "package.json"))) { + throw new CodegenError( + `No docs checkout at ${docsDir}. Pass --docs or set ALCHEMY_DOCS_DIR.`, + ); + } + + const status = git(docsDir, ["status", "--porcelain"]); + if (status !== "" && !options.allowDirty) { + throw new CodegenError( + `Docs checkout at ${docsDir} has uncommitted changes; commit/stash them or pass --allow-dirty.\n${status}`, + ); + } + const sha = git(docsDir, ["rev-parse", "HEAD"]); + const branch = git(docsDir, ["rev-parse", "--abbrev-ref", "HEAD"]); + + const { rest, rpc } = await collectSpecNames(); + console.log( + `Snapshotting from docs@${sha.slice(0, 9)} (${branch}): rest=[${rest.join(", ")}] rpc=[${rpc.join(", ")}]`, + ); + + // REST: bundle each spec with the same redocly invocation the docs repo's + // generate-open-api.sh uses (skipping its remote-spec fetches and lint pass). + for (const name of rest) { + execFileSync( + "pnpm", + [ + "exec", + "redocly", + "bundle", + `src/openapi/${name}/${name}.yaml`, + "--dereferenced", + "--output", + `content/api-specs/alchemy/rest/${name}.json`, + "--ext", + "json", + "--remove-unused-components", + ], + { cwd: docsDir, stdio: "inherit" }, + ); + } + + // OpenRPC: the docs script generates all RPC specs in one pass (offline). + if (rpc.length > 0) { + execFileSync("pnpm", ["run", "generate:rpc"], { + cwd: docsDir, + stdio: "inherit", + }); + } + + const checksums: Record = {}; + const copies: Array<{ name: string; sourcePath: string }> = [ + ...rest.map((name) => ({ + name, + sourcePath: resolve( + docsDir, + `content/api-specs/alchemy/rest/${name}.json`, + ), + })), + ...rpc.map((name) => ({ + name, + sourcePath: resolve( + docsDir, + `content/api-specs/alchemy/json-rpc/${name}.json`, + ), + })), + ]; + for (const { name, sourcePath } of copies.sort((a, b) => + a.name.localeCompare(b.name), + )) { + if (!existsSync(sourcePath)) { + throw new CodegenError(`Expected bundled spec at ${sourcePath}.`); + } + // Normalize through a single stringify so snapshot formatting never + // depends on the docs tooling's serializer. + const normalized = + JSON.stringify(JSON.parse(readFileSync(sourcePath, "utf8")), null, 2) + + "\n"; + const snapshotPath = resolve(SPECS_DIR, `${name}.json`); + const wrote = writeIfChanged(snapshotPath, normalized); + checksums[`${name}.json`] = sha256(normalized); + console.log(` specs/${name}.json ${wrote ? "updated" : "unchanged"}`); + } + + const lockfile: SpecsLockfile = { + docs: { repository: "alchemyplatform/docs", sha, branch }, + specs: checksums, + }; + const wroteLock = writeIfChanged( + LOCKFILE_PATH, + JSON.stringify(lockfile, null, 2) + "\n", + ); + console.log(` specs/specs.lock.json ${wroteLock ? "updated" : "unchanged"}`); +} + +/** + * Reads a committed spec snapshot, verifying its checksum against the + * lockfile. Generate-time guard against hand-edited snapshots. + * + * @param {string} name Spec basename without extension + * @returns {Record} The parsed spec + */ +export function readSnapshot(name: string): Record { + const snapshotPath = resolve(SPECS_DIR, `${name}.json`); + if (!existsSync(snapshotPath)) { + throw new CodegenError( + `Missing spec snapshot specs/${name}.json — run \`pnpm --filter @alchemy/api-codegen snapshot\`.`, + ); + } + const content = readFileSync(snapshotPath, "utf8"); + const lockfile = JSON.parse( + readFileSync(LOCKFILE_PATH, "utf8"), + ) as SpecsLockfile; + const expected = lockfile.specs[`${name}.json`]; + if (!expected) { + throw new CodegenError( + `specs/${name}.json is not in specs.lock.json — re-run snapshot.`, + ); + } + if (sha256(content) !== expected) { + throw new CodegenError( + `Checksum mismatch for specs/${name}.json — the snapshot was modified outside the snapshot command. Re-run snapshot.`, + ); + } + return JSON.parse(content) as Record; +} diff --git a/packages/api-codegen/src/targets.ts b/packages/api-codegen/src/targets.ts new file mode 100644 index 0000000000..d2f50121d6 --- /dev/null +++ b/packages/api-codegen/src/targets.ts @@ -0,0 +1,18 @@ +/** A consuming package wired into the codegen pipeline. */ +export type TargetConfig = { + /** Package directory, relative to the repo root. */ + packageDir: string; + /** Manifest module path, relative to the package directory. */ + manifestPath: string; + /** Generated-output directory, relative to the package directory. */ + outputDir: string; +}; + +/** All registered codegen targets, keyed by `--target` name. */ +export const TARGETS: Record = { + "data-apis": { + packageDir: "packages/data-apis", + manifestPath: "codegen.manifest.ts", + outputDir: "src/generated", + }, +}; diff --git a/packages/api-codegen/src/write.ts b/packages/api-codegen/src/write.ts new file mode 100644 index 0000000000..83e8337699 --- /dev/null +++ b/packages/api-codegen/src/write.ts @@ -0,0 +1,20 @@ +import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs"; +import { dirname } from "node:path"; + +/** + * Writes a file only if its content actually changed, creating parent + * directories as needed. Keeps git status and build caches quiet when + * regeneration produces identical output. + * + * @param {string} filePath Absolute path to write + * @param {string} content File content + * @returns {boolean} true if the file was written, false if it was already up to date + */ +export function writeIfChanged(filePath: string, content: string): boolean { + if (existsSync(filePath) && readFileSync(filePath, "utf8") === content) { + return false; + } + mkdirSync(dirname(filePath), { recursive: true }); + writeFileSync(filePath, content); + return true; +} diff --git a/packages/api-codegen/tests/fixtures/rest.fixture.json b/packages/api-codegen/tests/fixtures/rest.fixture.json new file mode 100644 index 0000000000..13550b6960 --- /dev/null +++ b/packages/api-codegen/tests/fixtures/rest.fixture.json @@ -0,0 +1,82 @@ +{ + "openapi": "3.1.0", + "info": { "title": "Fixture REST API", "version": "1.0" }, + "paths": { + "/{apiKey}/foo/create": { + "post": { + "operationId": "create-foo", + "parameters": [ + { + "name": "apiKey", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["name"], + "properties": { "name": { "type": "string" } } + } + } + } + }, + "responses": { + "200": { + "description": "ok", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { "id": { "type": "string" } } + } + } + } + } + } + } + }, + "/v3/{apiKey}/getBar": { + "get": { + "operationId": "getBar-v3", + "parameters": [ + { + "name": "apiKey", + "in": "path", + "required": true, + "schema": { "type": "string" } + }, + { + "name": "owner", + "in": "query", + "required": true, + "schema": { "type": "string" } + }, + { + "name": "tags[]", + "in": "query", + "schema": { "type": "array", "items": { "type": "string" } } + } + ], + "responses": { + "200": { + "description": "ok", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "items": { "type": "array", "items": { "type": "string" } } + } + } + } + } + } + } + } + } + } +} diff --git a/packages/api-codegen/tests/fixtures/rpc.fixture.json b/packages/api-codegen/tests/fixtures/rpc.fixture.json new file mode 100644 index 0000000000..6b43396ecc --- /dev/null +++ b/packages/api-codegen/tests/fixtures/rpc.fixture.json @@ -0,0 +1,43 @@ +{ + "openrpc": "1.2.4", + "info": { "title": "Fixture RPC API", "version": "1.0" }, + "methods": [ + { + "name": "fixture_getThings", + "params": [ + { + "name": "thingParams", + "required": true, + "schema": { + "type": "object", + "properties": { + "kind": { "type": "string", "enum": ["a", "b"] }, + "limit": { "type": "string" } + } + } + } + ], + "result": { + "name": "things", + "schema": { + "oneOf": [ + { "title": "Not Found (null)", "type": "string" }, + { + "type": "object", + "properties": { + "pageKey": { "type": "string" }, + "things": { + "type": "array", + "items": { + "type": "object", + "properties": { "id": { "type": "string" } } + } + } + } + } + ] + } + } + } + ] +} diff --git a/packages/api-codegen/tests/manifest.test.ts b/packages/api-codegen/tests/manifest.test.ts new file mode 100644 index 0000000000..6a48a8daf2 --- /dev/null +++ b/packages/api-codegen/tests/manifest.test.ts @@ -0,0 +1,71 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { validateRestManifest, validateRpcManifest } from "../src/manifest.js"; + +const restSpec = JSON.parse( + readFileSync(resolve(__dirname, "fixtures/rest.fixture.json"), "utf8"), +); +const rpcSpec = JSON.parse( + readFileSync(resolve(__dirname, "fixtures/rpc.fixture.json"), "utf8"), +); + +describe("validateRestManifest", () => { + it("passes when all operationIds exist and reports uncovered operations", () => { + const uncovered = validateRestManifest( + { + spec: "rest.fixture", + schemaTypeName: "FooRestSchema", + operations: [ + { operationId: "create-foo", exportBaseName: "CreateFoo" }, + ], + }, + restSpec, + ); + expect(uncovered).toEqual(["getBar-v3"]); + }); + + it("hard-errors on a missing operationId, naming it", () => { + expect(() => + validateRestManifest( + { + spec: "rest.fixture", + schemaTypeName: "FooRestSchema", + operations: [ + { operationId: "create-foo-RENAMED", exportBaseName: "CreateFoo" }, + ], + }, + restSpec, + ), + ).toThrow(/create-foo-RENAMED/); + }); +}); + +describe("validateRpcManifest", () => { + it("passes when all methods exist", () => { + const uncovered = validateRpcManifest( + { + spec: "rpc.fixture", + schemaTypeName: "FixtureRpcSchema", + methods: [{ method: "fixture_getThings", exportBaseName: "GetThings" }], + }, + rpcSpec, + ); + expect(uncovered).toEqual([]); + }); + + it("hard-errors on a missing method, naming it", () => { + expect(() => + validateRpcManifest( + { + spec: "rpc.fixture", + schemaTypeName: "FixtureRpcSchema", + methods: [ + { method: "fixture_getThingsGone", exportBaseName: "GetThings" }, + ], + }, + rpcSpec, + ), + ).toThrow(/fixture_getThingsGone/); + }); +}); diff --git a/packages/api-codegen/tests/normalizePath.test.ts b/packages/api-codegen/tests/normalizePath.test.ts new file mode 100644 index 0000000000..0d48a97efc --- /dev/null +++ b/packages/api-codegen/tests/normalizePath.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "vitest"; +import { normalizePath } from "../src/rest/normalizePath.js"; +import { CodegenError } from "../src/errors.js"; + +describe("normalizePath", () => { + it("strips {apiKey} segments and leading slash", () => { + expect( + normalizePath("/{apiKey}/assets/tokens/by-address", { + stripApiKeySegment: true, + }), + ).toBe("assets/tokens/by-address"); + }); + + it("strips a configured prefix after the apiKey segment", () => { + expect( + normalizePath("/v3/{apiKey}/getNFTsForOwner", { + stripApiKeySegment: true, + stripPrefix: "/v3", + }), + ).toBe("getNFTsForOwner"); + }); + + it("strips {apiKey} by default", () => { + expect(normalizePath("/{apiKey}/foo")).toBe("foo"); + }); + + it("preserves other path params", () => { + expect(normalizePath("/{apiKey}/contracts/{address}")).toBe( + "contracts/{address}", + ); + }); + + it("errors when the configured prefix does not match", () => { + expect(() => + normalizePath("/v4/{apiKey}/getNFTsForOwner", { stripPrefix: "/v3" }), + ).toThrow(CodegenError); + }); + + it("errors when normalization produces an empty route", () => { + expect(() => normalizePath("/{apiKey}")).toThrow(CodegenError); + }); +}); diff --git a/packages/api-codegen/tests/rpcEmitter.test.ts b/packages/api-codegen/tests/rpcEmitter.test.ts new file mode 100644 index 0000000000..1459b723e0 --- /dev/null +++ b/packages/api-codegen/tests/rpcEmitter.test.ts @@ -0,0 +1,56 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { emitRpcSchema } from "../src/rpc/rpcEmitter.js"; +import { closeObjectSchemas } from "../src/rpc/openrpcWalker.js"; + +const spec = JSON.parse( + readFileSync(resolve(__dirname, "fixtures/rpc.fixture.json"), "utf8"), +); + +describe("closeObjectSchemas", () => { + it("sets additionalProperties: false on object schemas that omit it", () => { + const closed = closeObjectSchemas({ + type: "object", + properties: { nested: { type: "object", properties: {} } }, + }) as Record; + expect(closed.additionalProperties).toBe(false); + expect(closed.properties.nested.additionalProperties).toBe(false); + }); + + it("does not override an explicit additionalProperties", () => { + const closed = closeObjectSchemas({ + type: "object", + additionalProperties: true, + }) as Record; + expect(closed.additionalProperties).toBe(true); + }); +}); + +describe("emitRpcSchema", () => { + it("compiles params/result types and emits the RpcSchema tuple", async () => { + const source = await emitRpcSchema( + { + spec: "rpc.fixture", + schemaTypeName: "FixtureRpcSchema", + methods: [ + { method: "fixture_getThings", exportBaseName: "FixtureGetThings" }, + ], + }, + spec, + ); + // single param → plain Params name; required → no optional marker + expect(source).toContain("export interface FixtureGetThingsParams"); + // raw j2ts output (prettier formatting happens at emit time) + expect(source).toContain(`kind?: ("a" | "b")`); + // oneOf(string-titled, object) result compiles to a union + expect(source).toContain("export type FixtureGetThingsResult"); + // no index signatures leak from open object schemas + expect(source).not.toContain("[k: string]"); + expect(source).toContain(`Method: "fixture_getThings";`); + expect(source).toContain( + "Parameters: [thingParams: FixtureGetThingsParams];", + ); + expect(source).toContain("ReturnType: FixtureGetThingsResult;"); + }); +}); diff --git a/packages/api-codegen/tests/schemaEmitter.test.ts b/packages/api-codegen/tests/schemaEmitter.test.ts new file mode 100644 index 0000000000..45d15daa93 --- /dev/null +++ b/packages/api-codegen/tests/schemaEmitter.test.ts @@ -0,0 +1,59 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { emitRestSchema } from "../src/rest/schemaEmitter.js"; + +const spec = JSON.parse( + readFileSync(resolve(__dirname, "fixtures/rest.fixture.json"), "utf8"), +); + +describe("emitRestSchema", () => { + it("emits Body/Response aliases and a schema tuple for a POST operation", () => { + const source = emitRestSchema( + { + spec: "rest.fixture", + schemaTypeName: "FooRestSchema", + pathRules: { stripApiKeySegment: true }, + operations: [ + { operationId: "create-foo", exportBaseName: "CreateFoo" }, + ], + }, + spec, + ); + expect(source).toContain( + `import type { operations } from "./rest.fixture.types.js";`, + ); + expect(source).toContain( + `export type CreateFooBody = NonNullable<\n operations["create-foo"]["requestBody"]\n>["content"]["application/json"];`, + ); + expect(source).toContain(`Route: "foo/create";`); + expect(source).toContain(`Method: "POST";`); + expect(source).toContain(`Body: CreateFooBody;`); + expect(source).toContain( + `FooRestSchema extends RestRequestSchema ? true : never;`, + ); + }); + + it("emits a query type and bodyless tuple entry for a GET operation", () => { + const source = emitRestSchema( + { + spec: "rest.fixture", + schemaTypeName: "BarRestSchema", + pathRules: { stripApiKeySegment: true, stripPrefix: "/v3" }, + operations: [ + { + operationId: "getBar-v3", + exportBaseName: "GetBar", + emitQueryType: true, + }, + ], + }, + spec, + ); + expect(source).toContain( + `export type GetBarQuery = NonNullable<\n operations["getBar-v3"]["parameters"]["query"]\n>;`, + ); + expect(source).toContain(`Route: "getBar";`); + expect(source).toContain(`Body?: undefined;`); + }); +}); diff --git a/packages/api-codegen/tsconfig.json b/packages/api-codegen/tsconfig.json new file mode 100644 index 0000000000..e171f89cba --- /dev/null +++ b/packages/api-codegen/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "typescript-template/base.json", + "compilerOptions": { + "types": ["node"] + }, + "include": ["src", "tests"] +} diff --git a/packages/api-codegen/vitest.config.ts b/packages/api-codegen/vitest.config.ts new file mode 100644 index 0000000000..2626bc221c --- /dev/null +++ b/packages/api-codegen/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineProject } from "vitest/config"; + +export default defineProject({ + test: { + name: "alchemy/api-codegen", + globals: true, + include: ["tests/**/*.test.ts"], + setupFiles: [], + globalSetup: undefined, + testTimeout: 30_000, + hookTimeout: 30_000, + }, +}); diff --git a/packages/data-apis/README.md b/packages/data-apis/README.md index dfe23b2f97..cd7b4c5a69 100644 --- a/packages/data-apis/README.md +++ b/packages/data-apis/README.md @@ -7,11 +7,11 @@ before scaling to the full v1 surface (Portfolio, Prices, NFT, Token, Transfers) One method per seam, not full coverage: -| Method | Channel | What it demonstrates | -| --- | --- | --- | +| Method | Channel | What it demonstrates | +| ------------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | `portfolio.getTokensByAddress` | REST → global `api.g.alchemy.com/data/v1` | Multi-network request bodies via `AlchemyRestClient`; networks are payload, the client's chain is not involved | -| `nft.getNftsForOwner` | REST → `{network}.g.alchemy.com/nft/v3` | Network-scoped endpoint resolution with per-request `network` override falling back to the client default | -| `transfers.getAssetTransfers` | JSON-RPC → `AlchemyTransport` | Plain viem action; network override derives a transport instance from `client.transport.config` | +| `nft.getNftsForOwner` | REST → `{network}.g.alchemy.com/nft/v3` | Network-scoped endpoint resolution with per-request `network` override falling back to the client default | +| `transfers.getAssetTransfers` | JSON-RPC → `AlchemyTransport` | Plain viem action; network override derives a transport instance from `client.transport.config` | Plus the two entry points: @@ -20,8 +20,10 @@ Plus the two entry points: const data = createDataClient({ apiKey, network: "eth-mainnet" }); // Developers already on a viem client with an Alchemy transport -const client = createClient({ chain: mainnet, transport: alchemyTransport({ apiKey }) }) - .extend(dataActions); +const client = createClient({ + chain: mainnet, + transport: alchemyTransport({ apiKey }), +}).extend(dataActions); ``` Network inputs accept all three formats everywhere, resolved by @@ -30,6 +32,16 @@ Network inputs accept all three formats everywhere, resolved by chain-ID mapping is derived from the existing daikon-generated `ALCHEMY_RPC_MAPPING` — no second registry. +## Generated internals + +Param/result types are generated from the docs repo's bundled OpenAPI/OpenRPC +specs by `@alchemy/api-codegen` (see that package's README for the +snapshot/generate pipeline). `src/generated/` is committed, machine-owned, and +never re-exported directly: the public types in `src/types.ts` are +hand-reviewed aliases, and `codegen.manifest.ts` maps spec operations to the +generated surface — referencing a renamed/removed spec operation fails +`pnpm generate` loudly. + ## Companion changes in @alchemy/common - `networks/networkRegistry.ts`: `resolveNetwork` + network types (slug map @@ -38,7 +50,6 @@ chain-ID mapping is derived from the existing daikon-generated ## Deliberately out of scope (tracked in the data SDK scope plan) -- Codegen from docs OpenAPI/OpenRPC specs (types here are hand-written) - Rest client hardening: retries, timeouts, request-id, first-class query params - Pagination iterators, error normalization, the SDK manifest, remaining methods - ws-tools generator change to emit `{ slug, chainId, caip2 }` entries + diff --git a/packages/data-apis/codegen.manifest.ts b/packages/data-apis/codegen.manifest.ts new file mode 100644 index 0000000000..ce041ddaaf --- /dev/null +++ b/packages/data-apis/codegen.manifest.ts @@ -0,0 +1,50 @@ +import type { CodegenManifest } from "@alchemy/api-codegen"; + +// The hand-maintained overlay mapping spec operations to this package's +// generated internals (src/generated/). Validated against the committed spec +// snapshots on every `pnpm generate` — referencing a renamed/removed spec +// operation is a hard error. Public naming and pagination semantics stay +// human decisions; everything type-shaped is generated. +export default { + rest: [ + { + spec: "portfolio", + schemaTypeName: "PortfolioRestSchema", + // Spec path: POST /{apiKey}/assets/tokens/by-address. Runtime auth is + // header-based against https://api.g.alchemy.com/data/v1. + pathRules: { stripApiKeySegment: true }, + operations: [ + { + operationId: "get-tokens-by-address", + exportBaseName: "GetTokensByAddress", + }, + ], + }, + { + spec: "nft", + schemaTypeName: "NftRestSchema", + // Spec path: GET /v3/{apiKey}/getNFTsForOwner. Runtime base URL is + // https://{network}.g.alchemy.com/nft/v3, so /v3 is stripped too. + pathRules: { stripApiKeySegment: true, stripPrefix: "/v3" }, + operations: [ + { + operationId: "getNFTsForOwner-v3", + exportBaseName: "GetNftsForOwner", + emitQueryType: true, + }, + ], + }, + ], + rpc: [ + { + spec: "transfers", + schemaTypeName: "TransfersRpcSchema", + methods: [ + { + method: "alchemy_getAssetTransfers", + exportBaseName: "AlchemyGetAssetTransfers", + }, + ], + }, + ], +} satisfies CodegenManifest; diff --git a/packages/data-apis/package.json b/packages/data-apis/package.json index 0b8bf7f2c2..4cba0ec8cc 100644 --- a/packages/data-apis/package.json +++ b/packages/data-apis/package.json @@ -35,11 +35,13 @@ "build:esm": "tsc --project tsconfig.build.json --outDir ./dist/esm", "build:types": "tsc --project tsconfig.build.json --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", "clean": "rm -rf ./dist", + "generate": "tsx ../api-codegen/src/cli.ts --target data-apis", "test": "vitest", "test:run": "vitest run", "smoke-test": "tsx scripts/smoke-test.ts" }, "devDependencies": { + "@alchemy/api-codegen": "workspace:*", "typescript-template": "workspace:*", "viem": "^2.45.0" }, diff --git a/packages/data-apis/scripts/smoke-test.ts b/packages/data-apis/scripts/smoke-test.ts index 3f80447693..eb8fc81e75 100644 --- a/packages/data-apis/scripts/smoke-test.ts +++ b/packages/data-apis/scripts/smoke-test.ts @@ -83,7 +83,7 @@ await test("single network (slug)", async () => { Array.isArray(result.data.tokens), "result.data.tokens should be an array", ); - assert(result.data.tokens.length > 0, "expected at least one token"); + assert((result.data.tokens ?? []).length > 0, "expected at least one token"); }); await test("multi-network: viem Chain + slug + CAIP-2 in one call", async () => { @@ -108,7 +108,7 @@ await test("multi-network: viem Chain + slug + CAIP-2 in one call", async () => ); // expect at least some native ETH/MATIC results across 3 networks assert( - result.data.tokens.length > 0, + (result.data.tokens ?? []).length > 0, "expected tokens across multiple networks", ); }); @@ -121,7 +121,10 @@ await test("uses client-level network (slug)", async () => { typeof result.totalCount === "number", "totalCount should be a number", ); - assert(result.totalCount > 0, "Vitalik should own some NFTs on mainnet"); + assert( + (result.totalCount ?? 0) > 0, + "Vitalik should own some NFTs on mainnet", + ); }); await test("uses client-level network (viem Chain)", async () => { @@ -177,7 +180,7 @@ await test("pageSize param", async () => { pageSize: 2, withMetadata: false, }); - assert(result.ownedNfts.length <= 2, "should respect pageSize=2"); + assert((result.ownedNfts ?? []).length <= 2, "should respect pageSize=2"); }); console.log("\n\x1b[1mtransfers.getAssetTransfers\x1b[0m"); @@ -191,7 +194,7 @@ await test("uses client-level network (slug)", async () => { withMetadata: true, }); assert(Array.isArray(result.transfers), "transfers should be an array"); - assert(result.transfers.length > 0, "expected at least one transfer"); + assert((result.transfers ?? []).length > 0, "expected at least one transfer"); }); await test("uses client-level network (viem Chain)", async () => { diff --git a/packages/data-apis/src/generated/rest/nft.schema.ts b/packages/data-apis/src/generated/rest/nft.schema.ts new file mode 100644 index 0000000000..c543c83c66 --- /dev/null +++ b/packages/data-apis/src/generated/rest/nft.schema.ts @@ -0,0 +1,36 @@ +/* eslint-disable -- machine-generated file */ +// AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. +// Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) +// is recorded in packages/api-codegen/specs/specs.lock.json. + +import type { RestRequestSchema } from "@alchemy/common"; +import type { operations } from "./nft.types.js"; + +/** 200 response for getNFTsForOwner-v3. */ +export type GetNftsForOwnerResponse = + operations["getNFTsForOwner-v3"]["responses"]["200"]["content"]["application/json"]; + +/** + * Query params for getNFTsForOwner-v3. Sent via the URL at runtime; + * RestRequestSchema has no query channel, so this is exposed as a + * standalone type for the SDK's params layer. + */ +export type GetNftsForOwnerQuery = NonNullable< + operations["getNFTsForOwner-v3"]["parameters"]["query"] +>; + +/** RestRequestSchema entries for the nft REST API. */ +export type NftRestSchema = readonly [ + { + /** GET /v3/{apiKey}/getNFTsForOwner (operationId: getNFTsForOwner-v3) */ + Route: "getNFTsForOwner"; + Method: "GET"; + Body?: undefined; + Response: GetNftsForOwnerResponse; + }, +]; + +/** Compile-time guard that the emitted tuple satisfies the shared constraint. */ +export type _AssertNftRestSchema = NftRestSchema extends RestRequestSchema + ? true + : never; diff --git a/packages/data-apis/src/generated/rest/nft.types.ts b/packages/data-apis/src/generated/rest/nft.types.ts new file mode 100644 index 0000000000..523cc926b4 --- /dev/null +++ b/packages/data-apis/src/generated/rest/nft.types.ts @@ -0,0 +1,5214 @@ +/* eslint-disable -- machine-generated file */ +// AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. +// Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) +// is recorded in packages/api-codegen/specs/specs.lock.json. + +export interface paths { + "/v3/{apiKey}/getNFTsForOwner": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * NFTs By Owner + * @description getNFTsForOwner - Retrieves all NFTs currently owned by a specified address. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + get: operations["getNFTsForOwner-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getNFTsForContract": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * NFTs By Contract + * @description getNFTsForContract - Retrieves all NFTs associated with a specific NFT contract. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + get: operations["getNFTsForContract-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getNFTsForCollection": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * NFTs By Collection + * @description getNFTsForCollection - Retrieves all NFTs associated with a specific NFT collection. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + get: operations["getNFTsForCollection-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getNFTMetadata": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * NFT Metadata By Token ID + * @description getNFTMetadata - Retrieves the metadata associated with a specific NFT. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + get: operations["getNFTMetadata-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getNFTMetadataBatch": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * NFT Metadata By Token ID [Batch] + * @description getNFTMetadataBatch - Retrieves metadata for up to 100 specified NFT contracts in a single request. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + post: operations["getNFTMetadataBatch-v3"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getContractMetadata": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Contract Metadata By Address + * @description getContractMetadata - Retrieves high-level collection or contract-level information for an NFT. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + get: operations["getContractMetadata-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getCollectionMetadata": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Collection Metadata By Slug + * @description getCollectionMetadata - Retrieves high-level collection or contract-level information for an NFT collection. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + get: operations["getCollectionMetadata-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/invalidateContract": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Invalidate Contract Cache + * @description Marks all cached tokens for the specified contract as stale, ensuring the next query fetches live data instead of cached data. + * + * Please note that this endpoint is only available on **Ethereum**, **Polygon**, **Arbitrum**, **Optimism** & **Base** networks. + */ + get: operations["invalidateContract-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getContractMetadataBatch": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Contract Metadata By Address [Batch] + * @description getContractMetadataBatch - Retrieves metadata for a list of specified contract addresses in a single request. + */ + post: operations["getContractMetadataBatch-v3"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getOwnersForNFT": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Owners By NFT + * @description getOwnersForNFT - Retrieves the owner(s) for a specific token. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + get: operations["getOwnersForNFT-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getOwnersForContract": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Owners By Contract + * @description getOwnersForContract - Retrieves all owners associated with a specific NFT contract. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + get: operations["getOwnersForContract-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getSpamContracts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Spam Contracts + * @description Returns a list of all spam contracts marked by Alchemy. + * + * Please note that this API endpoint is only available to paid tier customers. Upgrade your account [here](https://dashboard.alchemy.com/account). + * + * Spam NFT functionality is available on Mainnet for the following chains: Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast. More to come soon! + */ + get: operations["getSpamContracts-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/isSpamContract": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Is Spam Contract + * @description Determines whether a specific contract is marked as spam by Alchemy. + * + * Please note that this API endpoint is only available to paid tier customers. Upgrade your account [here](https://dashboard.alchemy.com/account). + * + * Spam NFT functionality is available on Mainnet for the following chains: Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast. More to come soon! + */ + get: operations["isSpamContract-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/isAirdropNFT": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Is Airdrop NFT + * @description Determines whether a specific token is marked as an airdrop. Airdrops are defined as NFTs minted to a user address in a transaction sent by a different address. + * + * Please note that this endpoint is only available on Ethereum (mainnet only) & Polygon (mainnet, amoy & mumbai). + */ + get: operations["isAirdropNFT-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/summarizeNFTAttributes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Attributes Summary By Contract + * @description Generates a summary of attribute prevalence for a specific NFT collection. + * + * Please note that this endpoint is only available on Ethereum (mainnet) & Polygon (mainnet & mumbai). + */ + get: operations["summarizeNFTAttributes-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getFloorPrice": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Floor Prices By Slug + * @description Retrieves the floor prices of an NFT collection across different marketplaces. + * + * Please note that this endpoint is only available on Ethereum mainnet for Opensea & Looksrare marketplaces. + */ + get: operations["getFloorPrice-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/searchContractMetadata": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Search Contract Metadata + * @description Searches for a keyword across metadata of all ERC-721 and ERC-1155 smart contracts. + * + * This endpoint is currently in BETA. If you have any questions or feedback, please contact us at support@alchemy.com or open a ticket in the dashboard. + * + * Please note that this endpoint is only available on **Ethereum**, **Polygon**, **Arbitrum**, **Optimism** & **Base** networks. + */ + get: operations["searchContractMetadata-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/isHolderOfContract": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Is Holder Of Contract + * @description isHolderOfContract - Determines whether a specific wallet holds any NFT from a given contract. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + get: operations["isHolderOfContract-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/computeRarity": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Attribute Rarity By NFT + * @description Calculates the rarity of each attribute within an NFT. + * + * Please note that this endpoint is only available on Ethereum (mainnet) & Polygon (mainnet & mumbai). + */ + get: operations["computeRarity-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getNFTSales": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * NFT Sales + * @description Retrieves NFT sales that have occurred through on-chain marketplaces. + * + * Please note that this endpoint is only available on Ethereum (Seaport, Wyvern, X2Y2, Blur, LooksRare, Cryptopunks), Polygon (Seaport) & Optimism (Seaport) mainnets. + */ + get: operations["getNFTSales-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getContractsForOwner": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Contracts By Owner + * @description getContractsForOwner - Retrieves all NFT contracts held by a specified owner address. + */ + get: operations["getContractsForOwner-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/getCollectionsForOwner": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Collections By Owner + * @description Retrieves all NFT collections held by a specified owner address. + * + * This endpoint is only supported on Ethereum. Use `getContractsForOwner` for support across all other chains we support! + */ + get: operations["getCollectionsForOwner-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/reportSpam": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Report Spam Address + * @description Reports a specific address to the API if it is suspected to be spam. + * + * Spam NFT functionality is available on Mainnet for the following chains: Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast. More to come soon! + */ + get: operations["reportSpam-v3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v3/{apiKey}/refreshNftMetadata": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Refresh NFT Metadata + * @description Submits a request for Alchemy to refresh the cached metadata of a specific NFT token. + * + * Please note that this endpoint is only supported on Ethereum (Mainnet & Sepolia), Polygon (Mainnet, Mumbai & Amoy), Arbitrum One (mainnet), Optimism (mainnet) & Base (mainnet). For other chains, you could use the `getNFTMetadata` endpoint with the `refreshCache` parameter set to `true` to refresh the metadata! + */ + post: operations["refreshNftMetadata-v3"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/getNFTs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * getNFTs + * @description Gets all NFTs currently owned by a given address. + */ + get: operations["getNFTs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/getNFTMetadata": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * getNFTMetadata + * @description Gets the metadata associated with a given NFT. + */ + get: operations["getNFTMetadata"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/getNFTMetadataBatch": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * getNFTMetadataBatch + * @description Gets the metadata associated with up to 100 given NFT contracts. + */ + post: operations["getNFTMetadataBatch"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/getContractMetadata": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * getContractMetadata + * @description Queries NFT high-level collection/contract level information. + */ + get: operations["getContractMetadata"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/getContractMetadataBatch": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * getContractMetadataBatch + * @description Gets the metadata associated with the given list of contract addresses + */ + post: operations["getContractMetadataBatch"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/getNFTsForCollection": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * getNFTsForCollection + * @description Gets all NFTs for a given NFT contract. + */ + get: operations["getNFTsForCollection"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/getOwnersForToken": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * getOwnersForToken + * @description Get the owner(s) for a token. + */ + get: operations["getOwnersForToken"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/getOwnersForCollection": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * getOwnersForCollection + * @description Gets all owners for a given NFT contract. + */ + get: operations["getOwnersForCollection"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/getSpamContracts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * getSpamContracts + * @description Returns a list of all spam contracts marked by Alchemy. + */ + get: operations["getSpamContracts"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/isSpamContract": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * isSpamContract + * @description Returns whether a contract is marked as spam or not by Alchemy. + */ + get: operations["isSpamContract"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/isAirdrop": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * isAirdrop + * @description Returns whether a token is marked as an airdrop or not. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. + */ + get: operations["isAirdrop"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/invalidateContract": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * invalidateContract + * @description Marks all cached tokens for the particular contract as stale. So the next time the endpoint is queried it fetches live data instead of fetching from cache. + */ + get: operations["invalidateContract"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/getFloorPrice": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * getFloorPrice + * @description Returns the floor prices of a NFT collection by marketplace. + */ + get: operations["getFloorPrice"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/computeRarity": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * computeRarity + * @description Computes the rarity of each attribute of an NFT. + */ + get: operations["computeRarity"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/searchContractMetadata": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * searchContractMetadata + * @description Search for a keyword across metadata of all ERC-721 and ERC-1155 smart contracts + */ + get: operations["searchContractMetadata"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/summarizeNFTAttributes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * summarizeNFTAttributes + * @description Generate a summary of attribute prevalence for an NFT collection. + */ + get: operations["summarizeNFTAttributes"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/isHolderOfCollection": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * isHolderOfCollection + * @description Checks whether a wallet holds a NFT in a given collection + */ + get: operations["isHolderOfCollection"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/getNFTSales": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * getNFTSales + * @description Gets NFT sales that have happened through on-chain marketplaces + */ + get: operations["getNFTSales"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/getContractsForOwner": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * getContractsForOwner + * @description Gets all NFT contracts held by an owner address. + */ + get: operations["getContractsForOwner"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v2/{apiKey}/reportSpam": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * reportSpam + * @description Report a particular address to our APIs if you think it is spam + */ + get: operations["reportSpam"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** @description Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract */ + rawv3: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + }; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + }; + id: { + /** @default 44 */ + tokenId: string; + tokenMetadata?: { + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + }; + }; + idV3: { + /** @default 44 */ + tokenId: string; + tokenMetadata?: { + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + }; + }; + tokenUri: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + }; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - The image URL that appears alongside the asset image on NFT platforms. */ + external_url?: string; + /** @description String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal. */ + background_color?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }[]; + }; + /** @description The object that represents a smart contract and has all data corresponding to that contract */ + ownedContract: { + /** @description Address of the held contract */ + address?: string; + /** @description Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens. */ + totalBalance?: number; + /** @description Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens. */ + numDistinctTokensOwned?: number; + isSpam?: boolean; + /** @description One of the tokens from this contract held by the owner. */ + tokenId?: string; + /** @description The name of the contract, i.e. "Bored Ape Yacht Club". */ + name?: string; + /** @description The title of the token held by the owner i.e. "Something #22". */ + title?: string; + /** @description The symbol of the contract, i.e. BAYC. */ + symbol?: string; + /** @description The NFT standard used by the contract, i.e. ERC721 or ERC1155. */ + tokenType?: string; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }[]; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + opensea?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + /** @description The object that represents a smart contract and has all data corresponding to that contract */ + ownedContractv3: { + /** @default 0xe785E82358879F061BC3dcAC6f0444462D4b5330 */ + address: string; + /** @description The name of the contract, i.e. "Bored Ape Yacht Club". */ + name?: string; + /** @description The symbol of the contract, i.e. BAYC. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + tokenType?: string; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens. */ + totalBalance?: number; + /** @description Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens. */ + numDistinctTokensOwned?: number; + /** @description `True` if the contract is detected as spam contract. `False` if it is not spam or has not been evaluated by our system yet */ + isSpam?: boolean; + /** @description Details of the display NFT for this contract. This NFT and its image can be used to represent the contract when displaying info about it. */ + displayNft?: { + /** @description One of the tokens from this contract held by the owner. */ + tokenId?: string; + /** @description The title of the token held by the owner i.e. "Something #22". */ + name?: string; + }; + /** @description Details of the image corresponding to this contract */ + image?: { + /** @description The Url of the image stored in Alchemy cache */ + cachedUrl?: string; + /** @description The Url that has the thumbnail version of the NFT */ + thumbnailUrl?: string; + /** @description The Url that has the NFT image in png */ + pngUrl?: string; + /** @description The Url of the image stored in Alchemy cache */ + contentType?: string; + /** @description The size of the media asset in bytes. */ + size?: number; + /** @description The original Url of the image coming straight from the smart contract */ + originalUrl?: string; + }; + }; + /** @description Metadata for an NFT collection held by an owner address. Includes general metadata about the collection, as well as information specific to the owner such as the total balance and the token ID of a random NFT for display purposes. */ + ownedCollectionv3: { + /** @description The name of the collection, i.e. "Bored Ape Yacht Club". */ + name?: string; + /** @description The human-readable string used to identify the collection on OpenSea. */ + slug?: string; + /** @description Floor price data for the collection */ + floorPrice?: { + /** @description The marketplace the floor price is on */ + marketplace?: string; + /** @description Floor price of the collection on the marketplace */ + floorPrice?: number; + /** @description The currency of the floor price */ + priceCurrency?: string; + }; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description Contract-level data for a collection, such as contract type, name, and symbol. */ + contract?: { + /** @description Address of the contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + }; + /** @description Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens. */ + totalBalance?: number; + /** @description Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens. */ + numDistinctTokensOwned?: number; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description Details of the display NFT for this contract. This NFT and its image can be used to represent the contract when displaying info about it. */ + displayNft?: { + /** @description One of the tokens from this contract held by the owner. */ + tokenId?: string; + /** @description The title of the token held by the owner i.e. "Something #22". */ + name?: string; + }; + /** @description Details of the image corresponding to this contract */ + image?: { + /** @description The Url of the image stored in Alchemy cache */ + cachedUrl?: string; + /** @description The Url that has the thumbnail version of the NFT */ + thumbnailUrl?: string; + /** @description The Url that has the NFT image in png */ + pngUrl?: string; + /** @description The Url of the image stored in Alchemy cache */ + contentType?: string; + /** @description The size of the media asset in bytes. */ + size?: number; + /** @description The original Url of the image coming straight from the smart contract */ + originalUrl?: string; + }; + }; + /** @description Contract-level data for a collection, such as contract type, name, and symbol. */ + ownedCollectionContract: { + /** @description Address of the contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + }; + media: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }[]; + /** @description The object that represents an NFT and has all data corresponding to that NFT */ + ownedNFT: { + /** @description Object - Contract for returned NFT */ + contract?: { + /** @description String - Address of NFT contract. */ + address?: string; + }; + id?: { + /** @default 44 */ + tokenId: string; + tokenMetadata?: { + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + }; + }; + /** @description String - Token balance */ + balance?: string; + /** @description String - Name of the NFT asset. */ + title?: string; + /** @description String - Brief human-readable description */ + description?: string; + tokenUri?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + }; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - The image URL that appears alongside the asset image on NFT platforms. */ + external_url?: string; + /** @description String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal. */ + background_color?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }[]; + }; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + contractMetadata?: { + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + opensea?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + /** @description Information about whether and why a contract was marked as spam. */ + spamInfo?: { + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + /** @description Only present if the request specified `orderBy=transferTime`. */ + acquiredAt?: { + /** @description Block timestamp of the block where the NFT was most recently acquired. */ + blockTimestamp?: string; + /** @description Block number of the block where the NFT was most recently acquired. */ + blockNumber?: string; + }; + }; + /** @description The object that represents an NFT and has all data corresponding to that NFT */ + ownedNFTv3: { + /** @description The contract object that has details of a contract */ + contract?: { + /** @description Address of the held contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + /** @default 44 */ + tokenId: string; + tokenType?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Brief human-readable description */ + description?: string; + /** @description Details of the image corresponding to this contract */ + image?: { + /** @description The Url of the image stored in Alchemy cache */ + cachedUrl?: string; + /** @description The Url that has the thumbnail version of the NFT */ + thumbnailUrl?: string; + /** @description The Url that has the NFT image in png */ + pngUrl?: string; + /** @description The Url of the image stored in Alchemy cache */ + contentType?: string; + /** @description The size of the media asset in bytes. */ + size?: number; + /** @description The original Url of the image coming straight from the smart contract */ + originalUrl?: string; + }; + /** @description Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract */ + raw?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + }; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + }; + /** @description The collection object that has details of a collection */ + collection?: { + /** @description String - Collection name */ + name?: string; + /** @description String - OpenSea collection slug */ + slug?: string; + /** @description String - URL for the external site of the collection */ + externalUrl?: string; + /** @description String - Banner image URL for the collection */ + bannerImageUrl?: string; + }; + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + /** @description Only present if the request specified `orderBy=transferTime`. */ + acquiredAt?: { + /** @description Block timestamp of the block where the NFT was most recently acquired. */ + blockTimestamp?: string; + /** @description Block number of the block where the NFT was most recently acquired. */ + blockNumber?: string; + }; + animation?: { + cachedUrl?: string; + contentType?: string; + size?: number; + orginalUrl?: string; + }; + mint?: { + /** @description Address that minted the NFT */ + mintAddress?: string; + /** @description Block number when the NFT was minted */ + blockNumber?: number; + /** @description Timestamp when the NFT was minted */ + timestamp?: string; + /** @description Transaction hash of the mint transaction */ + transactionHash?: string; + }; + /** @description List of all addresses that own the given NFT. */ + owners?: string[]; + }; + contractMetadata: { + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + opensea?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + contractMetadatav3: { + /** @description String - Contract address for the queried NFT collection */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + collectionMetadatav3: { + /** @description String - Name of the queried NFT Collection */ + name?: string; + /** @description The human-readable string used to identify the collection on OpenSea. */ + slug?: string; + /** @description Floor price data for the collection */ + floorPrice?: { + /** @description The marketplace the floor price is on */ + marketplace?: string; + /** @description Floor price of the collection on the marketplace */ + floorPrice?: number; + /** @description The currency of the floor price */ + priceCurrency?: string; + }; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + }; + /** @description The contract object that has details of a contract */ + contractv3: { + /** @description Address of the held contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + }; + responses: never; + parameters: { + apiKey: string; + /** @description String - Address for NFT owner (can be in ENS format for Eth Mainnet). */ + owner: string; + /** @description String - Wallet address to check for contract ownership. */ + wallet: string; + /** @description String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results. */ + pageKey: string; + /** @description Number of NFTs to be returned per page. Defaults to 100. Max is 100. */ + pageSize: number; + /** @description Number of owners to be returned per page. */ + getOwnersForTokenPageSize: number; + /** @description Array of contract addresses to filter the responses with. Max limit 45 contracts. */ + contractAddresses: string[]; + /** @description Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. */ + withMetadata: boolean; + /** + * @description Array of filters (as ENUMS) that will be applied to the query. NFTs that match one or more of these filters will be excluded from the response. May not be used in conjunction with includeFilters[]. Filter Options: + * - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**. + * - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only. + * - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts) + */ + excludeFilters: ("SPAM" | "AIRDROPS")[]; + /** @description String - any valid blockchain address for NFT collections, contracts, mints, etc. */ + address: string; + /** @description String - any valid blockchain address for NFT collections, contracts, mints, etc. */ + addressRequired: string; + /** + * @description Array of filters (as ENUMS) that will be applied to the query. Only NFTs that match one or more of these filters will be included in the response. May not be used in conjunction with excludeFilters[]. Filter Options: + * - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**. + * - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only. + * - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts) + */ + includeFilters: ("SPAM" | "AIRDROPS")[]; + /** + * @description Enum - the confidence level at which to filter spam at. + * + * Confidence Levels: + * - VERY_HIGH + * - HIGH + * - MEDIUM + * - LOW + * + * The confidence level set means that any spam that is at that confidence level or higher will be filtered out. For example, if the confidence level is HIGH, contracts that we have HIGH or VERY_HIGH confidence in being spam will be filtered out from the response. + * Defaults to VERY_HIGH for Ethereum Mainnet and MEDIUM for Matic Mainnet. + * + * **Please note that this filter is only available on paid tiers. Upgrade your account [here](https://dashboard.alchemy.com/settings/billing/).** + */ + spamConfidenceLevel: "VERY_HIGH" | "HIGH" | "MEDIUM" | "LOW"; + /** @description String - OpenSea slug for the NFT collection. */ + collectionSlug: string; + /** @description String - OpenSea slug for the NFT collection. */ + collectionSlugRequired: string; + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddressRequired: string; + /** @description String - The ID of the token. Can be in hex or decimal format. */ + tokenId: string; + /** @description String - The ID of the token in decimal format. */ + tokenIdV3: string; + /** @description String - The ID of the token. Can be in hex or decimal format. */ + tokenIdForNFTSales: string; + /** @description String - 'ERC721' or 'ERC1155'; specifies type of token to query for. API requests will perform faster if this is specified. */ + tokenType: string; + /** @description String - A tokenID offset used for pagination. Can be a hex string, or a decimal. Users can specify the offset themselves to start from a custom offset, or to fetch multiple token ranges in parallel. */ + startToken: string; + /** @description Integer - Sets the total number of NFTs returned in the response. Defaults to 100. */ + limit: number; + /** @description No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0. */ + tokenUriTimeoutInMs: number; + /** @description Boolean - If set to `true` the query will include the token balances per token id for each owner. `false` by default. */ + withTokenBalances: boolean; + /** @description Defaults to false for faster response times. If true will refresh metadata for given token. If false will check the cache and use it or refresh if cache doesn't exist. */ + refreshCache: boolean; + /** @description String - The point in time or block number (in hex or decimal) to fetch collection ownership information for. */ + block: string; + /** @description String - The block number to start fetching NFT sales data from. Allowed values are decimal and hex integers, and "latest". Defaults to "0". */ + fromBlock: string; + /** @description Enum - Whether to return the results ascending from startBlock or descending from startBlock. Defaults to descending (false). */ + order: "asc" | "desc"; + /** + * @description Enum - ordering scheme to use for ordering NFTs in the response. If unspecified, NFTs will be ordered by contract address and token ID. + * - transferTime: NFTs will be ordered by the time they were transferred into the wallet, with newest NFTs first. Note: This ordering is supported on Ethereum Mainnet, Optimism Mainnet, Polygon Mainnet, Base Mainnet, Arbitrum One, Polygon Amoy, Base Sepolia, Arbitrum Sepolia, Ethereum Sepolia, and Optimism Sepolia. + */ + orderBy: "transferTime"; + /** @description Enum - The name of the NFT marketplace to filter sales by. The endpoint currently supports "seaport", "wyvern", "looksrare", "x2y2", "blur", and "cryptopunks". Defaults to returning sales from all supported marketplaces. */ + marketplace: + | "seaport" + | "looksrare" + | "x2y2" + | "wyvern" + | "blur" + | "cryptopunks"; + /** @description String - The address of the NFT buyer to filter sales by. Defaults to returning sales involving any buyer. */ + buyerAddress: string; + /** @description String - The address of the NFT seller to filter sales by. Defaults to returning sales involving any seller. */ + sellerAddress: string; + /** @description Enum - Filter by whether the buyer or seller was the taker in the NFT trade. Allowed filter values are "BUYER" and "SELLER". Defaults to returning both buyer and seller taker trades. */ + taker: "BUYER" | "SELLER"; + /** @description String - The search string that you want to search for in contract metadata */ + query: string; + }; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + "getNFTsForOwner-v3": { + parameters: { + query: { + /** @description String - Address for NFT owner (can be in ENS format for Eth Mainnet). */ + owner: string; + /** @description Array of contract addresses to filter the responses with. Max limit 45 contracts. */ + "contractAddresses[]"?: string[]; + /** @description Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. */ + withMetadata?: boolean; + /** + * @description Enum - ordering scheme to use for ordering NFTs in the response. If unspecified, NFTs will be ordered by contract address and token ID. + * - transferTime: NFTs will be ordered by the time they were transferred into the wallet, with newest NFTs first. Note: This ordering is supported on Ethereum Mainnet, Optimism Mainnet, Polygon Mainnet, Base Mainnet, Arbitrum One, Polygon Amoy, Base Sepolia, Arbitrum Sepolia, Ethereum Sepolia, and Optimism Sepolia. + */ + orderBy?: "transferTime"; + /** + * @description Array of filters (as ENUMS) that will be applied to the query. NFTs that match one or more of these filters will be excluded from the response. May not be used in conjunction with includeFilters[]. Filter Options: + * - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**. + * - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only. + * - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts) + */ + "excludeFilters[]"?: ("SPAM" | "AIRDROPS")[]; + /** + * @description Array of filters (as ENUMS) that will be applied to the query. Only NFTs that match one or more of these filters will be included in the response. May not be used in conjunction with excludeFilters[]. Filter Options: + * - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**. + * - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only. + * - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts) + */ + "includeFilters[]"?: ("SPAM" | "AIRDROPS")[]; + /** + * @description Enum - the confidence level at which to filter spam at. + * + * Confidence Levels: + * - VERY_HIGH + * - HIGH + * - MEDIUM + * - LOW + * + * The confidence level set means that any spam that is at that confidence level or higher will be filtered out. For example, if the confidence level is HIGH, contracts that we have HIGH or VERY_HIGH confidence in being spam will be filtered out from the response. + * Defaults to VERY_HIGH for Ethereum Mainnet and MEDIUM for Matic Mainnet. + * + * **Please note that this filter is only available on paid tiers. Upgrade your account [here](https://dashboard.alchemy.com/settings/billing/).** + */ + spamConfidenceLevel?: "VERY_HIGH" | "HIGH" | "MEDIUM" | "LOW"; + /** @description No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0. */ + tokenUriTimeoutInMs?: number; + /** @description String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results. */ + pageKey?: string; + /** @description Number of NFTs to be returned per page. Defaults to 100. Max is 100. */ + pageSize?: number; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns the list of all NFTs owned by the given address and satisfying the given input parameters. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Array of the NFT objects corresponding to the NFTs owned by the owner */ + ownedNfts?: { + /** @description The contract object that has details of a contract */ + contract?: { + /** @description Address of the held contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + /** @default 44 */ + tokenId: string; + tokenType?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Brief human-readable description */ + description?: string; + /** @description Details of the image corresponding to this contract */ + image?: { + /** @description The Url of the image stored in Alchemy cache */ + cachedUrl?: string; + /** @description The Url that has the thumbnail version of the NFT */ + thumbnailUrl?: string; + /** @description The Url that has the NFT image in png */ + pngUrl?: string; + /** @description The Url of the image stored in Alchemy cache */ + contentType?: string; + /** @description The size of the media asset in bytes. */ + size?: number; + /** @description The original Url of the image coming straight from the smart contract */ + originalUrl?: string; + }; + /** @description Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract */ + raw?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + }; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + }; + /** @description The collection object that has details of a collection */ + collection?: { + /** @description String - Collection name */ + name?: string; + /** @description String - OpenSea collection slug */ + slug?: string; + /** @description String - URL for the external site of the collection */ + externalUrl?: string; + /** @description String - Banner image URL for the collection */ + bannerImageUrl?: string; + }; + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + /** @description Only present if the request specified `orderBy=transferTime`. */ + acquiredAt?: { + /** @description Block timestamp of the block where the NFT was most recently acquired. */ + blockTimestamp?: string; + /** @description Block number of the block where the NFT was most recently acquired. */ + blockNumber?: string; + }; + animation?: { + cachedUrl?: string; + contentType?: string; + size?: number; + orginalUrl?: string; + }; + mint?: { + /** @description Address that minted the NFT */ + mintAddress?: string; + /** @description Block number when the NFT was minted */ + blockNumber?: number; + /** @description Timestamp when the NFT was minted */ + timestamp?: string; + /** @description Transaction hash of the mint transaction */ + transactionHash?: string; + }; + /** @description List of all addresses that own the given NFT. */ + owners?: string[]; + }[]; + /** @description Integer - Total number of NFTs (distinct `tokenIds`) owned by the given address. */ + totalCount?: number; + pageKey?: string; + /** @description Block Information of the block as of which the corresponding data is valid */ + validAt?: { + /** @description The block number above information is valid as of */ + blockNumber?: number; + /** @description The block hash above information is valid as of */ + blockHash?: string; + /** @description The block timestamp above information is valid as of */ + blockTimestamp?: string; + }; + }; + }; + }; + }; + }; + "getNFTsForContract-v3": { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + /** @description Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. */ + withMetadata?: boolean; + /** @description String - A tokenID offset used for pagination. Can be a hex string, or a decimal. Users can specify the offset themselves to start from a custom offset, or to fetch multiple token ranges in parallel. */ + startToken?: string; + /** @description Integer - Sets the total number of NFTs returned in the response. Defaults to 100. */ + limit?: number; + /** @description No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0. */ + tokenUriTimeoutInMs?: number; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns a list of NFTs associated with the specified contract address. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of objects that represent NFTs stored under the queried contract address. */ + nfts?: { + /** @description The contract object that has details of a contract */ + contract?: { + /** @description Address of the held contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + /** @default 44 */ + tokenId: string; + tokenType?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Brief human-readable description */ + description?: string; + /** @description Details of the image corresponding to this contract */ + image?: { + /** @description The Url of the image stored in Alchemy cache */ + cachedUrl?: string; + /** @description The Url that has the thumbnail version of the NFT */ + thumbnailUrl?: string; + /** @description The Url that has the NFT image in png */ + pngUrl?: string; + /** @description The Url of the image stored in Alchemy cache */ + contentType?: string; + /** @description The size of the media asset in bytes. */ + size?: number; + /** @description The original Url of the image coming straight from the smart contract */ + originalUrl?: string; + }; + /** @description Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract */ + raw?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + }; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + }; + /** @description The collection object that has details of a collection */ + collection?: { + /** @description String - Collection name */ + name?: string; + /** @description String - OpenSea collection slug */ + slug?: string; + /** @description String - URL for the external site of the collection */ + externalUrl?: string; + /** @description String - Banner image URL for the collection */ + bannerImageUrl?: string; + }; + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + /** @description Only present if the request specified `orderBy=transferTime`. */ + acquiredAt?: { + /** @description Block timestamp of the block where the NFT was most recently acquired. */ + blockTimestamp?: string; + /** @description Block number of the block where the NFT was most recently acquired. */ + blockNumber?: string; + }; + animation?: { + cachedUrl?: string; + contentType?: string; + size?: number; + orginalUrl?: string; + }; + mint?: { + /** @description Address that minted the NFT */ + mintAddress?: string; + /** @description Block number when the NFT was minted */ + blockNumber?: number; + /** @description Timestamp when the NFT was minted */ + timestamp?: string; + /** @description Transaction hash of the mint transaction */ + transactionHash?: string; + }; + /** @description List of all addresses that own the given NFT. */ + owners?: string[]; + }[]; + /** @description String - An offset used for pagination. Can be passed back as the "startToken" of a subsequent request to get the next page of results. Absent if there are no more results. */ + pageKey?: string; + }; + }; + }; + }; + }; + "getNFTsForCollection-v3": { + parameters: { + query?: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress?: string; + /** @description String - OpenSea slug for the NFT collection. */ + collectionSlug?: string; + /** @description Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. */ + withMetadata?: boolean; + /** @description String - A tokenID offset used for pagination. Can be a hex string, or a decimal. Users can specify the offset themselves to start from a custom offset, or to fetch multiple token ranges in parallel. */ + startToken?: string; + /** @description Integer - Sets the total number of NFTs returned in the response. Defaults to 100. */ + limit?: number; + /** @description No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0. */ + tokenUriTimeoutInMs?: number; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns a list of NFTs associated with the specified collection. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of objects that represent NFTs stored under the queried contract address or collection slug. */ + nfts?: { + id?: { + /** @default 44 */ + tokenId: string; + tokenMetadata?: { + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + }; + }; + tokenUri?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + }; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - The image URL that appears alongside the asset image on NFT platforms. */ + external_url?: string; + /** @description String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal. */ + background_color?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }[]; + }; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + contractMetadata?: { + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + opensea?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + }[]; + /** @description String - An offset used for pagination. Can be passed back as the "startToken" of a subsequent request to get the next page of results. Absent if there are no more results. */ + nextToken?: string; + }; + }; + }; + }; + }; + "getNFTMetadata-v3": { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + /** @description String - The ID of the token. Can be in hex or decimal format. */ + tokenId: string; + /** @description String - 'ERC721' or 'ERC1155'; specifies type of token to query for. API requests will perform faster if this is specified. */ + tokenType?: string; + /** @description No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0. */ + tokenUriTimeoutInMs?: number; + /** @description Defaults to false for faster response times. If true will refresh metadata for given token. If false will check the cache and use it or refresh if cache doesn't exist. */ + refreshCache?: boolean; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns the metadata of the specified NFT. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description The contract object that has details of a contract */ + contract?: { + /** @description Address of the held contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + /** @default 44 */ + tokenId: string; + tokenType?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Brief human-readable description */ + description?: string; + /** @description Details of the image corresponding to this contract */ + image?: { + /** @description The Url of the image stored in Alchemy cache */ + cachedUrl?: string; + /** @description The Url that has the thumbnail version of the NFT */ + thumbnailUrl?: string; + /** @description The Url that has the NFT image in png */ + pngUrl?: string; + /** @description The Url of the image stored in Alchemy cache */ + contentType?: string; + /** @description The size of the media asset in bytes. */ + size?: number; + /** @description The original Url of the image coming straight from the smart contract */ + originalUrl?: string; + }; + /** @description Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract */ + raw?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + }; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + }; + /** @description The collection object that has details of a collection */ + collection?: { + /** @description String - Collection name */ + name?: string; + /** @description String - OpenSea collection slug */ + slug?: string; + /** @description String - URL for the external site of the collection */ + externalUrl?: string; + /** @description String - Banner image URL for the collection */ + bannerImageUrl?: string; + }; + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + /** @description Only present if the request specified `orderBy=transferTime`. */ + acquiredAt?: { + /** @description Block timestamp of the block where the NFT was most recently acquired. */ + blockTimestamp?: string; + /** @description Block number of the block where the NFT was most recently acquired. */ + blockNumber?: string; + }; + animation?: { + cachedUrl?: string; + contentType?: string; + size?: number; + orginalUrl?: string; + }; + mint?: { + /** @description Address that minted the NFT */ + mintAddress?: string; + /** @description Block number when the NFT was minted */ + blockNumber?: number; + /** @description Timestamp when the NFT was minted */ + timestamp?: string; + /** @description Transaction hash of the mint transaction */ + transactionHash?: string; + }; + /** @description List of all addresses that own the given NFT. */ + owners?: string[]; + }; + }; + }; + }; + }; + "getNFTMetadataBatch-v3": { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** @description List of token objects to batch request NFT metadata for. Maximum 100. */ + tokens: { + /** @default 0xe785E82358879F061BC3dcAC6f0444462D4b5330 */ + contractAddress: string; + /** @default 44 */ + tokenId: string; + tokenType?: string; + }[]; + tokenUriTimeoutInMs?: number; + /** @default false */ + refreshCache?: boolean; + }; + }; + }; + responses: { + /** @description Returns an array of NFT metadata corresponding to the batch query. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description The contract object that has details of a contract */ + contract?: { + /** @description Address of the held contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + /** @default 44 */ + tokenId: string; + tokenType?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Brief human-readable description */ + description?: string; + /** @description Details of the image corresponding to this contract */ + image?: { + /** @description The Url of the image stored in Alchemy cache */ + cachedUrl?: string; + /** @description The Url that has the thumbnail version of the NFT */ + thumbnailUrl?: string; + /** @description The Url that has the NFT image in png */ + pngUrl?: string; + /** @description The Url of the image stored in Alchemy cache */ + contentType?: string; + /** @description The size of the media asset in bytes. */ + size?: number; + /** @description The original Url of the image coming straight from the smart contract */ + originalUrl?: string; + }; + /** @description Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract */ + raw?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + }; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + }; + /** @description The collection object that has details of a collection */ + collection?: { + /** @description String - Collection name */ + name?: string; + /** @description String - OpenSea collection slug */ + slug?: string; + /** @description String - URL for the external site of the collection */ + externalUrl?: string; + /** @description String - Banner image URL for the collection */ + bannerImageUrl?: string; + }; + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + /** @description Only present if the request specified `orderBy=transferTime`. */ + acquiredAt?: { + /** @description Block timestamp of the block where the NFT was most recently acquired. */ + blockTimestamp?: string; + /** @description Block number of the block where the NFT was most recently acquired. */ + blockNumber?: string; + }; + animation?: { + cachedUrl?: string; + contentType?: string; + size?: number; + orginalUrl?: string; + }; + mint?: { + /** @description Address that minted the NFT */ + mintAddress?: string; + /** @description Block number when the NFT was minted */ + blockNumber?: number; + /** @description Timestamp when the NFT was minted */ + timestamp?: string; + /** @description Transaction hash of the mint transaction */ + transactionHash?: string; + }; + /** @description List of all addresses that own the given NFT. */ + owners?: string[]; + }[]; + }; + }; + }; + }; + "getContractMetadata-v3": { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns the contract metadata for the specified address. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description String - Contract address for the queried NFT collection */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + }; + }; + }; + }; + "getCollectionMetadata-v3": { + parameters: { + query: { + /** @description String - OpenSea slug for the NFT collection. */ + collectionSlug: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns the collection metadata for the specified slug. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description String - Name of the queried NFT Collection */ + name?: string; + /** @description The human-readable string used to identify the collection on OpenSea. */ + slug?: string; + /** @description Floor price data for the collection */ + floorPrice?: { + /** @description The marketplace the floor price is on */ + marketplace?: string; + /** @description Floor price of the collection on the marketplace */ + floorPrice?: number; + /** @description The currency of the floor price */ + priceCurrency?: string; + }; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + }; + }; + }; + }; + }; + "invalidateContract-v3": { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns confirmation of cache invalidation along with the number of tokens invalidated. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** + * @description True if the contract was invalidated. + * False - if it wasn't. + */ + success?: string; + /** @description The number of tokens that were invalidated as a result of running this query. */ + numTokensInvalidated?: number; + }; + }; + }; + }; + }; + "getContractMetadataBatch-v3": { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description List of contract addresses to batch metadata requests for. + * @default [ + * "0xe785E82358879F061BC3dcAC6f0444462D4b5330", + * "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d" + * ] + */ + contractAddresses?: string[]; + }; + }; + }; + responses: { + /** @description Returns an array of contract metadata corresponding to the batch query. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description String - Contract address for the queried NFT collection */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }[]; + }; + }; + }; + }; + "getOwnersForNFT-v3": { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + /** @description String - The ID of the token. Can be in hex or decimal format. */ + tokenId: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns the owner(s) of the specified NFT. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of all addresses that own the given NFT. */ + owners?: string[]; + pageKey?: string; + }; + }; + }; + }; + }; + "getOwnersForContract-v3": { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + /** @description Boolean - If set to `true` the query will include the token balances per token id for each owner. `false` by default. */ + withTokenBalances?: boolean; + /** @description String - used for contracts with >50,000 owners. `pageKey` field can be passed back as request parameter to get the next page of results. */ + pageKey?: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns a list of all owners for the specified contract. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of all addresses that own one of the NFTs from the queried contract address. The format is applicable when `withTokenBalances=true`. */ + owners?: { + /** @default 0xe785E82358879F061BC3dcAC6f0444462D4b5330 */ + ownerAddress: string; + /** @description a list of the token ids and balances for the owner of the collection */ + tokenBalances?: { + /** @description tokenId of the NFT in the collection that an owner has */ + tokenId?: string; + /** @description the number of the specified token in the collection that the user owns */ + balance?: number; + }[]; + }[]; + }; + }; + }; + }; + }; + "getSpamContracts-v3": { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns a list of all spam contracts marked by Alchemy. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description A list of contract addresses earmarked as spam by Alchemy. */ + contractAddresses?: string[]; + }; + }; + }; + }; + }; + "isSpamContract-v3": { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns whether the specified contract is marked as spam. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** + * @description True - if the queried contract is marked as spam. + * False - if the queried contract is considered valid. + */ + isSpamContract?: boolean; + }; + }; + }; + }; + }; + "isAirdropNFT-v3": { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + /** @description String - The ID of the token. Can be in hex or decimal format. */ + tokenId: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns whether the specified token is marked as an airdrop. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** + * @description True - if the queried token is marked as an airdrop. + * False - if the queried token is not marked as an airdrop. + */ + isAirdrop?: boolean; + }; + }; + }; + }; + }; + "summarizeNFTAttributes-v3": { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns a summary of attribute prevalence for the specified NFT collection. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** @description Object mapping trait types to the prevalence of each trait within that type. */ + summary?: Record; + /** @default 0xe785E82358879F061BC3dcAC6f0444462D4b5330 */ + contractAddress: string; + }; + }; + }; + }; + }; + "getFloorPrice-v3": { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + /** @description String - OpenSea slug for the NFT collection. */ + collectionSlug?: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns the floor prices of the specified NFT collection across different marketplaces. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Name of the NFT marketplace where the collection is listed (in camel case). Current marketplaces supported - `openSea`, `looksRare`. So instead of the word `nftMarketplaceName` you will see marketplace names like `openSea` here. */ + nftMarketplaceName?: { + /** @description Number - The floor price of the collection on the given marketplace. */ + floorPrice?: number; + /** + * @description String - The currency in which the floor price is denominated. Typically, denominated in ETH + * @enum {string} + */ + priceCurrency?: "ETH"; + /** @description String - Link to the collection on the given marketplace. */ + collectionUrl?: string; + /** @description String - UTC timestamp of when the floor price was retrieved from the marketplace. */ + retrievedAt?: string; + /** @description String - Returns the error `unable to fetch floor price` if there was an error fetching floor prices from the given marketplace. */ + error?: string; + }; + }; + }; + }; + }; + }; + "searchContractMetadata-v3": { + parameters: { + query: { + /** @description String - The search string that you want to search for in contract metadata */ + query: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns the list of NFT contracts where the metadata has one or more keywords from the search string. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description String - Contract address for the queried NFT collection */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }[]; + }; + }; + }; + }; + "isHolderOfContract-v3": { + parameters: { + query: { + /** @description String - Wallet address to check for contract ownership. */ + wallet: string; + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns whether the specified wallet holds any NFT from the given contract. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Whether the given wallet owns any token in the given NFT contract. */ + isHolderOfContract?: boolean; + }; + }; + }; + }; + }; + "computeRarity-v3": { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + /** @description String - The ID of the token. Can be in hex or decimal format. */ + tokenId: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns the rarity information for each attribute of the specified NFT. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description NFT attributes and their associated prevalence. */ + rarities?: { + /** @description Name of the trait category, i.e., Hat, Color, Face, etc. */ + trait_type?: string; + /** @description Value for the trait, i.e., White Cap, Blue, Angry, etc. */ + value?: string; + /** @description Floating point value from 0 to 1 representing the prevalence of this value for this trait type. */ + prevalence?: number; + }[]; + }; + }; + }; + }; + }; + "getNFTSales-v3": { + parameters: { + query?: { + /** @description String - The block number to start fetching NFT sales data from. Allowed values are decimal and hex integers, and "latest". Defaults to "0". */ + fromBlock?: string; + /** @description String - The block number to start fetching NFT sales data from. Allowed values are decimal and hex integers, and "latest". Defaults to "latest". */ + toBlock?: string; + /** @description Enum - Whether to return the results ascending from startBlock or descending from startBlock. Defaults to descending (false). */ + order?: "asc" | "desc"; + /** @description Enum - The name of the NFT marketplace to filter sales by. The endpoint currently supports "seaport", "wyvern", "looksrare", "x2y2", "blur", and "cryptopunks". Defaults to returning sales from all supported marketplaces. */ + marketplace?: + | "seaport" + | "looksrare" + | "x2y2" + | "wyvern" + | "blur" + | "cryptopunks"; + /** @description String - The contract address of an NFT collection to filter sales by. Defaults to returning all NFT contracts. */ + contractAddress?: string; + /** @description String - The token ID of an NFT within the collection specified by contractAddress to filter sales by. Defaults to returning all token IDs. */ + tokenId?: string; + /** @description String - The address of the NFT buyer to filter sales by. Defaults to returning sales involving any buyer. */ + buyerAddress?: string; + /** @description String - The address of the NFT seller to filter sales by. Defaults to returning sales involving any seller. */ + sellerAddress?: string; + /** @description Enum - Filter by whether the buyer or seller was the taker in the NFT trade. Allowed filter values are "BUYER" and "SELLER". Defaults to returning both buyer and seller taker trades. */ + taker?: "BUYER" | "SELLER"; + /** @description Integer - The maximum number of NFT sales to return. Maximum and default values are 1000. */ + limit?: number; + /** @description String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results. */ + pageKey?: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns a list of NFT sales that match the query parameters. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of NFT sales that match the query. */ + nftSales?: { + /** @description String - The marketplace the sale took place on. */ + marketplace?: string; + /** @description String - The address of the marketplace contract. */ + marketplaceAddress?: string; + /** @description String - The contract address of the collection the NFT belongs to. */ + contractAddress?: string; + /** @description String - The decimal token ID of the NFT being sold. */ + tokenId?: string; + /** @description Integer - The number of tokens sold in the sale as a decimal integer string. */ + quantity?: string; + /** @description String - The address of the buyer in the NFT sale. */ + buyerAddress?: string; + /** @description String - The address of the seller in the NFT sale. */ + sellerAddress?: string; + /** + * @description String - Whether the price taker in the trade was the buyer or the seller. + * @enum {string} + */ + taker?: "BUYER" | "SELLER"; + /** @description The payment from buyer to the seller. */ + sellerFee?: { + /** @description String - The amount of the payment from the buyer to seller as a decimal integer string. */ + amount?: string; + /** @description String - The smart contract address of the token used for the payment. */ + tokenAddress?: string; + /** @description String - The symbol of the token used for the payment. */ + symbol?: string; + /** @description Integer - The number of decimals of the token used for the payment. */ + decimals?: number; + }; + /** @description The payment from buyer to the NFT marketplace protocol. */ + protocolFee?: { + /** @description String - The amount of the payment to the marketplace as a decimal integer string. */ + amount?: string; + /** @description String - The smart contract address of the token used for the payment. */ + tokenAddress?: string; + /** @description String - The symbol of the token used for the payment. */ + symbol?: string; + /** @description Integer - The number of decimals of the token used for the payment. */ + decimals?: number; + }; + /** @description The payment from buyer to the royalty address of the NFT collection. */ + royaltyFee?: { + /** @description String - The amount of the payment to the royalty collector as a decimal integer string. */ + amount?: string; + /** @description String - The smart contract address of the token used for the payment. */ + tokenAddress?: string; + /** @description String - The symbol of the token used for the payment. */ + symbol?: string; + /** @description Integer - The number of decimals of the token used for the payment. */ + decimals?: number; + }; + /** @description Integer - The block number the NFT sale took place in. */ + blockNumber?: number; + /** @description Integer - The log number of the sale event emitted within the block. */ + logIndex?: number; + /** @description Integer - The index of the token within the bundle of NFTs sold in the sale. */ + bundleIndex?: number; + /** @description String - The transaction hash of the transaction containing the sale. */ + transactionHash?: string; + }[]; + /** @description String - The page key to use to fetch the next page of results. Returns null if there are no more results. */ + pageKey?: string; + /** @description Block Information of the block as of which the corresponding data is valid */ + validAt?: { + /** @description The block number above information is valid as of */ + blockNumber?: number; + /** @description The block hash above information is valid as of */ + blockHash?: string; + /** @description The block timestamp above information is valid as of */ + blockTimestamp?: string; + }; + }; + }; + }; + }; + }; + "getContractsForOwner-v3": { + parameters: { + query: { + /** @description String - Address for NFT owner (can be in ENS format for Eth Mainnet). */ + owner: string; + /** @description String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results. */ + pageKey?: string; + /** @description Number of NFTs to be returned per page. Defaults to 100. Max is 100. */ + pageSize?: number; + /** @description Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. */ + withMetadata?: boolean; + /** + * @description Array of filters (as ENUMS) that will be applied to the query. Only NFTs that match one or more of these filters will be included in the response. May not be used in conjunction with excludeFilters[]. Filter Options: + * - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**. + * - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only. + * - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts) + */ + "includeFilters[]"?: ("SPAM" | "AIRDROPS")[]; + /** + * @description Array of filters (as ENUMS) that will be applied to the query. NFTs that match one or more of these filters will be excluded from the response. May not be used in conjunction with includeFilters[]. Filter Options: + * - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**. + * - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only. + * - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts) + */ + "excludeFilters[]"?: ("SPAM" | "AIRDROPS")[]; + /** + * @description Enum - ordering scheme to use for ordering NFTs in the response. If unspecified, NFTs will be ordered by contract address and token ID. + * - transferTime: NFTs will be ordered by the time they were transferred into the wallet, with newest NFTs first. Note: This ordering is supported on Ethereum Mainnet, Optimism Mainnet, Polygon Mainnet, Base Mainnet, Arbitrum One, Polygon Amoy, Base Sepolia, Arbitrum Sepolia, Ethereum Sepolia, and Optimism Sepolia. + */ + orderBy?: "transferTime"; + /** + * @description Enum - the confidence level at which to filter spam at. + * + * Confidence Levels: + * - VERY_HIGH + * - HIGH + * - MEDIUM + * - LOW + * + * The confidence level set means that any spam that is at that confidence level or higher will be filtered out. For example, if the confidence level is HIGH, contracts that we have HIGH or VERY_HIGH confidence in being spam will be filtered out from the response. + * Defaults to VERY_HIGH for Ethereum Mainnet and MEDIUM for Matic Mainnet. + * + * **Please note that this filter is only available on paid tiers. Upgrade your account [here](https://dashboard.alchemy.com/settings/billing/).** + */ + spamConfidenceLevel?: "VERY_HIGH" | "HIGH" | "MEDIUM" | "LOW"; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns a list of NFT contracts held by the specified owner address. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + contracts?: { + /** @default 0xe785E82358879F061BC3dcAC6f0444462D4b5330 */ + address: string; + /** @description The name of the contract, i.e. "Bored Ape Yacht Club". */ + name?: string; + /** @description The symbol of the contract, i.e. BAYC. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + tokenType?: string; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens. */ + totalBalance?: number; + /** @description Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens. */ + numDistinctTokensOwned?: number; + /** @description `True` if the contract is detected as spam contract. `False` if it is not spam or has not been evaluated by our system yet */ + isSpam?: boolean; + /** @description Details of the display NFT for this contract. This NFT and its image can be used to represent the contract when displaying info about it. */ + displayNft?: { + /** @description One of the tokens from this contract held by the owner. */ + tokenId?: string; + /** @description The title of the token held by the owner i.e. "Something #22". */ + name?: string; + }; + /** @description Details of the image corresponding to this contract */ + image?: { + /** @description The Url of the image stored in Alchemy cache */ + cachedUrl?: string; + /** @description The Url that has the thumbnail version of the NFT */ + thumbnailUrl?: string; + /** @description The Url that has the NFT image in png */ + pngUrl?: string; + /** @description The Url of the image stored in Alchemy cache */ + contentType?: string; + /** @description The size of the media asset in bytes. */ + size?: number; + /** @description The original Url of the image coming straight from the smart contract */ + originalUrl?: string; + }; + }[]; + pageKey?: string; + /** @description String - Total number of NFT contracts held by the given address returned in this page. */ + totalCount?: string; + }; + }; + }; + }; + }; + "getCollectionsForOwner-v3": { + parameters: { + query: { + /** @description String - Address for NFT owner (can be in ENS format for Eth Mainnet). */ + owner: string; + /** @description String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results. */ + pageKey?: string; + /** @description Number of NFTs to be returned per page. Defaults to 100. Max is 100. */ + pageSize?: number; + /** @description Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. */ + withMetadata?: boolean; + /** + * @description Array of filters (as ENUMS) that will be applied to the query. Only NFTs that match one or more of these filters will be included in the response. May not be used in conjunction with excludeFilters[]. Filter Options: + * - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**. + * - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only. + * - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts) + */ + "includeFilters[]"?: ("SPAM" | "AIRDROPS")[]; + /** + * @description Array of filters (as ENUMS) that will be applied to the query. NFTs that match one or more of these filters will be excluded from the response. May not be used in conjunction with includeFilters[]. Filter Options: + * - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**. + * - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only. + * - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts) + */ + "excludeFilters[]"?: ("SPAM" | "AIRDROPS")[]; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns a list of NFT collections held by the specified owner address. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + collections?: { + /** @description The name of the collection, i.e. "Bored Ape Yacht Club". */ + name?: string; + /** @description The human-readable string used to identify the collection on OpenSea. */ + slug?: string; + /** @description Floor price data for the collection */ + floorPrice?: { + /** @description The marketplace the floor price is on */ + marketplace?: string; + /** @description Floor price of the collection on the marketplace */ + floorPrice?: number; + /** @description The currency of the floor price */ + priceCurrency?: string; + }; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description Contract-level data for a collection, such as contract type, name, and symbol. */ + contract?: { + /** @description Address of the contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + }; + /** @description Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens. */ + totalBalance?: number; + /** @description Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens. */ + numDistinctTokensOwned?: number; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description Details of the display NFT for this contract. This NFT and its image can be used to represent the contract when displaying info about it. */ + displayNft?: { + /** @description One of the tokens from this contract held by the owner. */ + tokenId?: string; + /** @description The title of the token held by the owner i.e. "Something #22". */ + name?: string; + }; + /** @description Details of the image corresponding to this contract */ + image?: { + /** @description The Url of the image stored in Alchemy cache */ + cachedUrl?: string; + /** @description The Url that has the thumbnail version of the NFT */ + thumbnailUrl?: string; + /** @description The Url that has the NFT image in png */ + pngUrl?: string; + /** @description The Url of the image stored in Alchemy cache */ + contentType?: string; + /** @description The size of the media asset in bytes. */ + size?: number; + /** @description The original Url of the image coming straight from the smart contract */ + originalUrl?: string; + }; + }[]; + pageKey?: string; + /** @description String - Total number of NFT collections held by the given address. */ + totalCount?: string; + }; + }; + }; + }; + }; + "reportSpam-v3": { + parameters: { + query: { + /** @description String - The address to check for spam status. */ + address: string; + isSpam: boolean; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns a confirmation message if the address was successfully reported as spam. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": string; + }; + }; + }; + }; + "refreshNftMetadata-v3": { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description Contract address of the token you want to refresh. + * @default 0xe785E82358879F061BC3dcAC6f0444462D4b5330 + */ + contractAddress: string; + /** + * @description Token ID of the token you want to refresh. Must belong to the contract address. + * @default 44 + */ + tokenId: string; + }; + }; + }; + responses: { + /** @description Returns the status of the refresh request along with the estimated time to complete. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description If the token is successfully queued for ingestion the value will be "Queued". */ + status?: string; + /** @description Estimated time until the metadata refresh is complete for this token. */ + estimatedMsToRefresh?: string; + }; + }; + }; + }; + }; + getNFTs: { + parameters: { + query: { + /** @description String - Address for NFT owner (can be in ENS format for Eth Mainnet). */ + owner: string; + /** @description Array of contract addresses to filter the responses with. Max limit 45 contracts. */ + "contractAddresses[]"?: string[]; + /** @description Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. */ + withMetadata?: boolean; + /** + * @description Enum - ordering scheme to use for ordering NFTs in the response. If unspecified, NFTs will be ordered by contract address and token ID. + * - transferTime: NFTs will be ordered by the time they were transferred into the wallet, with newest NFTs first. Note: This ordering is supported on Ethereum Mainnet, Optimism Mainnet, Polygon Mainnet, Base Mainnet, Arbitrum One, Polygon Amoy, Base Sepolia, Arbitrum Sepolia, Ethereum Sepolia, and Optimism Sepolia. + */ + orderBy?: "transferTime"; + /** + * @description Array of filters (as ENUMS) that will be applied to the query. NFTs that match one or more of these filters will be excluded from the response. May not be used in conjunction with includeFilters[]. Filter Options: + * - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**. + * - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only. + * - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts) + */ + "excludeFilters[]"?: ("SPAM" | "AIRDROPS")[]; + /** + * @description Array of filters (as ENUMS) that will be applied to the query. Only NFTs that match one or more of these filters will be included in the response. May not be used in conjunction with excludeFilters[]. Filter Options: + * - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**. + * - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only. + * - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts) + */ + "includeFilters[]"?: ("SPAM" | "AIRDROPS")[]; + /** + * @description Enum - the confidence level at which to filter spam at. + * + * Confidence Levels: + * - VERY_HIGH + * - HIGH + * - MEDIUM + * - LOW + * + * The confidence level set means that any spam that is at that confidence level or higher will be filtered out. For example, if the confidence level is HIGH, contracts that we have HIGH or VERY_HIGH confidence in being spam will be filtered out from the response. + * Defaults to VERY_HIGH for Ethereum Mainnet and MEDIUM for Matic Mainnet. + * + * **Please note that this filter is only available on paid tiers. Upgrade your account [here](https://dashboard.alchemy.com/settings/billing/).** + */ + spamConfidenceLevel?: "VERY_HIGH" | "HIGH" | "MEDIUM" | "LOW"; + /** @description No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0. */ + tokenUriTimeoutInMs?: number; + /** @description String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results. */ + pageKey?: string; + /** @description Number of NFTs to be returned per page. Defaults to 100. Max is 100. */ + pageSize?: number; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns the list of all NFTs owned by the given address and satisfying the given input parameters. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ownedNfts?: { + /** @description Object - Contract for returned NFT */ + contract?: { + /** @description String - Address of NFT contract. */ + address?: string; + }; + id?: { + /** @default 44 */ + tokenId: string; + tokenMetadata?: { + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + }; + }; + /** @description String - Token balance */ + balance?: string; + /** @description String - Name of the NFT asset. */ + title?: string; + /** @description String - Brief human-readable description */ + description?: string; + tokenUri?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + }; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - The image URL that appears alongside the asset image on NFT platforms. */ + external_url?: string; + /** @description String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal. */ + background_color?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }[]; + }; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + contractMetadata?: { + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + opensea?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + /** @description Information about whether and why a contract was marked as spam. */ + spamInfo?: { + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + /** @description Only present if the request specified `orderBy=transferTime`. */ + acquiredAt?: { + /** @description Block timestamp of the block where the NFT was most recently acquired. */ + blockTimestamp?: string; + /** @description Block number of the block where the NFT was most recently acquired. */ + blockNumber?: string; + }; + }[]; + pageKey?: string; + /** @description Integer - Total number of NFTs (distinct `tokenIds`) owned by the given address. */ + totalCount?: number; + /** @description String - The canonical head block hash of when your request was received i.e. the block corresponding to `latest` */ + blockHash?: string; + }; + }; + }; + }; + }; + getNFTMetadata: { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + /** @description String - The ID of the token. Can be in hex or decimal format. */ + tokenId: string; + /** @description String - 'ERC721' or 'ERC1155'; specifies type of token to query for. API requests will perform faster if this is specified. */ + tokenType?: string; + /** @description No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0. */ + tokenUriTimeoutInMs?: number; + /** @description Defaults to false for faster response times. If true will refresh metadata for given token. If false will check the cache and use it or refresh if cache doesn't exist. */ + refreshCache?: boolean; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Object - Contract for returned NFT */ + contract?: { + /** @description String - Address of NFT contract. */ + address?: string; + }; + id?: { + /** @default 44 */ + tokenId: string; + tokenMetadata?: { + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + }; + }; + /** @description String - Token balance */ + balance?: string; + /** @description String - Name of the NFT asset. */ + title?: string; + /** @description String - Brief human-readable description */ + description?: string; + tokenUri?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + }; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - The image URL that appears alongside the asset image on NFT platforms. */ + external_url?: string; + /** @description String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal. */ + background_color?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }[]; + }; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + contractMetadata?: { + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + opensea?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + /** @description Information about whether and why a contract was marked as spam. */ + spamInfo?: { + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + /** @description Only present if the request specified `orderBy=transferTime`. */ + acquiredAt?: { + /** @description Block timestamp of the block where the NFT was most recently acquired. */ + blockTimestamp?: string; + /** @description Block number of the block where the NFT was most recently acquired. */ + blockNumber?: string; + }; + }; + }; + }; + }; + }; + getNFTMetadataBatch: { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** @description List of token objects to batch request NFT metadata for. Maximum 100. */ + tokens: { + /** @default 0xe785E82358879F061BC3dcAC6f0444462D4b5330 */ + contractAddress: string; + /** @default 44 */ + tokenId: string; + tokenType?: string; + }[]; + tokenUriTimeoutInMs?: number; + /** @default false */ + refreshCache?: boolean; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Object - Contract for returned NFT */ + contract?: { + /** @description String - Address of NFT contract. */ + address?: string; + }; + id?: { + /** @default 44 */ + tokenId: string; + tokenMetadata?: { + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + }; + }; + /** @description String - Token balance */ + balance?: string; + /** @description String - Name of the NFT asset. */ + title?: string; + /** @description String - Brief human-readable description */ + description?: string; + tokenUri?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + }; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - The image URL that appears alongside the asset image on NFT platforms. */ + external_url?: string; + /** @description String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal. */ + background_color?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }[]; + }; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + contractMetadata?: { + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + opensea?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + /** @description Information about whether and why a contract was marked as spam. */ + spamInfo?: { + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + /** @description Only present if the request specified `orderBy=transferTime`. */ + acquiredAt?: { + /** @description Block timestamp of the block where the NFT was most recently acquired. */ + blockTimestamp?: string; + /** @description Block number of the block where the NFT was most recently acquired. */ + blockNumber?: string; + }; + }[]; + }; + }; + }; + }; + getContractMetadata: { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description String - Contract address for the queried NFT collection */ + address?: string; + contractMetadata?: { + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + opensea?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + }; + }; + }; + }; + }; + getContractMetadataBatch: { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description list of contract addresses to batch metadata requests for + * @default [ + * "0xe785E82358879F061BC3dcAC6f0444462D4b5330", + * "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d" + * ] + */ + contractAddresses?: string[]; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @default 0xe785E82358879F061BC3dcAC6f0444462D4b5330 */ + address: string; + /** @description The object that represents a smart contract and has all data corresponding to that contract */ + contractMetadata?: { + /** @description Address of the held contract */ + address?: string; + /** @description Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens. */ + totalBalance?: number; + /** @description Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens. */ + numDistinctTokensOwned?: number; + isSpam?: boolean; + /** @description One of the tokens from this contract held by the owner. */ + tokenId?: string; + /** @description The name of the contract, i.e. "Bored Ape Yacht Club". */ + name?: string; + /** @description The title of the token held by the owner i.e. "Something #22". */ + title?: string; + /** @description The symbol of the contract, i.e. BAYC. */ + symbol?: string; + /** @description The NFT standard used by the contract, i.e. ERC721 or ERC1155. */ + tokenType?: string; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }[]; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + opensea?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + }[]; + }; + }; + }; + }; + getNFTsForCollection: { + parameters: { + query?: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress?: string; + /** @description String - OpenSea slug for the NFT collection. */ + collectionSlug?: string; + /** @description Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. */ + withMetadata?: boolean; + /** @description String - A tokenID offset used for pagination. Can be a hex string, or a decimal. Users can specify the offset themselves to start from a custom offset, or to fetch multiple token ranges in parallel. */ + startToken?: string; + /** @description Integer - Sets the total number of NFTs returned in the response. Defaults to 100. */ + limit?: number; + /** @description No set timeout by default - When metadata is requested, this parameter is the timeout (in milliseconds) for the website hosting the metadata to respond. If you want to _only_ access the cache and not live fetch any metadata for cache misses then set this value to 0. */ + tokenUriTimeoutInMs?: number; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of objects that represent NFTs stored under the queried contract address or collection slug. */ + nfts?: { + id?: { + /** @default 44 */ + tokenId: string; + tokenMetadata?: { + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + }; + }; + tokenUri?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + }; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - The image URL that appears alongside the asset image on NFT platforms. */ + external_url?: string; + /** @description String - Background color of the NFT item. Usually must be defined as a six-character hexadecimal. */ + background_color?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }[]; + }; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + contractMetadata?: { + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + opensea?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + }[]; + /** @description String - An offset used for pagination. Can be passed back as the "startToken" of a subsequent request to get the next page of results. Absent if there are no more results. */ + nextToken?: string; + }; + }; + }; + }; + }; + getOwnersForToken: { + parameters: { + query: { + /** @description String - Contract address for the token to get the owner for. */ + contractAddress: string; + /** @description String - The ID of the token. Can be in hex or decimal format. */ + tokenId: string; + /** @description String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results. */ + pageKey?: string; + /** @description Number of owners to be returned per page. */ + pageSize?: number; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of all addresses that own the given NFT. */ + owners?: string[]; + }; + }; + }; + }; + }; + getOwnersForCollection: { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + /** @description Boolean - If set to `true` the query will include the token balances per token id for each owner. `false` by default. */ + withTokenBalances?: boolean; + /** @description String - used for collections with >50,000 owners. `pageKey` field can be passed back as request parameter to get the next page of results. */ + pageKey?: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": string[]; + }; + }; + }; + }; + getSpamContracts: { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description A list of contract addresses earmarked as spam by Alchemy. */ + contractAddresses?: string[]; + }; + }; + }; + }; + }; + isSpamContract: { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": boolean; + }; + }; + }; + }; + isAirdrop: { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + /** @description String - The ID of the token. Can be in hex or decimal format. */ + tokenId: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": boolean; + }; + }; + }; + }; + invalidateContract: { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** + * @description True if the contract was invalidated. + * False - if it wasn't. + */ + success?: string; + /** @description The number of tokens that were invalidated as a result of running this query. */ + numTokensInvalidated?: number; + }; + }; + }; + }; + }; + getFloorPrice: { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Name of the NFT marketplace where the collection is listed. Current marketplaces supported - OpenSea, LooksRare */ + nftMarketplace?: { + /** @description Number - The floor price of the collection on the given marketplace. */ + floorPrice?: number; + /** + * @description String - The currency in which the floor price is denominated. Typically, denominated in ETH + * @enum {string} + */ + priceCurrency?: "ETH"; + /** @description String - Link to the collection on the given marketplace. */ + collectionUrl?: string; + /** @description String - UTC timestamp of when the floor price was retrieved from the marketplace. */ + retrievedAt?: string; + /** @description String - Returns an error if there was an error fetching floor prices from the given marketplace. */ + error?: string; + }; + }; + }; + }; + }; + }; + computeRarity: { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + /** @description String - The ID of the token. Can be in hex or decimal format. */ + tokenId: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description NFT attributes and their associated prevalence. */ + rarities?: { + /** @description Name of the trait category, i.e. Hat, Color, Face, etc. */ + trait_type?: string; + /** @description Value for the trait, i.e. White Cap, Blue, Angry, etc. */ + value?: string; + /** @description Floating point value from 0 to 1 representing the prevalence of this value for this trait type. */ + prevalence?: number; + }[]; + }; + }; + }; + }; + }; + searchContractMetadata: { + parameters: { + query: { + /** @description String - The search string that you want to search for in contract metadata */ + query: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Returns the list of NFT contracts where the metadata has one or more keywords from the search string. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @default 0xe785E82358879F061BC3dcAC6f0444462D4b5330 */ + address: string; + contractMetadata?: { + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + opensea?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }; + }[]; + }; + }; + }; + }; + summarizeNFTAttributes: { + parameters: { + query: { + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** @description Object mapping trait types to the prevalence of each trait within that type. */ + summary?: Record; + /** @default 0xe785E82358879F061BC3dcAC6f0444462D4b5330 */ + contractAddress: string; + }; + }; + }; + }; + }; + isHolderOfCollection: { + parameters: { + query: { + /** @description String - Address for NFT owner (can be in ENS format for Eth Mainnet). */ + wallet: string; + /** @description String - Contract address for the NFT contract (ERC721 and ERC1155 supported). */ + contractAddress: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Whether the given wallet owns any token in the given NFT collection. */ + isHolderOfCollection?: boolean; + }; + }; + }; + }; + }; + getNFTSales: { + parameters: { + query?: { + /** @description String - The block number to start fetching NFT sales data from. Allowed values are decimal and hex integers, and "latest". Defaults to "0". */ + fromBlock?: string; + /** @description String - The block number to start fetching NFT sales data from. Allowed values are decimal and hex integers, and "latest". Defaults to "latest". */ + toBlock?: string; + /** @description Enum - Whether to return the results ascending from startBlock or descending from startBlock. Defaults to descending (false). */ + order?: "asc" | "desc"; + /** @description Enum - The name of the NFT marketplace to filter sales by. The endpoint currently supports "seaport", "wyvern", "looksrare", "x2y2", "blur", and "cryptopunks". Defaults to returning sales from all supported marketplaces. */ + marketplace?: + | "seaport" + | "looksrare" + | "x2y2" + | "wyvern" + | "blur" + | "cryptopunks"; + /** @description String - The contract address of a NFT collection to filter sales by. Defaults to returning all NFT contracts. */ + contractAddress?: string; + /** @description String - The token ID of an NFT within the collection specified by contractAddress to filter sales by. Defaults to returning all token IDs. */ + tokenId?: string; + /** @description String - The address of the NFT buyer to filter sales by. Defaults to returning sales involving any buyer. */ + buyerAddress?: string; + /** @description String - The address of the NFT seller to filter sales by. Defaults to returning sales involving any seller. */ + sellerAddress?: string; + /** @description Enum - Filter by whether the buyer or seller was the taker in the NFT trade. Allowed filter values are "BUYER" and "SELLER". Defaults to returning both buyer and seller taker trades. */ + taker?: "BUYER" | "SELLER"; + /** @description Integer - The maximum number of NFT sales to return. Maximum and default values are 1000. */ + limit?: number; + /** @description String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results. */ + pageKey?: string; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of NFT sales that match the query */ + nftSales?: { + /** @description String - The marketplace the sale took place on. */ + marketplace?: string; + /** @description String - The contract address of the collection the NFT belongs to. */ + contractAddress?: string; + /** @description String - The decimal token ID of the NFT being sold. */ + tokenId?: string; + /** @description Integer - The number of tokens sold in the sale as a decimal integer string. */ + quantity?: string; + /** @description String - The address of the buyer in the NFT sale. */ + buyerAddress?: string; + /** @description String - The address of the seller in the NFT sale. */ + sellerAddress?: string; + /** + * @description String - Whether the price taker in the trade was the buyer or the seller. + * @enum {string} + */ + taker?: "BUYER" | "SELLER"; + /** @description The payment from buyer to the seller */ + sellerFee?: { + /** @description String - The amount of the payment from the buyer to seller as a decimal integer string. */ + amount?: string; + /** @description String - The symbol of the token used for the payment. */ + symbol?: string; + /** @description Integer - The number of decimals of the token used for the payment. */ + decimals?: number; + }; + /** @description The payment from buyer to the NFT marketplace protocol */ + protocolFee?: { + /** @description String - The amount of the payment to the marketplace as a decimal integer string. */ + amount?: string; + /** @description String - The symbol of the token used for the payment. */ + symbol?: string; + /** @description Integer - The number of decimals of the token used for the payment. */ + decimals?: number; + }; + /** @description The payment from buyer to the royalty address of the NFT collection */ + royaltyFee?: { + /** @description String - The amount of the payment to the royalty collector as a decimal integer string. */ + amount?: string; + /** @description String - The symbol of the token used for the payment. */ + symbol?: string; + /** @description Integer - The number of decimals of the token used for the payment. */ + decimals?: number; + }; + /** @description Integer - The block number the NFT sale took place in. */ + blockNumber?: number; + /** @description Integer - The log number of the sale event emitted within the block. */ + logIndex?: number; + /** @description Integer - The index of the token within the bundle of NFTs sold in the sale. */ + bundleIndex?: number; + /** @description String - The transaction hash of the transaction containing the sale. */ + transactionHash?: string; + }[]; + /** @description String - The page key to use to fetch the next page of results. Returns null if there are no more results. */ + pageKey?: string; + }; + }; + }; + }; + }; + getContractsForOwner: { + parameters: { + query: { + /** @description String - Address for NFT owner (can be in ENS format for Eth Mainnet). */ + owner: string; + /** @description String - key for pagination. If more results are available, a pageKey will be returned in the response. Pass back the pageKey as a param to fetch the next page of results. */ + pageKey?: string; + /** @description Number of NFTs to be returned per page. Defaults to 100. Max is 100. */ + pageSize?: number; + /** @description Boolean - if set to `true`, returns NFT metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. */ + withMetadata?: boolean; + /** + * @description Array of filters (as ENUMS) that will be applied to the query. Only NFTs that match one or more of these filters will be included in the response. May not be used in conjunction with excludeFilters[]. Filter Options: + * - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**. + * - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only. + * - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts) + */ + "includeFilters[]"?: ("SPAM" | "AIRDROPS")[]; + /** + * @description Array of filters (as ENUMS) that will be applied to the query. NFTs that match one or more of these filters will be excluded from the response. May not be used in conjunction with includeFilters[]. Filter Options: + * - SPAM: NFTs that have been classified as spam. Spam classification has a wide range of criteria that includes but is not limited to emitting fake events and copying other well-known NFTs. Please note that this filter is currently supported on Mainnet for Base, Arbitrum, Optimism, Ethereum, Polygon, Worldchain, Avax, BNB, Gnosis, Zksync, Unichain, and Blast, and is **available exclusively on paid tiers**. + * - AIRDROPS: NFTs that have were airdropped to the user. Airdrops are defined as NFTs that were minted to a user address in a transaction sent by a different address. NOTE: this filter is currently supported on Ethereum Mainnet, Ethereum Goerli, and Matic Mainnet only. + * - To learn more about spam, you can refer to this: [Spam NFTs and how to fix them](https://www.alchemy.com/overviews/spam-nfts) + */ + "excludeFilters[]"?: ("SPAM" | "AIRDROPS")[]; + /** + * @description Enum - ordering scheme to use for ordering NFTs in the response. If unspecified, NFTs will be ordered by contract address and token ID. + * - transferTime: NFTs will be ordered by the time they were transferred into the wallet, with newest NFTs first. Note: This ordering is supported on Ethereum Mainnet, Optimism Mainnet, Polygon Mainnet, Base Mainnet, Arbitrum One, Polygon Amoy, Base Sepolia, Arbitrum Sepolia, Ethereum Sepolia, and Optimism Sepolia. + */ + orderBy?: "transferTime"; + /** + * @description Enum - the confidence level at which to filter spam at. + * + * Confidence Levels: + * - VERY_HIGH + * - HIGH + * - MEDIUM + * - LOW + * + * The confidence level set means that any spam that is at that confidence level or higher will be filtered out. For example, if the confidence level is HIGH, contracts that we have HIGH or VERY_HIGH confidence in being spam will be filtered out from the response. + * Defaults to VERY_HIGH for Ethereum Mainnet and MEDIUM for Matic Mainnet. + * + * **Please note that this filter is only available on paid tiers. Upgrade your account [here](https://dashboard.alchemy.com/settings/billing/).** + */ + spamConfidenceLevel?: "VERY_HIGH" | "HIGH" | "MEDIUM" | "LOW"; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + contracts?: { + /** @description Address of the held contract */ + address?: string; + /** @description Sum of NFT balances across all token IDs held by the owner. For non-fungible tokens this will be equal to the `numDistinctTokensOwned`, but it may be higher if the user holds some fungible ERC1155 tokens. */ + totalBalance?: number; + /** @description Number of distinct token IDs held by the owner. For non-fungible tokens this will be equal to the `totalBalance`, but it may be lower if the user holds some fungible ERC1155 tokens. */ + numDistinctTokensOwned?: number; + isSpam?: boolean; + /** @description One of the tokens from this contract held by the owner. */ + tokenId?: string; + /** @description The name of the contract, i.e. "Bored Ape Yacht Club". */ + name?: string; + /** @description The title of the token held by the owner i.e. "Something #22". */ + title?: string; + /** @description The symbol of the contract, i.e. BAYC. */ + symbol?: string; + /** @description The NFT standard used by the contract, i.e. ERC721 or ERC1155. */ + tokenType?: string; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + media?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + raw?: string; + /** @description String - Public gateway uri for the raw uri above. */ + gateway?: string; + /** @description URL for a resized thumbnail of the NFT media asset. */ + thumbnail?: string; + /** @description The media format (jpg, gif, png, etc.) of the gateway and thumbnail assets. */ + format?: string; + /** @description The size of the media asset in bytes. */ + bytes?: number; + }[]; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + opensea?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + }[]; + pageKey?: string; + /** @description String - Total number of NFT contracts held by the given address returned in this page. */ + totalCount?: string; + }; + }; + }; + }; + }; + reportSpam: { + parameters: { + query: { + /** @description String - The address to check for spam status. */ + address: string; + /** @description Boolean - Whether the address is spam. */ + isSpam: boolean; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": string; + }; + }; + }; + }; +} diff --git a/packages/data-apis/src/generated/rest/portfolio.schema.ts b/packages/data-apis/src/generated/rest/portfolio.schema.ts new file mode 100644 index 0000000000..9a41ff79c4 --- /dev/null +++ b/packages/data-apis/src/generated/rest/portfolio.schema.ts @@ -0,0 +1,31 @@ +/* eslint-disable -- machine-generated file */ +// AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. +// Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) +// is recorded in packages/api-codegen/specs/specs.lock.json. + +import type { RestRequestSchema } from "@alchemy/common"; +import type { operations } from "./portfolio.types.js"; + +/** Request body for get-tokens-by-address. */ +export type GetTokensByAddressBody = NonNullable< + operations["get-tokens-by-address"]["requestBody"] +>["content"]["application/json"]; + +/** 200 response for get-tokens-by-address. */ +export type GetTokensByAddressResponse = + operations["get-tokens-by-address"]["responses"]["200"]["content"]["application/json"]; + +/** RestRequestSchema entries for the portfolio REST API. */ +export type PortfolioRestSchema = readonly [ + { + /** POST /{apiKey}/assets/tokens/by-address (operationId: get-tokens-by-address) */ + Route: "assets/tokens/by-address"; + Method: "POST"; + Body: GetTokensByAddressBody; + Response: GetTokensByAddressResponse; + }, +]; + +/** Compile-time guard that the emitted tuple satisfies the shared constraint. */ +export type _AssertPortfolioRestSchema = + PortfolioRestSchema extends RestRequestSchema ? true : never; diff --git a/packages/data-apis/src/generated/rest/portfolio.types.ts b/packages/data-apis/src/generated/rest/portfolio.types.ts new file mode 100644 index 0000000000..5cacfec454 --- /dev/null +++ b/packages/data-apis/src/generated/rest/portfolio.types.ts @@ -0,0 +1,1719 @@ +/* eslint-disable -- machine-generated file */ +// AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. +// Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) +// is recorded in packages/api-codegen/specs/specs.lock.json. + +export interface paths { + "/{apiKey}/assets/tokens/by-address": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Tokens By Wallet + * @description Fetches fungible tokens (native, ERC-20 and SPL) for multiple wallet addresses and networks. Returns a list of tokens with balances, prices, and metadata for each wallet/network combination. This endpoint is supported on Ethereum, Solana, and 30+ EVM chains. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + post: operations["get-tokens-by-address"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{apiKey}/assets/tokens/balances/by-address": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Token Balances By Wallet + * @description Fetches fungible tokens (native, ERC-20, and SPL) for multiple wallet addresses and networks. Returns a list of tokens with balances for each wallet/network combination. This endpoint is supported on Ethereum, Solana, and 30+ EVM chains. See the full list of supported networks [here](https://dashboard.alchemy.com/chains) + */ + post: operations["get-token-balances-by-address"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{apiKey}/assets/nfts/by-address": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * NFTs By Wallet + * @description Fetches NFTs for multiple wallet addresses and networks. Returns a list of NFTs and metadata for each wallet/network combination. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + post: operations["get-nfts-by-address"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{apiKey}/assets/nfts/contracts/by-address": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * NFT Collections By Wallet + * @description Fetches NFT collections (contracts) for multiple wallet addresses and networks. Returns a list of collections and metadata for each wallet/network combination. This endpoint is supported on Ethereum and many L2s, including Polygon, Arbitrum, Optimism, Base, World Chain and more. See the full list of supported networks [here](https://dashboard.alchemy.com/chains). + */ + post: operations["get-nft-contracts-by-address"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + ByAddressRequest: { + /** @description Array of wallet addresses and the networks to query them on. Maximum 2 addresses and maximum 5 networks per address. */ + addresses: { + /** + * @description Wallet address. + * @default 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + * @example 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + */ + address: string; + /** + * @description Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default [ + * "eth-mainnet", + * "base-mainnet", + * "matic-mainnet" + * ] + */ + networks: string[]; + }[]; + }; + ByAddressRequestWith1AddressAnd2Networks: { + /** @description Array of address and networks pairs (limit 1 pairs, max 2 networks). Networks should match network enums. */ + addresses: { + /** + * @description Wallet address. + * @default 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + * @example 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + */ + address: string; + /** + * @description (In BETA and only accepts ETH & BASE mainnets). Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default [ + * "eth-mainnet", + * "base-mainnet" + * ] + */ + networks: string[]; + }[]; + /** @description The cursor that points to the previous set of results. */ + before?: string; + /** @description The cursor that points to the end of the current set of results. */ + after?: string; + /** + * @description Sets the maximum number of items per page (Max: 50) + * @default 25 + */ + limit: number; + pageKey?: string; + }; + ByAddressRequestWith3PairsAnd20Networks: { + /** @description Array of address and networks pairs (limit 3 pairs, max 20 networks). Networks should match network enums. */ + addresses: { + /** + * @description Wallet address. + * @default 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + * @example 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + */ + address: string; + /** + * @description Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default [ + * "eth-mainnet", + * "base-mainnet", + * "matic-mainnet" + * ] + */ + networks: string[]; + }[]; + /** + * @description Whether to include each chain's native token in the response (e.g. ETH on Ethereum). The native token will have a null contract address. + * @default true + * @example true + */ + includeNativeTokens: boolean; + /** + * @description Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @default true + */ + includeErc20Tokens: boolean; + }; + ByAddressRequestWithOptions: { + /** @description Array of wallet addresses and the networks to query them on. Maximum 2 addresses and maximum 5 networks per address. */ + addresses: { + /** + * @description Wallet address. + * @default 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + * @example 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + */ + address: string; + /** + * @description Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default [ + * "eth-mainnet", + * "base-mainnet", + * "matic-mainnet" + * ] + */ + networks: string[]; + }[]; + /** + * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @default true + */ + withMetadata: boolean; + /** + * @description Boolean - if set to `true`, returns token prices. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @default true + */ + withPrices: boolean; + /** + * @description Whether to include each chain's native token in the response (e.g. ETH on Ethereum). The native token will have a null contract address. + * @default true + * @example true + */ + includeNativeTokens: boolean; + /** + * @description Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @default true + */ + includeErc20Tokens: boolean; + }; + ByAddressRequestWithNFTOptionsAndPaging: { + /** @description Array of address and networks pairs (limit 2 pairs, max 15 networks each). Networks should match network enums. */ + addresses: { + /** + * @description Wallet address. + * @default 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + * @example 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + */ + address: string; + /** + * @description Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default [ + * "eth-mainnet", + * "base-mainnet", + * "matic-mainnet" + * ] + */ + networks: ( + | "eth-mainnet" + | "eth-sepolia" + | "eth-holesky" + | "avax-mainnet" + | "avax-fuji" + | "zksync-mainnet" + | "opt-mainnet" + | "polygon-mainnet" + | "polygon-amoy" + | "arb-mainnet" + | "arb-sepolia" + | "blast-mainnet" + | "blast-sepolia" + | "base-mainnet" + | "base-sepolia" + | "soneium-mainnet" + | "soneium-minato" + | "scroll-mainnet" + | "scroll-sepolia" + | "shape-mainnet" + | "shape-sepolia" + | "lens-mainnet" + | "lens-sepolia" + | "starknet-mainnet" + | "starknet-sepolia" + | "rootstock-mainnet" + | "rootstock-testnet" + | "linea-mainnet" + | "linea-sepolia" + | "settlus-septestnet" + | "abstract-mainnet" + | "abstract-testnet" + | "apechain-mainnet" + )[]; + excludeFilters?: ("SPAM" | "AIRDROPS")[]; + includeFilters?: ("SPAM" | "AIRDROPS")[]; + /** @enum {string} */ + spamConfidenceLevel?: "VERY_HIGH" | "HIGH" | "MEDIUM" | "LOW"; + }[]; + /** + * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @default true + */ + withMetadata: boolean; + pageKey?: string; + /** @default 100 */ + pageSize: number; + }; + ByAddressRequestWithNFTOptions: { + /** @description Array of wallet addresses and the networks to query them on. Maximum 2 addresses and maximum 5 networks per address. */ + addresses: { + /** + * @description Wallet address. + * @default 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + * @example 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + */ + address: string; + /** + * @description Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default [ + * "eth-mainnet", + * "base-mainnet", + * "matic-mainnet" + * ] + */ + networks: string[]; + }[]; + /** + * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @default true + */ + withMetadata: boolean; + }; + AddressItemForNFTOwnership: { + /** + * @description Wallet address. + * @default 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + * @example 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + */ + address: string; + /** + * @description Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default [ + * "eth-mainnet", + * "base-mainnet", + * "matic-mainnet" + * ] + */ + networks: ( + | "eth-mainnet" + | "eth-sepolia" + | "eth-holesky" + | "avax-mainnet" + | "avax-fuji" + | "zksync-mainnet" + | "opt-mainnet" + | "polygon-mainnet" + | "polygon-amoy" + | "arb-mainnet" + | "arb-sepolia" + | "blast-mainnet" + | "blast-sepolia" + | "base-mainnet" + | "base-sepolia" + | "soneium-mainnet" + | "soneium-minato" + | "scroll-mainnet" + | "scroll-sepolia" + | "shape-mainnet" + | "shape-sepolia" + | "lens-mainnet" + | "lens-sepolia" + | "starknet-mainnet" + | "starknet-sepolia" + | "rootstock-mainnet" + | "rootstock-testnet" + | "linea-mainnet" + | "linea-sepolia" + | "settlus-septestnet" + | "abstract-mainnet" + | "abstract-testnet" + | "apechain-mainnet" + )[]; + excludeFilters?: ("SPAM" | "AIRDROPS")[]; + includeFilters?: ("SPAM" | "AIRDROPS")[]; + /** @enum {string} */ + spamConfidenceLevel?: "VERY_HIGH" | "HIGH" | "MEDIUM" | "LOW"; + }; + TokensResponse: { + /** @description List of tokens by address, with prices and metadata. */ + data: { + tokens?: { + /** @description Wallet address. */ + address: string; + /** @description Network identifier. */ + network: string; + /** @description Token address. */ + tokenAddress: string; + /** @description Balance of that particular token. */ + tokenBalance: string; + tokenMetadata?: { + /** @description Number of decimals the token uses */ + decimals?: number; + /** @description URL of the token's logo image */ + logo?: string; + /** @description Token's name */ + name?: string; + /** @description Token's symbol */ + symbol?: string; + }; + /** @description List of price information. */ + tokenPrices?: { + /** @example usd */ + currency: string; + /** @example 4608.2208671202 */ + value: string; + /** + * Format: date-time + * @example 2025-08-26T20:17:27Z + */ + lastUpdatedAt: string; + }[]; + /** @description Error message if applicable. */ + error?: string | null; + }[]; + pageKey?: string; + }; + }; + TokenBalancesResponse: { + data: { + /** @description List of token balances by address. */ + tokens?: { + /** @description Network identifier. */ + network: string; + /** @description Wallet address. */ + address: string; + /** @description Token address. */ + tokenAddress: string; + /** @description Balance of that particular token. */ + tokenBalance: string; + }[]; + pageKey?: string; + }; + }; + NFTByOwnerResponse: { + /** @description List of nfts by address with appropriate metadata. */ + data: { + ownedNfts?: { + /** @description Network identifier. */ + network?: string; + /** @description Wallet address. */ + address?: string; + /** @description The contract object that has details of a contract */ + contract?: { + /** @description Address of the held contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + /** @default 44 */ + tokenId: string; + tokenType?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Brief human-readable description */ + description?: string; + /** @description Details of the image corresponding to this contract */ + image?: { + /** @description The Url of the image stored in Alchemy cache */ + cachedUrl?: string; + /** @description The Url that has the thumbnail version of the NFT */ + thumbnailUrl?: string; + /** @description The Url that has the NFT image in png */ + pngUrl?: string; + /** @description The Url of the image stored in Alchemy cache */ + contentType?: string; + /** @description The size of the media asset in bytes. */ + size?: number; + /** @description The original Url of the image coming straight from the smart contract */ + originalUrl?: string; + }; + /** @description Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract */ + raw?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + }; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + }; + /** @description The collection object that has details of a collection */ + collection?: { + /** @description String - Collection name */ + name?: string; + /** @description String - OpenSea collection slug */ + slug?: string; + /** @description String - URL for the external site of the collection */ + externalUrl?: string; + /** @description String - Banner image URL for the collection */ + bannerImageUrl?: string; + }; + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + /** @description Only present if the request specified `orderBy=transferTime`. */ + acquiredAt?: { + /** @description Block timestamp of the block where the NFT was most recently acquired. */ + blockTimestamp?: string; + /** @description Block number of the block where the NFT was most recently acquired. */ + blockNumber?: string; + }; + }[]; + /** @description Integer - Total number of NFTs (distinct `tokenIds`) owned by the given address. */ + totalCount?: number; + pageKey?: string; + }; + }; + NFTCollectionsByOwnerResponse: { + /** @description List of nft collections. */ + data: { + contracts?: { + /** @description Network identifier. */ + network?: string; + /** @description Wallet address. */ + address?: string; + /** @description The contract object that has details of a contract */ + contract?: { + /** @description Address of the held contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + }[]; + /** @description Integer - Total number of NFTs (distinct `tokenIds`) owned by the given address. */ + totalCount?: number; + pageKey?: string; + }; + }; + /** @description The object that represents an NFT and has all data corresponding to that NFT */ + NFTResponseItem: { + /** @description Network identifier. */ + network?: string; + /** @description Wallet address. */ + address?: string; + /** @description The contract object that has details of a contract */ + contract?: { + /** @description Address of the held contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + /** @default 44 */ + tokenId: string; + tokenType?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Brief human-readable description */ + description?: string; + /** @description Details of the image corresponding to this contract */ + image?: { + /** @description The Url of the image stored in Alchemy cache */ + cachedUrl?: string; + /** @description The Url that has the thumbnail version of the NFT */ + thumbnailUrl?: string; + /** @description The Url that has the NFT image in png */ + pngUrl?: string; + /** @description The Url of the image stored in Alchemy cache */ + contentType?: string; + /** @description The size of the media asset in bytes. */ + size?: number; + /** @description The original Url of the image coming straight from the smart contract */ + originalUrl?: string; + }; + /** @description Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract */ + raw?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + }; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + }; + /** @description The collection object that has details of a collection */ + collection?: { + /** @description String - Collection name */ + name?: string; + /** @description String - OpenSea collection slug */ + slug?: string; + /** @description String - URL for the external site of the collection */ + externalUrl?: string; + /** @description String - Banner image URL for the collection */ + bannerImageUrl?: string; + }; + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + /** @description Only present if the request specified `orderBy=transferTime`. */ + acquiredAt?: { + /** @description Block timestamp of the block where the NFT was most recently acquired. */ + blockTimestamp?: string; + /** @description Block number of the block where the NFT was most recently acquired. */ + blockNumber?: string; + }; + }; + /** @description The object that represents an NFT collection */ + NFTCollectionResponseItem: { + /** @description Network identifier. */ + network?: string; + /** @description Wallet address. */ + address?: string; + /** @description The contract object that has details of a contract */ + contract?: { + /** @description Address of the held contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + }; + TransactionHistoryResponse: { + /** @description The cursor that points to the previous set of results. */ + before?: string; + /** @description The cursor that points to the end of the current set of results. */ + after?: string; + /** @description Total count of the response items. */ + totalCount?: number; + /** @description List of transactions by address. */ + transactions: { + /** @description Network associated with an individual transaction */ + network?: string; + /** @description Transaction hash */ + hash?: string; + /** @description (ISO 8601) Timestamp of transaction mining / confirmation */ + timeStamp?: string; + /** @description Block number of transaction mining / confirmation */ + blockNumber?: number; + /** @description Block hash of transaction mining / confirmation */ + blockHash?: string; + /** @description Transaction nonce */ + nonce?: number; + /** @description Position of transaction within a block */ + transactionIndex?: number; + /** @description From address of transaction (hex string). */ + fromAddress?: string; + /** @description To address of transaction (hex string). null if contract creation. */ + toAddress?: string; + /** @description 20 Bytes - The contract address created, if the transaction was a contract creation, otherwise null */ + contractAddress?: string; + /** @description (uint8) Value of native token value moved within the external transaction */ + value?: string; + /** @description The total amount of gas used when this transaction was executed in the block. */ + cumulativeGasUsed?: string; + /** @description Gas price parameter */ + effectiveGasPrice?: string; + /** @description The amount of gas used by this specific transaction alone */ + gasUsed?: string; + /** @description Array of log objects, which this transaction generated */ + logs?: { + /** @description 20 Bytes - contract address from which this log originated. */ + contractAddress?: string; + /** @description Integer of the log index position in the block. null when its pending log. */ + logIndex?: string; + /** @description Contains one or more 32 Bytes non-indexed arguments of the log. */ + data?: string; + /** @description true when the log was removed, due to a chain reorganization. false if its a valid log. */ + removed?: boolean; + /** @description Array of zero to four 32 Bytes DATA of indexed log arguments */ + topics?: string[]; + }[]; + /** @description Array of internal transaction objects, which this transaction generated */ + internalTxns?: { + /** @description CALL or CREATE */ + type?: string; + /** @description 20 Bytes - address of the sender */ + fromAddress?: string; + /** @description 20 Bytes - address of the receiver. null when its a contract creation transaction */ + toAddress?: string; + /** @description amount of value for transfer (in hex) */ + value?: string; + /** @description amount of gas provided for the call (in hex) */ + gas?: string; + /** @description amount of gas used during the call (in hex) */ + gasUsed?: string; + /** @description call data */ + input?: string; + /** @description return data */ + output?: string; + /** @description error, if any */ + error?: string; + /** @description solidity revert reason, if any */ + revertReason?: string; + }[]; + }[]; + }; + TransactionHistoryResponseItem: { + /** @description Network associated with an individual transaction */ + network?: string; + /** @description Transaction hash */ + hash?: string; + /** @description (ISO 8601) Timestamp of transaction mining / confirmation */ + timeStamp?: string; + /** @description Block number of transaction mining / confirmation */ + blockNumber?: number; + /** @description Block hash of transaction mining / confirmation */ + blockHash?: string; + /** @description Transaction nonce */ + nonce?: number; + /** @description Position of transaction within a block */ + transactionIndex?: number; + /** @description From address of transaction (hex string). */ + fromAddress?: string; + /** @description To address of transaction (hex string). null if contract creation. */ + toAddress?: string; + /** @description 20 Bytes - The contract address created, if the transaction was a contract creation, otherwise null */ + contractAddress?: string; + /** @description (uint8) Value of native token value moved within the external transaction */ + value?: string; + /** @description The total amount of gas used when this transaction was executed in the block. */ + cumulativeGasUsed?: string; + /** @description Gas price parameter */ + effectiveGasPrice?: string; + /** @description The amount of gas used by this specific transaction alone */ + gasUsed?: string; + /** @description Array of log objects, which this transaction generated */ + logs?: { + /** @description 20 Bytes - contract address from which this log originated. */ + contractAddress?: string; + /** @description Integer of the log index position in the block. null when its pending log. */ + logIndex?: string; + /** @description Contains one or more 32 Bytes non-indexed arguments of the log. */ + data?: string; + /** @description true when the log was removed, due to a chain reorganization. false if its a valid log. */ + removed?: boolean; + /** @description Array of zero to four 32 Bytes DATA of indexed log arguments */ + topics?: string[]; + }[]; + /** @description Array of internal transaction objects, which this transaction generated */ + internalTxns?: { + /** @description CALL or CREATE */ + type?: string; + /** @description 20 Bytes - address of the sender */ + fromAddress?: string; + /** @description 20 Bytes - address of the receiver. null when its a contract creation transaction */ + toAddress?: string; + /** @description amount of value for transfer (in hex) */ + value?: string; + /** @description amount of gas provided for the call (in hex) */ + gas?: string; + /** @description amount of gas used during the call (in hex) */ + gasUsed?: string; + /** @description call data */ + input?: string; + /** @description return data */ + output?: string; + /** @description error, if any */ + error?: string; + /** @description solidity revert reason, if any */ + revertReason?: string; + }[]; + }; + TokenPricesResponse: { + /** @description List of token price data. */ + data: { + /** @description Token symbol. */ + symbol: string; + /** @description List of price information. */ + prices: { + /** @description Currency code (e.g., USD). */ + currency: string; + /** @description Price value as a string. */ + value: string; + /** + * Format: date-time + * @description Time when the price was last updated. + */ + lastUpdatedAt: string; + }[]; + /** @description Error message if applicable. */ + error: string | null; + }[]; + }; + TokenPriceResponseItem: { + /** @description Token symbol. */ + symbol: string; + /** @description List of price information. */ + prices: { + /** @description Currency code (e.g., USD). */ + currency: string; + /** @description Price value as a string. */ + value: string; + /** + * Format: date-time + * @description Time when the price was last updated. + */ + lastUpdatedAt: string; + }[]; + /** @description Error message if applicable. */ + error: string | null; + }; + BlockTimestampResponse: { + /** @description List of blocks */ + data: { + /** @description Network identifier */ + network: string; + block: { + /** @description Block number */ + number: number; + /** @description ISO timestamp of the block */ + timestamp: string; + }; + }[]; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + "get-tokens-by-address": { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "addresses": [ + * { + * "address": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + * "networks": [ + * "eth-mainnet", + * "base-mainnet", + * "matic-mainnet" + * ] + * } + * ], + * "withMetadata": true, + * "withPrices": true, + * "includeNativeTokens": true, + * "includeErc20Tokens": false + * } + */ + "application/json": { + /** @description Array of wallet addresses and the networks to query them on. Maximum 2 addresses and maximum 5 networks per address. */ + addresses: { + /** + * @description Wallet address. + * @default 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + * @example 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + */ + address: string; + /** + * @description Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default [ + * "eth-mainnet", + * "base-mainnet", + * "matic-mainnet" + * ] + */ + networks: string[]; + }[]; + /** + * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @default true + */ + withMetadata?: boolean; + /** + * @description Boolean - if set to `true`, returns token prices. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @default true + */ + withPrices?: boolean; + /** + * @description Whether to include each chain's native token in the response (e.g. ETH on Ethereum). The native token will have a null contract address. + * @default true + * @example true + */ + includeNativeTokens?: boolean; + /** + * @description Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @default true + */ + includeErc20Tokens?: boolean; + }; + }; + }; + responses: { + /** @description Successful response! */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of tokens by address, with prices and metadata. */ + data: { + tokens?: { + /** @description Wallet address. */ + address: string; + /** @description Network identifier. */ + network: string; + /** @description Token address. */ + tokenAddress: string; + /** @description Balance of that particular token. */ + tokenBalance: string; + tokenMetadata?: { + /** @description Number of decimals the token uses */ + decimals?: number; + /** @description URL of the token's logo image */ + logo?: string; + /** @description Token's name */ + name?: string; + /** @description Token's symbol */ + symbol?: string; + }; + /** @description List of price information. */ + tokenPrices?: { + /** @example usd */ + currency: string; + /** @example 4608.2208671202 */ + value: string; + /** + * Format: date-time + * @example 2025-08-26T20:17:27Z + */ + lastUpdatedAt: string; + }[]; + /** @description Error message if applicable. */ + error?: string | null; + }[]; + pageKey?: string; + }; + }; + }; + }; + /** @description Bad Request: Invalid input (e.g., malformed JSON). */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + /** @description Too Many Requests: Rate limit exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + }; + }; + "get-token-balances-by-address": { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "addresses": [ + * { + * "address": "0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152", + * "networks": [ + * "eth-mainnet", + * "base-mainnet", + * "matic-mainnet" + * ] + * } + * ], + * "includeNativeTokens": true, + * "includeErc20Tokens": false + * } + */ + "application/json": { + /** @description Array of address and networks pairs (limit 3 pairs, max 20 networks). Networks should match network enums. */ + addresses: { + /** + * @description Wallet address. + * @default 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + * @example 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + */ + address: string; + /** + * @description Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default [ + * "eth-mainnet", + * "base-mainnet", + * "matic-mainnet" + * ] + */ + networks: string[]; + }[]; + /** + * @description Whether to include each chain's native token in the response (e.g. ETH on Ethereum). The native token will have a null contract address. + * @default true + * @example true + */ + includeNativeTokens?: boolean; + /** + * @description Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @default true + */ + includeErc20Tokens?: boolean; + }; + }; + }; + responses: { + /** @description Successful response! */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: { + /** @description List of token balances by address. */ + tokens?: { + /** @description Network identifier. */ + network: string; + /** @description Wallet address. */ + address: string; + /** @description Token address. */ + tokenAddress: string; + /** @description Balance of that particular token. */ + tokenBalance: string; + }[]; + pageKey?: string; + }; + }; + }; + }; + /** @description Bad Request: Invalid input (e.g., malformed JSON). */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + /** @description Too Many Requests: Rate limit exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + }; + }; + "get-nfts-by-address": { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Array of address and networks pairs (limit 2 pairs, max 15 networks each). Networks should match network enums. */ + addresses: { + /** + * @description Wallet address. + * @default 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + * @example 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + */ + address: string; + /** + * @description Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default [ + * "eth-mainnet", + * "base-mainnet", + * "matic-mainnet" + * ] + */ + networks: ( + | "eth-mainnet" + | "eth-sepolia" + | "eth-holesky" + | "avax-mainnet" + | "avax-fuji" + | "zksync-mainnet" + | "opt-mainnet" + | "polygon-mainnet" + | "polygon-amoy" + | "arb-mainnet" + | "arb-sepolia" + | "blast-mainnet" + | "blast-sepolia" + | "base-mainnet" + | "base-sepolia" + | "soneium-mainnet" + | "soneium-minato" + | "scroll-mainnet" + | "scroll-sepolia" + | "shape-mainnet" + | "shape-sepolia" + | "lens-mainnet" + | "lens-sepolia" + | "starknet-mainnet" + | "starknet-sepolia" + | "rootstock-mainnet" + | "rootstock-testnet" + | "linea-mainnet" + | "linea-sepolia" + | "settlus-septestnet" + | "abstract-mainnet" + | "abstract-testnet" + | "apechain-mainnet" + )[]; + excludeFilters?: ("SPAM" | "AIRDROPS")[]; + includeFilters?: ("SPAM" | "AIRDROPS")[]; + /** @enum {string} */ + spamConfidenceLevel?: "VERY_HIGH" | "HIGH" | "MEDIUM" | "LOW"; + }[]; + /** + * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @default true + */ + withMetadata?: boolean; + pageKey?: string; + /** @default 100 */ + pageSize?: number; + } & { + /** + * @description Field to order results by + * @enum {string} + */ + orderBy?: "transferTime"; + /** + * @description Sort order for results + * @enum {string} + */ + sortOrder?: "asc" | "desc"; + }; + }; + }; + responses: { + /** @description Successful response! */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of nfts by address with appropriate metadata. */ + data: { + ownedNfts?: { + /** @description Network identifier. */ + network?: string; + /** @description Wallet address. */ + address?: string; + /** @description The contract object that has details of a contract */ + contract?: { + /** @description Address of the held contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + /** @default 44 */ + tokenId: string; + tokenType?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Brief human-readable description */ + description?: string; + /** @description Details of the image corresponding to this contract */ + image?: { + /** @description The Url of the image stored in Alchemy cache */ + cachedUrl?: string; + /** @description The Url that has the thumbnail version of the NFT */ + thumbnailUrl?: string; + /** @description The Url that has the NFT image in png */ + pngUrl?: string; + /** @description The Url of the image stored in Alchemy cache */ + contentType?: string; + /** @description The size of the media asset in bytes. */ + size?: number; + /** @description The original Url of the image coming straight from the smart contract */ + originalUrl?: string; + }; + /** @description Raw details of the NFT like its tokenUri and metadata info obtained directly from the smart contract */ + raw?: { + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description Relevant metadata for NFT contract. This is useful for viewing image url, traits, etc. without having to follow the metadata url in tokenUri to parse manually. */ + metadata?: { + /** @description String - URL to the NFT asset image. Can be standard URLs pointing to images on conventional servers, IPFS, or Arweave. Most types of images (SVGs, PNGs, JPEGs, etc.) are supported by NFT marketplaces. */ + image?: string; + /** @description String - Name of the NFT asset. */ + name?: string; + /** @description String - Human-readable description of the NFT asset. (Markdown is supported/rendered on OpenSea and other NFT platforms) */ + description?: string; + /** @description Object - Traits/attributes/characteristics for each NFT asset. */ + attributes?: { + value?: string; + trait_type?: string; + }[]; + }; + /** @description String - A string describing a particular reason that we were unable to fetch complete metadata for the NFT. */ + error?: string; + }; + /** @description The collection object that has details of a collection */ + collection?: { + /** @description String - Collection name */ + name?: string; + /** @description String - OpenSea collection slug */ + slug?: string; + /** @description String - URL for the external site of the collection */ + externalUrl?: string; + /** @description String - Banner image URL for the collection */ + bannerImageUrl?: string; + }; + /** @description String - Uri representing the location of the NFT's original metadata blob. This is a backup for you to parse when the metadata field is not automatically populated. */ + tokenUri?: string; + /** @description String - ISO timestamp of the last cache refresh for the information returned in the metadata field. */ + timeLastUpdated?: string; + /** @description Only present if the request specified `orderBy=transferTime`. */ + acquiredAt?: { + /** @description Block timestamp of the block where the NFT was most recently acquired. */ + blockTimestamp?: string; + /** @description Block number of the block where the NFT was most recently acquired. */ + blockNumber?: string; + }; + }[]; + /** @description Integer - Total number of NFTs (distinct `tokenIds`) owned by the given address. */ + totalCount?: number; + pageKey?: string; + }; + }; + }; + }; + /** @description Bad Request: Invalid input (e.g., malformed JSON). */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + /** @description Too Many Requests: Rate limit exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + }; + }; + "get-nft-contracts-by-address": { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Array of address and networks pairs (limit 2 pairs, max 15 networks each). Networks should match network enums. */ + addresses: { + /** + * @description Wallet address. + * @default 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + * @example 0x1E6E8695FAb3Eb382534915eA8d7Cc1D1994B152 + */ + address: string; + /** + * @description Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default [ + * "eth-mainnet", + * "base-mainnet", + * "matic-mainnet" + * ] + */ + networks: ( + | "eth-mainnet" + | "eth-sepolia" + | "eth-holesky" + | "avax-mainnet" + | "avax-fuji" + | "zksync-mainnet" + | "opt-mainnet" + | "polygon-mainnet" + | "polygon-amoy" + | "arb-mainnet" + | "arb-sepolia" + | "blast-mainnet" + | "blast-sepolia" + | "base-mainnet" + | "base-sepolia" + | "soneium-mainnet" + | "soneium-minato" + | "scroll-mainnet" + | "scroll-sepolia" + | "shape-mainnet" + | "shape-sepolia" + | "lens-mainnet" + | "lens-sepolia" + | "starknet-mainnet" + | "starknet-sepolia" + | "rootstock-mainnet" + | "rootstock-testnet" + | "linea-mainnet" + | "linea-sepolia" + | "settlus-septestnet" + | "abstract-mainnet" + | "abstract-testnet" + | "apechain-mainnet" + )[]; + excludeFilters?: ("SPAM" | "AIRDROPS")[]; + includeFilters?: ("SPAM" | "AIRDROPS")[]; + /** @enum {string} */ + spamConfidenceLevel?: "VERY_HIGH" | "HIGH" | "MEDIUM" | "LOW"; + }[]; + /** + * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @default true + */ + withMetadata?: boolean; + pageKey?: string; + /** @default 100 */ + pageSize?: number; + } & { + /** + * @description Field to order results by + * @enum {string} + */ + orderBy?: "transferTime"; + /** + * @description Sort order for results + * @enum {string} + */ + sortOrder?: "asc" | "desc"; + }; + }; + }; + responses: { + /** @description Successful response! */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of nft collections. */ + data: { + contracts?: { + /** @description Network identifier. */ + network?: string; + /** @description Wallet address. */ + address?: string; + /** @description The contract object that has details of a contract */ + contract?: { + /** @description Address of the held contract */ + address?: string; + /** @description String - NFT contract name. */ + name?: string; + /** @description String - NFT contract symbol abbreviation. */ + symbol?: string; + /** @description String - Total number of NFTs in a given NFT collection. */ + totalSupply?: string; + /** + * @description String - For valid NFTs, 'ERC721' or 'ERC1155.' For invalid NFTs, a descriptive reason such as 'NO_SUPPORTED_NFT_STANDARD' if the input contract address doesn't support a known NFT standard, or 'NOT_A_CONTRACT' if there is no contract deployed at the input address. + * @enum {string} + */ + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + /** @description String - Address that deployed the smart contract */ + contractDeployer?: string; + /** @description Number - The Block Number when the deployment transaction is successfully mined */ + deployedBlockNumber?: number; + /** @description Note that the OpenSea metadata object is currently only available on ETH and Polygon Mainnet. Please reach out to us at support@alchemy.com if you would like to access this data on other networks. */ + openseaMetadata?: { + /** @description NFT floor price */ + floorPrice?: number; + /** @description OpenSea collection name */ + collectionName?: string; + /** @description Collection approval status within OpenSea. For more info, see the Opensea docs at docs.opensea.io/reference/collection-model */ + safelistRequestStatus?: string; + /** @description OpenSea CDN image URL */ + imageUrl?: string; + /** @description OpenSea collection description. Note: this value is truncated to 255 characters. */ + description?: string; + /** @description Collection homepage */ + externalUrl?: string; + /** @description The twitter username of the collection */ + twitterUsername?: string; + /** @description The discord URL of the collection */ + discordUrl?: string; + /** @description The banner image URL of the collection */ + bannerImageUrl?: string; + /** @description The timestamp when the collection was last ingested by us */ + lastIngestedAt?: string; + }; + /** @description "true" if contract is spam, else "false". **Only available on paid tiers.** */ + isSpam?: string; + /** @description List of reasons why a contract was classified as spam. **Only available on paid tiers.** */ + spamClassifications?: string[]; + }; + }[]; + /** @description Integer - Total number of NFTs (distinct `tokenIds`) owned by the given address. */ + totalCount?: number; + pageKey?: string; + }; + }; + }; + }; + /** @description Bad Request: Invalid input (e.g., malformed JSON). */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + /** @description Too Many Requests: Rate limit exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + }; + }; +} diff --git a/packages/data-apis/src/generated/rpc/transfers.ts b/packages/data-apis/src/generated/rpc/transfers.ts new file mode 100644 index 0000000000..9cb54064dc --- /dev/null +++ b/packages/data-apis/src/generated/rpc/transfers.ts @@ -0,0 +1,125 @@ +/* eslint-disable -- machine-generated file */ +// AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. +// Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) +// is recorded in packages/api-codegen/specs/specs.lock.json. + +export interface AlchemyGetAssetTransfersParams { + fromBlock?: string; + toBlock?: string; + fromAddress?: string; + toAddress?: string; + excludeZeroValue?: boolean; + /** + * Array of transfer categories to include. Note: 'internal' category is not supported on Base, it is only available on Ethereum Mainnet and Polygon Mainnet. + */ + category?: ( + | "external" + | "internal" + | "erc20" + | "erc721" + | "erc1155" + | "specialnft" + )[]; + /** + * An array of contract addresses to filter for. + */ + contractAddresses?: string[]; + order?: "asc" | "desc"; + /** + * Available only on ETH, Base, Polygon, Arbitrum, Optimism + */ + withMetadata?: boolean; + maxCount?: string; + pageKey?: string; +} + +export type AlchemyGetAssetTransfersResult = + | NotFoundNull + | { + /** + * Uuid of next page of results (if exists, else blank). + */ + pageKey?: string; + /** + * Array of transfer objects sorted in ascending or descending order by block number. + */ + transfers?: { + /** + * 'external', 'internal', 'token', 'erc20', 'erc721', 'erc1155', 'specialnft' + */ + category?: string; + /** + * Block number of the transfer (hex string). + */ + blockNum?: string; + /** + * From address (hex string). + */ + from?: string; + /** + * To address (hex string). null if contract creation. + */ + to?: string; + /** + * Asset transfer value. null if it's an ERC721 or unknown decimals. + */ + value?: number; + /** + * (Deprecated) Legacy token ID field for ERC721 tokens. Use `tokenId` instead. + */ + erc721TokenId?: string; + /** + * Array of objects with (tokenId, value) for ERC1155 transfers, null otherwise. + */ + erc1155Metadata?: { + tokenId?: string; + value?: string; + }[]; + /** + * Token ID for NFT tokens (ERC721, ERC1155, etc.). + */ + tokenId?: string; + /** + * ETH or the token's symbol. null if unavailable. + */ + asset?: string; + /** + * Unique identifier for the transfer object. + */ + uniqueId?: string; + /** + * Transaction hash (hex string). + */ + hash?: string; + rawContract?: { + /** + * Raw hex transfer value. null for NFT transfers. + */ + value?: string; + /** + * Contract address (hex string). null for external or internal transfers. + */ + address?: string; + /** + * Contract decimal in hex. null if not known. + */ + decimal?: string; + }; + metadata?: { + /** + * ISO-formatted timestamp of the block containing this transfer. (Available only on ETH, Base, Polygon, Arbitrum, Optimism) + */ + blockTimestamp?: string; + }; + }[]; + }; +export type NotFoundNull = string; + +/** viem RpcSchema entries for the transfers JSON-RPC methods. */ +export type TransfersRpcSchema = [ + { + Method: "alchemy_getAssetTransfers"; + Parameters: [assetTransferParams: AlchemyGetAssetTransfersParams]; + ReturnType: AlchemyGetAssetTransfersResult; + }, +]; diff --git a/packages/data-apis/src/schema/rest.ts b/packages/data-apis/src/schema/rest.ts index ec748ec1de..a725eb72c5 100644 --- a/packages/data-apis/src/schema/rest.ts +++ b/packages/data-apis/src/schema/rest.ts @@ -1,42 +1,5 @@ -import type { RestRequestSchema } from "@alchemy/common"; -import type { - GetNftsForOwnerResult, - GetTokensByAddressResult, -} from "../types.js"; - -// NOTE(mvp): hand-written RestRequestSchema entries; the production plan -// generates these from the docs repo's bundled OpenAPI specs via -// `pnpm generate:sdk` (openapi-typescript), keyed by the SDK manifest. - -/** Routes served by the global Data API (https://api.g.alchemy.com/data/v1). */ -export type PortfolioRestSchema = readonly [ - { - Route: "assets/tokens/by-address"; - Method: "POST"; - Body: { - addresses: Array<{ address: string; networks: string[] }>; - withMetadata?: boolean; - withPrices?: boolean; - includeNativeTokens?: boolean; - includeErc20Tokens?: boolean; - }; - Response: GetTokensByAddressResult; - }, -]; - -/** Routes served by the network-scoped NFT API ({network}.g.alchemy.com/nft/v3). */ -export type NftRestSchema = readonly [ - { - Route: "getNFTsForOwner"; - Method: "GET"; - Body?: undefined; - Response: GetNftsForOwnerResult; - }, -]; - -// Compile-time check that the schemas satisfy the shared constraint. -type _AssertPortfolio = PortfolioRestSchema extends RestRequestSchema - ? true - : never; -type _AssertNft = NftRestSchema extends RestRequestSchema ? true : never; -export type _SchemaAssertions = [_AssertPortfolio, _AssertNft]; +// Generated by @alchemy/api-codegen from the docs repo's bundled OpenAPI +// specs (see src/generated/). Re-exported here so the public import path is +// stable regardless of how generation is organized internally. +export type { NftRestSchema } from "../generated/rest/nft.schema.js"; +export type { PortfolioRestSchema } from "../generated/rest/portfolio.schema.js"; diff --git a/packages/data-apis/src/schema/rpc.ts b/packages/data-apis/src/schema/rpc.ts index 6894686cd5..fb3b5fec9d 100644 --- a/packages/data-apis/src/schema/rpc.ts +++ b/packages/data-apis/src/schema/rpc.ts @@ -1,30 +1,22 @@ +import type { AlchemyGetAssetTransfersParams } from "../generated/rpc/transfers.js"; import type { GetAssetTransfersResult } from "../types.js"; -// NOTE(mvp): hand-written RpcSchema entry; the production plan generates these -// from the docs repo's bundled OpenRPC specs (alchemy_getAssetTransfers et al). +// Params/result internals are generated by @alchemy/api-codegen from the docs +// repo's bundled OpenRPC specs (see src/generated/rpc/). -export type GetAssetTransfersRpcParams = { - fromBlock?: string; - toBlock?: string; - fromAddress?: string; - toAddress?: string; - excludeZeroValue?: boolean; - category: string[]; - contractAddresses?: string[]; - order?: "asc" | "desc"; - withMetadata?: boolean; - pageKey?: string; - maxCount?: string; -}; +export type GetAssetTransfersRpcParams = AlchemyGetAssetTransfersParams; /** * viem RpcSchema entries for the Data JSON-RPC methods. Attach to a client to * get typed `client.request({ method: "alchemy_getAssetTransfers", ... })`. + * + * ReturnType uses the SDK's {@link GetAssetTransfersResult}, which collapses + * the spec's "Not Found (null)" string branch (a docs-spec artifact). */ export type DataRpcSchema = [ { Method: "alchemy_getAssetTransfers"; - Parameters: [GetAssetTransfersRpcParams]; + Parameters: [AlchemyGetAssetTransfersParams]; ReturnType: GetAssetTransfersResult; }, ]; diff --git a/packages/data-apis/src/types.ts b/packages/data-apis/src/types.ts index 9c1dffdf06..6c848d7b1d 100644 --- a/packages/data-apis/src/types.ts +++ b/packages/data-apis/src/types.ts @@ -1,9 +1,22 @@ import type { NetworkInput } from "@alchemy/common"; +import type { + GetNftsForOwnerQuery, + GetNftsForOwnerResponse, +} from "./generated/rest/nft.schema.js"; +import type { + GetTokensByAddressBody, + GetTokensByAddressResponse, +} from "./generated/rest/portfolio.schema.js"; +import type { + AlchemyGetAssetTransfersParams, + AlchemyGetAssetTransfersResult, +} from "./generated/rpc/transfers.js"; -// NOTE(mvp): these param/result types are hand-written against the docs specs -// to prove the architecture. The production plan generates them from the docs -// repo's bundled OpenAPI/OpenRPC output (see data-sdk-scope-plan.md), with -// reviewed, semver-bearing public names aliasing generated internals. +// Public param/result types are hand-reviewed aliases over generated +// internals (src/generated/, produced by @alchemy/api-codegen from the docs +// repo's bundled OpenAPI/OpenRPC specs). Generated names are never +// re-exported directly: every public-surface change is a deliberate edit +// here, even when the underlying spec moves. /** An address paired with the networks to query it on. */ export interface PortfolioAddressEntry { @@ -12,92 +25,56 @@ export interface PortfolioAddressEntry { networks: NetworkInput[]; } -export interface GetTokensByAddressParams { +/** + * Generated request body with the wire-format `addresses` (plain string + * networks) replaced by the SDK's three-format {@link PortfolioAddressEntry}. + */ +export type GetTokensByAddressParams = Omit< + GetTokensByAddressBody, + "addresses" +> & { addresses: PortfolioAddressEntry[]; - withMetadata?: boolean; - withPrices?: boolean; - includeNativeTokens?: boolean; - includeErc20Tokens?: boolean; -} +}; -export interface PortfolioToken { - address: string; - network: string; - tokenAddress: string | null; - tokenBalance: string; - tokenMetadata?: { - name?: string | null; - symbol?: string | null; - decimals?: number | null; - logo?: string | null; - }; - tokenPrices?: Array<{ - currency: string; - value: string; - lastUpdatedAt: string; - }>; -} +export type GetTokensByAddressResult = GetTokensByAddressResponse; -export interface GetTokensByAddressResult { - data: { - tokens: PortfolioToken[]; - pageKey?: string; - }; -} +export type PortfolioToken = NonNullable< + GetTokensByAddressResponse["data"]["tokens"] +>[number]; -export interface GetNftsForOwnerParams { - owner: string; +/** + * Generated query params plus the SDK's network override; the wire's + * bracketed `contractAddresses[]` key is replaced with a plain array (the + * action serializes it back to the bracketed form). + */ +export type GetNftsForOwnerParams = Omit< + GetNftsForOwnerQuery, + "contractAddresses[]" +> & { /** Overrides the client-level network for this request. */ network?: NetworkInput; + /** Contract addresses to filter by (max 45). */ contractAddresses?: string[]; - withMetadata?: boolean; - pageKey?: string; - pageSize?: number; -} +}; -export interface GetNftsForOwnerResult { - ownedNfts: Array>; - totalCount: number; - pageKey?: string; - validAt?: Record; -} +export type GetNftsForOwnerResult = GetNftsForOwnerResponse; -export interface GetAssetTransfersParams { +/** Generated RPC params plus the SDK's network override. */ +export type GetAssetTransfersParams = AlchemyGetAssetTransfersParams & { /** Overrides the client-level network for this request. */ network?: NetworkInput; - fromBlock?: string; - toBlock?: string; - fromAddress?: string; - toAddress?: string; - excludeZeroValue?: boolean; - category: Array< - "external" | "internal" | "erc20" | "erc721" | "erc1155" | "specialnft" - >; - contractAddresses?: string[]; - order?: "asc" | "desc"; - withMetadata?: boolean; - pageKey?: string; - maxCount?: string; -} +}; -export interface AssetTransfer { - category: string; - blockNum: string; - from: string; - to: string | null; - value: number | null; - asset: string | null; - hash: string; - uniqueId: string; - rawContract: { - value: string | null; - address: string | null; - decimal: string | null; - }; - metadata?: { blockTimestamp: string }; -} +/** + * The spec result is `oneOf: ["Not Found (null)" string, object]`; the string + * branch is a docs-spec artifact the SDK deliberately does not surface, so it + * is collapsed away here (and in {@link DataRpcSchema}). + */ +export type GetAssetTransfersResult = Exclude< + AlchemyGetAssetTransfersResult, + string +>; -export interface GetAssetTransfersResult { - transfers: AssetTransfer[]; - pageKey?: string; -} +export type AssetTransfer = NonNullable< + GetAssetTransfersResult["transfers"] +>[number]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a5905d755..0ca8616a60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -220,6 +220,21 @@ importers: specifier: ^2.45.0 version: 2.46.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.4.3) + packages/api-codegen: + devDependencies: + json-schema-to-typescript: + specifier: 15.0.4 + version: 15.0.4 + openapi-typescript: + specifier: 7.13.0 + version: 7.13.0(typescript@5.9.3) + prettier: + specifier: 3.3.3 + version: 3.3.3 + typescript-template: + specifier: workspace:* + version: link:../../templates/typescript + packages/common: dependencies: zod: @@ -242,6 +257,9 @@ importers: specifier: workspace:* version: link:../common devDependencies: + '@alchemy/api-codegen': + specifier: workspace:* + version: link:../api-codegen typescript-template: specifier: workspace:* version: link:../../templates/typescript @@ -353,6 +371,10 @@ packages: peerDependencies: typescript: ^5.8.2 + '@apidevtools/json-schema-ref-parser@11.9.3': + resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} + engines: {node: '>= 16'} + '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} @@ -1521,6 +1543,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@lerna/create@8.2.4': resolution: {integrity: sha512-A8AlzetnS2WIuhijdAzKUyFpR5YbLLfV3luQ4lzBgIBgRfuoBDZeF+RSZPhra+7A6/zTUlrbhKZIOi/MNhqgvQ==} engines: {node: '>=18.0.0'} @@ -1901,6 +1926,16 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/config@0.22.0': + resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==} + + '@redocly/openapi-core@1.34.15': + resolution: {integrity: sha512-HAwCnNyKcs5XGQqms+9t7OdAPM/5TDstmhF+0i7tdCFato2QKuYIlyWETwkXd8c5zbltr1oB+6y9NTeQLr2d6Q==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@reown/appkit-common@1.7.8': resolution: {integrity: sha512-ridIhc/x6JOp7KbDdwGKY4zwf8/iK8EYBl+HtWrruutSLwZyVi5P8WaZa+8iajL6LcDcDF7LoyLwMTym7SRuwQ==} @@ -3470,6 +3505,9 @@ packages: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -4778,6 +4816,10 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -5104,6 +5146,10 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -5161,6 +5207,11 @@ packages: json-rpc-random-id@1.0.1: resolution: {integrity: sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA==} + json-schema-to-typescript@15.0.4: + resolution: {integrity: sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==} + engines: {node: '>=16.0.0'} + hasBin: true + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -6135,6 +6186,12 @@ packages: zod: optional: true + openapi-typescript@7.13.0: + resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==} + hasBin: true + peerDependencies: + typescript: ^5.x + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -6282,6 +6339,10 @@ packages: resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} engines: {node: '>=16'} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} @@ -6397,6 +6458,10 @@ packages: resolution: {integrity: sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w==} engines: {node: '>=6'} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} @@ -7098,6 +7163,10 @@ packages: resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} engines: {node: '>=14.0.0'} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -7351,6 +7420,10 @@ packages: resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} engines: {node: '>=14.16'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -7592,6 +7665,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js-replace@1.0.1: + resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -7986,6 +8062,9 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -8108,6 +8187,12 @@ snapshots: - bufferutil - utf-8-validate + '@apidevtools/json-schema-ref-parser@11.9.3': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.1 + '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -8137,7 +8222,7 @@ snapshots: '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -8197,7 +8282,7 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) lodash.debounce: 4.0.8 resolve: 1.22.11 transitivePeerDependencies: @@ -8946,7 +9031,7 @@ snapshots: '@babel/parser': 7.29.0 '@babel/template': 7.28.6 '@babel/types': 7.29.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -9268,7 +9353,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.14.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -9320,7 +9405,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -9499,6 +9584,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@jsdevtools/ono@7.1.3': {} + '@lerna/create@8.2.4(@types/node@22.19.15)(babel-plugin-macros@3.1.0)(encoding@0.1.13)(typescript@5.9.3)': dependencies: '@npmcli/arborist': 7.5.4 @@ -9691,7 +9778,7 @@ snapshots: bufferutil: 4.1.0 cross-fetch: 4.1.0(encoding@0.1.13) date-fns: 2.30.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eciesjs: 0.4.17 eventemitter2: 6.4.9 readable-stream: 3.6.2 @@ -9715,7 +9802,7 @@ snapshots: '@paulmillr/qr': 0.2.1 bowser: 2.14.1 cross-fetch: 4.1.0(encoding@0.1.13) - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eciesjs: 0.4.17 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 @@ -9742,7 +9829,7 @@ snapshots: '@scure/base': 1.2.6 '@types/debug': 4.1.12 '@types/lodash': 4.17.24 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) lodash: 4.17.23 pony-cause: 2.1.11 semver: 7.7.4 @@ -9754,7 +9841,7 @@ snapshots: dependencies: '@ethereumjs/tx': 4.2.0 '@types/debug': 4.1.12 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) semver: 7.7.4 superstruct: 1.0.4 transitivePeerDependencies: @@ -9767,7 +9854,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) pony-cause: 2.1.11 semver: 7.7.4 uuid: 9.0.1 @@ -9781,7 +9868,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) pony-cause: 2.1.11 semver: 7.7.4 uuid: 9.0.1 @@ -9828,7 +9915,7 @@ snapshots: dependencies: agent-base: 7.1.4 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@10.2.2) lru-cache: 10.4.3 socks-proxy-agent: 8.0.5 transitivePeerDependencies: @@ -10180,6 +10267,29 @@ snapshots: '@pkgr/core@0.2.9': {} + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.1 + + '@redocly/config@0.22.0': {} + + '@redocly/openapi-core@1.34.15(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.22.0 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.1.1 + minimatch: 5.1.9 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + '@reown/appkit-common@1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.22.4)': dependencies: big.js: 6.2.2 @@ -11311,7 +11421,7 @@ snapshots: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 @@ -11336,7 +11446,7 @@ snapshots: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3) - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 8.57.1 optionalDependencies: typescript: 5.9.3 @@ -11352,7 +11462,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3) '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 8.57.1 tsutils: 3.21.0(typescript@5.9.3) optionalDependencies: @@ -11368,7 +11478,7 @@ snapshots: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) globby: 11.1.0 is-glob: 4.0.3 semver: 7.7.4 @@ -12608,6 +12718,8 @@ snapshots: color-support@1.1.3: {} + colorette@1.4.0: {} + colorette@2.0.20: {} columnify@1.6.0: @@ -12853,9 +12965,11 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.4.3: + debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 decamelize-keys@1.1.1: dependencies: @@ -13038,7 +13152,7 @@ snapshots: engine.io-client@6.6.4(bufferutil@4.1.0)(utf-8-validate@5.0.10): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) engine.io-parser: 5.2.3 ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) xmlhttprequest-ssl: 2.1.2 @@ -13374,7 +13488,7 @@ snapshots: '@es-joy/jsdoccomment': 0.46.0 are-docs-informative: 0.0.2 comment-parser: 1.4.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) escape-string-regexp: 4.0.0 eslint: 8.57.1 espree: 10.4.0 @@ -13488,7 +13602,7 @@ snapshots: ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -14159,7 +14273,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -14171,10 +14285,10 @@ snapshots: transitivePeerDependencies: - debug - https-proxy-agent@7.0.6: + https-proxy-agent@7.0.6(supports-color@10.2.2): dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -14230,6 +14344,8 @@ snapshots: indent-string@4.0.0: {} + index-to-position@1.2.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -14501,10 +14617,6 @@ snapshots: dependencies: ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) - isows@1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)): - dependencies: - ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) - iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -14557,6 +14669,8 @@ snapshots: jiti@2.6.1: {} + js-levenshtein@1.1.6: {} + js-tokens@4.0.0: {} js-yaml@3.14.2: @@ -14584,7 +14698,7 @@ snapshots: form-data: 4.0.5 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@10.2.2) is-potential-custom-element-name: 1.0.1 nwsapi: 2.2.23 parse5: 7.3.0 @@ -14621,6 +14735,18 @@ snapshots: json-rpc-random-id@1.0.1: {} + json-schema-to-typescript@15.0.4: + dependencies: + '@apidevtools/json-schema-ref-parser': 11.9.3 + '@types/json-schema': 7.0.15 + '@types/lodash': 4.17.24 + is-glob: 4.0.3 + js-yaml: 4.1.1 + lodash: 4.17.23 + minimist: 1.2.8 + prettier: 3.3.3 + tinyglobby: 0.2.17 + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -15755,7 +15881,7 @@ snapshots: micromark@3.2.0: dependencies: '@types/debug': 4.1.12 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) decode-named-character-reference: 1.3.0 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -15777,7 +15903,7 @@ snapshots: micromark@4.0.2: dependencies: '@types/debug': 4.1.12 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 @@ -16231,6 +16357,16 @@ snapshots: transitivePeerDependencies: - encoding + openapi-typescript@7.13.0(typescript@5.9.3): + dependencies: + '@redocly/openapi-core': 1.34.15(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -16480,6 +16616,12 @@ snapshots: lines-and-columns: 2.0.4 type-fest: 3.13.1 + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.0 + index-to-position: 1.2.0 + type-fest: 4.41.0 + parse-ms@4.0.0: {} parse-path@7.1.0: @@ -16579,6 +16721,8 @@ snapshots: dependencies: irregular-plurals: 2.0.0 + pluralize@8.0.0: {} + pngjs@5.0.0: {} pony-cause@2.1.11: {} @@ -17157,7 +17301,7 @@ snapshots: socket.io-client@4.8.3(bufferutil@4.1.0)(utf-8-validate@5.0.10): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) engine.io-client: 6.6.4(bufferutil@4.1.0)(utf-8-validate@5.0.10) socket.io-parser: 4.2.5 transitivePeerDependencies: @@ -17168,14 +17312,14 @@ snapshots: socket.io-parser@4.2.5: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) socks: 2.8.7 transitivePeerDependencies: - supports-color @@ -17382,6 +17526,8 @@ snapshots: superstruct@2.0.2: {} + supports-color@10.2.2: {} + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -17571,7 +17717,7 @@ snapshots: tuf-js@2.2.1: dependencies: '@tufjs/models': 2.0.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) make-fetch-happen: 13.0.1 transitivePeerDependencies: - supports-color @@ -17623,6 +17769,8 @@ snapshots: type-fest@3.13.1: {} + type-fest@4.41.0: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -17734,7 +17882,7 @@ snapshots: '@types/node': 22.19.15 '@types/unist': 3.0.3 concat-stream: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) extend: 3.0.2 glob: 10.5.0 ignore: 6.0.2 @@ -17876,6 +18024,8 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js-replace@1.0.1: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -18027,7 +18177,7 @@ snapshots: '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76) - isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10)) ox: 0.12.4(typescript@5.9.3)(zod@3.25.76) ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: @@ -18044,7 +18194,7 @@ snapshots: '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 abitype: 1.2.3(typescript@5.9.3)(zod@4.4.3) - isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10)) ox: 0.12.4(typescript@5.9.3)(zod@4.4.3) ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: @@ -18345,6 +18495,8 @@ snapshots: yallist@5.0.0: {} + yaml-ast-parser@0.0.43: {} + yaml@1.10.2: {} yaml@2.3.1: {} diff --git a/turbo.json b/turbo.json index 3642a69139..3214ffa74a 100644 --- a/turbo.json +++ b/turbo.json @@ -9,6 +9,11 @@ "clean": { "cache": false }, + "generate": { + "dependsOn": ["^build"], + "outputs": ["src/generated/**"], + "cache": false + }, "test": { "env": ["VITEST_SEPOLIA_FORK_URL"], "dependsOn": ["^build"], From 9098985d2603a7b741ed914b25af11b75a30d228 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Wed, 10 Jun 2026 10:19:38 -0400 Subject: [PATCH 06/20] chore(data-apis): re-snapshot specs from docs main; add prices + token specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Snapshots now pinned to alchemyplatform/docs main (becfd714) instead of a feature branch. prices (REST) and token (OpenRPC) enter the manifest so generation covers all five v1 spec sources; actions land in follow-ups. RPC emitter now strips non-root schema titles before compilation — title-named shared subschemas (e.g. token's Hex Encoded Address) otherwise hoist duplicate identifiers when one spec has multiple methods. Co-Authored-By: Claude Fable 5 --- packages/api-codegen/specs/portfolio.json | 24 +- packages/api-codegen/specs/prices.json | 1263 +++++++++++++++++ packages/api-codegen/specs/specs.lock.json | 8 +- packages/api-codegen/specs/token.json | 561 ++++++++ packages/api-codegen/src/rpc/openrpcWalker.ts | 30 +- packages/data-apis/codegen.manifest.ts | 40 + .../src/generated/rest/portfolio.types.ts | 24 +- .../src/generated/rest/prices.schema.ts | 68 + .../src/generated/rest/prices.types.ts | 735 ++++++++++ packages/data-apis/src/generated/rpc/token.ts | 105 ++ .../data-apis/src/generated/rpc/transfers.ts | 3 +- 11 files changed, 2830 insertions(+), 31 deletions(-) create mode 100644 packages/api-codegen/specs/prices.json create mode 100644 packages/api-codegen/specs/token.json create mode 100644 packages/data-apis/src/generated/rest/prices.schema.ts create mode 100644 packages/data-apis/src/generated/rest/prices.types.ts create mode 100644 packages/data-apis/src/generated/rpc/token.ts diff --git a/packages/api-codegen/specs/portfolio.json b/packages/api-codegen/specs/portfolio.json index 8a9d094209..a98cc8f98e 100644 --- a/packages/api-codegen/specs/portfolio.json +++ b/packages/api-codegen/specs/portfolio.json @@ -69,12 +69,12 @@ } }, "withMetadata": { - "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call.", "type": "boolean", "default": true }, "withPrices": { - "description": "Boolean - if set to `true`, returns token prices. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "description": "Boolean - if set to `true`, returns token prices. Setting this to false will reduce payload size and may result in a faster API call.", "type": "boolean", "default": true }, @@ -85,7 +85,7 @@ "default": true }, "includeErc20Tokens": { - "description": "Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "description": "Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call.", "type": "boolean", "default": true } @@ -348,7 +348,7 @@ "default": true }, "includeErc20Tokens": { - "description": "Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "description": "Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call.", "type": "boolean", "default": true } @@ -616,7 +616,7 @@ } }, "withMetadata": { - "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call.", "type": "boolean", "default": true }, @@ -1123,7 +1123,7 @@ } }, "withMetadata": { - "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call.", "type": "boolean", "default": true }, @@ -1507,7 +1507,7 @@ "default": true }, "includeErc20Tokens": { - "description": "Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "description": "Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call.", "type": "boolean", "default": true } @@ -1552,12 +1552,12 @@ } }, "withMetadata": { - "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call.", "type": "boolean", "default": true }, "withPrices": { - "description": "Boolean - if set to `true`, returns token prices. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "description": "Boolean - if set to `true`, returns token prices. Setting this to false will reduce payload size and may result in a faster API call.", "type": "boolean", "default": true }, @@ -1568,7 +1568,7 @@ "default": true }, "includeErc20Tokens": { - "description": "Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "description": "Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call.", "type": "boolean", "default": true } @@ -1679,7 +1679,7 @@ } }, "withMetadata": { - "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call.", "type": "boolean", "default": true }, @@ -1731,7 +1731,7 @@ } }, "withMetadata": { - "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`.", + "description": "Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call.", "type": "boolean", "default": true } diff --git a/packages/api-codegen/specs/prices.json b/packages/api-codegen/specs/prices.json new file mode 100644 index 0000000000..6c33d8d775 --- /dev/null +++ b/packages/api-codegen/specs/prices.json @@ -0,0 +1,1263 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "📈 Prices API", + "version": "1.0" + }, + "servers": [ + { + "url": "https://api.g.alchemy.com/prices/v1" + } + ], + "paths": { + "/{apiKey}/tokens/by-symbol": { + "get": { + "summary": "Token Prices By Symbol", + "description": "Fetches current prices for multiple tokens using their symbols. Returns a list of token prices, each containing the symbol, prices, and an optional error field.\n", + "tags": [ + "Prices API Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "required": true, + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)." + } + }, + { + "in": "query", + "name": "symbols", + "required": true, + "description": "Array of token symbols (limit 25). Example: symbols=[ETH,BTC]\n", + "schema": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "default": "ETH" + } + }, + "style": "form", + "explode": true + } + ], + "responses": { + "200": { + "description": "Successful response, even if some tokens are missing.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "description": "List of token price data.", + "items": { + "type": "object", + "properties": { + "symbol": { + "type": "string", + "description": "Token symbol." + }, + "prices": { + "type": "array", + "description": "List of price information.", + "items": { + "type": "object", + "properties": { + "currency": { + "type": "string", + "description": "Currency code (e.g., USD)." + }, + "value": { + "type": "string", + "description": "Price value as a string." + }, + "lastUpdatedAt": { + "type": "string", + "format": "date-time", + "description": "Time when the price was last updated." + } + }, + "required": [ + "currency", + "value", + "lastUpdatedAt" + ] + } + }, + "error": { + "type": "string", + "description": "Error message if applicable." + } + }, + "required": [ + "symbol", + "prices", + "error" + ] + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "description": "Bad Request: Malformed request or missing parameters.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + }, + "429": { + "description": "Too Many Requests: Rate limit exceeded.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "get-token-prices-by-symbol" + } + }, + "/{apiKey}/tokens/by-address": { + "post": { + "summary": "Token Prices By Address", + "description": "Fetches current prices for multiple tokens using network and address pairs. Returns a list of token prices, each containing the network, address, prices, and an optional error field.\n", + "tags": [ + "Prices API Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "required": true, + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)." + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "minItems": 1, + "description": "Array of token network and address pairs (limit 25 addresses, max 3 networks). Networks should match network enums.\n", + "items": { + "type": "object", + "properties": { + "network": { + "type": "string", + "example": "eth-mainnet", + "default": "eth-mainnet", + "description": "Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + }, + "address": { + "type": "string", + "description": "Token contract address.", + "example": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "default": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + } + }, + "required": [ + "network", + "address" + ] + }, + "maxItems": 25 + } + }, + "required": [ + "addresses" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response, even if some prices are missing.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "description": "List of token prices by address.", + "items": { + "type": "object", + "properties": { + "network": { + "type": "string", + "description": "Network identifier." + }, + "address": { + "type": "string", + "description": "Token contract address." + }, + "prices": { + "type": "array", + "description": "List of price information.", + "items": { + "type": "object", + "properties": { + "currency": { + "type": "string", + "description": "Currency code (e.g., USD)." + }, + "value": { + "type": "string", + "description": "Price value as a string." + }, + "lastUpdatedAt": { + "type": "string", + "format": "date-time", + "description": "Time when the price was last updated." + } + }, + "required": [ + "currency", + "value", + "lastUpdatedAt" + ] + } + }, + "error": { + "type": "string", + "description": "Error message if applicable." + } + }, + "required": [ + "network", + "address", + "prices", + "error" + ] + } + } + }, + "required": [ + "data" + ] + } + } + } + }, + "400": { + "description": "Bad Request: Invalid input (e.g., malformed JSON).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + }, + "429": { + "description": "Too Many Requests: Rate limit exceeded.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "get-token-prices-by-address" + } + }, + "/{apiKey}/tokens/historical": { + "post": { + "summary": "Historical Token Prices", + "description": "Provides historical price data for a single token over a time range. You can identify the token by symbol or by network and contract address.\n", + "tags": [ + "Prices API Endpoints" + ], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "required": true, + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)." + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Request body for fetching historical token prices. Provide either the token `symbol` or both `network` and `address`, along with the required time range parameters.\n", + "oneOf": [ + { + "required": [ + "symbol", + "startTime", + "endTime" + ], + "properties": { + "symbol": { + "type": "string", + "description": "Token symbol (e.g., ETH, BTC).", + "example": "ETH", + "default": "ETH" + }, + "startTime": { + "oneOf": [ + { + "type": "string", + "format": "date-time", + "description": "Start of the time range in ISO 8601 format.", + "default": "2024-01-01T00:00:00Z" + }, + { + "type": "number", + "description": "Start of the time range as a timestamp in seconds since epoch.", + "default": 1704067200 + } + ], + "description": "Start of the time range.", + "example": "2024-01-01T00:00:00Z" + }, + "endTime": { + "oneOf": [ + { + "type": "string", + "format": "date-time", + "description": "End of the time range in ISO 8601 format.", + "default": "2024-01-31T23:59:59Z" + }, + { + "type": "number", + "description": "End of the time range as a timestamp in seconds since epoch.", + "default": 1706745599 + } + ], + "description": "End of the time range.", + "example": "2024-01-31T23:59:59Z" + }, + "interval": { + "type": "string", + "description": "Time interval for data points. Max ranges: (5m, 7d), (1h, 30d), (1d, 1yr)\n", + "enum": [ + "5m", + "1h", + "1d" + ], + "default": "1d", + "example": "1d" + }, + "withMarketData": { + "type": "boolean", + "description": "Whether to include market cap and volume for each token", + "example": true, + "default": false + } + } + }, + { + "required": [ + "network", + "address", + "startTime", + "endTime" + ], + "properties": { + "network": { + "type": "string", + "description": "Network identifier (e.g., eth-mainnet).", + "example": "eth-mainnet", + "default": "eth-mainnet" + }, + "address": { + "type": "string", + "description": "Token contract address.", + "example": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "default": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + }, + "startTime": { + "oneOf": [ + { + "type": "string", + "format": "date-time", + "description": "Start of the time range in ISO 8601 format.", + "default": "2024-01-01T00:00:00Z" + }, + { + "type": "number", + "description": "Start of the time range as a timestamp since epoch.", + "default": 1704067200 + } + ], + "description": "Start of the time range.", + "example": "2024-01-01T00:00:00Z" + }, + "endTime": { + "oneOf": [ + { + "type": "string", + "format": "date-time", + "description": "End of the time range in ISO 8601 format.", + "default": "2024-01-31T23:59:59Z" + }, + { + "type": "number", + "description": "End of the time range as a timestamp since epoch.", + "default": 1706745599 + } + ], + "description": "End of the time range.", + "example": "2024-01-31T23:59:59Z" + }, + "interval": { + "type": "string", + "description": "Time interval for data points. Max ranges: (5m, 7d), (1h, 30d), (1d, 1yr)\n", + "enum": [ + "5m", + "1h", + "1d" + ], + "default": "1d", + "example": "1d" + }, + "withMarketData": { + "type": "boolean", + "description": "Whether to include market cap and volume for each token", + "example": true, + "default": false + } + } + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response with historical price data.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Response containing historical price data. It will either include `symbol` or both `network` and `address` based on the request.\n", + "oneOf": [ + { + "type": "object", + "properties": { + "symbol": { + "type": "string", + "description": "Token symbol.", + "example": "ETH", + "default": "ETH" + }, + "currency": { + "type": "string", + "description": "Currency identifier.", + "example": "usd", + "default": "usd" + }, + "data": { + "type": "array", + "description": "List of historical price data points.", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Price value as a string.", + "example": "1900.00" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Timestamp of the price data point.", + "example": "2024-01-01T00:00:00Z" + }, + "marketCap": { + "type": "string", + "description": "Total market capitalization at the timestamp", + "example": "274292310008.21802" + }, + "totalVolume": { + "type": "string", + "description": "Volume traded during the defined interval", + "example": "6715146404.608721" + } + }, + "required": [ + "value", + "timestamp" + ] + } + } + }, + "required": [ + "symbol", + "currency", + "data" + ] + }, + { + "type": "object", + "properties": { + "network": { + "type": "string", + "description": "Network identifier.", + "example": "eth-mainnet", + "default": "eth-mainnet" + }, + "address": { + "type": "string", + "description": "Token contract address.", + "example": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "default": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + }, + "currency": { + "type": "string", + "description": "Currency identifier.", + "example": "usd", + "default": "usd" + }, + "data": { + "type": "array", + "description": "List of historical price data points.", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Price value as a string.", + "example": "1900.00" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Timestamp of the price data point.", + "example": "2024-01-01T00:00:00Z" + }, + "marketCap": { + "type": "string", + "description": "Total market capitalization at the timestamp", + "example": "274292310008.21802" + }, + "totalVolume": { + "type": "string", + "description": "Volume traded during the defined interval", + "example": "6715146404.608721" + } + }, + "required": [ + "value", + "timestamp" + ] + } + } + }, + "required": [ + "network", + "address", + "currency", + "data" + ] + } + ] + } + } + } + }, + "400": { + "description": "Bad Request: Invalid input (e.g., malformed request, missing parameters).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found: Token not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + }, + "429": { + "description": "Too Many Requests: Rate limit exceeded.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Detailed error message." + } + }, + "required": [ + "message" + ], + "description": "Error details." + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "operationId": "get-historical-token-prices" + } + } + }, + "components": { + "securitySchemes": { + "apiKey": { + "type": "apiKey", + "name": "Authorization", + "in": "header", + "description": "An API key that will be supplied in a named header.", + "x-default": "Bearer API_KEY" + } + }, + "schemas": { + "TokenPricesResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "description": "List of token price data.", + "items": { + "type": "object", + "properties": { + "symbol": { + "type": "string", + "description": "Token symbol." + }, + "prices": { + "type": "array", + "description": "List of price information.", + "items": { + "type": "object", + "properties": { + "currency": { + "type": "string", + "description": "Currency code (e.g., USD)." + }, + "value": { + "type": "string", + "description": "Price value as a string." + }, + "lastUpdatedAt": { + "type": "string", + "format": "date-time", + "description": "Time when the price was last updated." + } + }, + "required": [ + "currency", + "value", + "lastUpdatedAt" + ] + } + }, + "error": { + "type": "string", + "description": "Error message if applicable." + } + }, + "required": [ + "symbol", + "prices", + "error" + ] + } + } + }, + "required": [ + "data" + ] + }, + "TokenPriceResponseItem": { + "type": "object", + "properties": { + "symbol": { + "type": "string", + "description": "Token symbol." + }, + "prices": { + "type": "array", + "description": "List of price information.", + "items": { + "type": "object", + "properties": { + "currency": { + "type": "string", + "description": "Currency code (e.g., USD)." + }, + "value": { + "type": "string", + "description": "Price value as a string." + }, + "lastUpdatedAt": { + "type": "string", + "format": "date-time", + "description": "Time when the price was last updated." + } + }, + "required": [ + "currency", + "value", + "lastUpdatedAt" + ] + } + }, + "error": { + "type": "string", + "description": "Error message if applicable." + } + }, + "required": [ + "symbol", + "prices", + "error" + ] + }, + "ByAddressRequest": { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "minItems": 1, + "description": "Array of token network and address pairs (limit 25 addresses, max 3 networks). Networks should match network enums.\n", + "items": { + "type": "object", + "properties": { + "network": { + "type": "string", + "example": "eth-mainnet", + "default": "eth-mainnet", + "description": "Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains)" + }, + "address": { + "type": "string", + "description": "Token contract address.", + "example": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "default": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + } + }, + "required": [ + "network", + "address" + ] + }, + "maxItems": 25 + } + }, + "required": [ + "addresses" + ] + }, + "AddressPricesResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "description": "List of token prices by address.", + "items": { + "type": "object", + "properties": { + "network": { + "type": "string", + "description": "Network identifier." + }, + "address": { + "type": "string", + "description": "Token contract address." + }, + "prices": { + "type": "array", + "description": "List of price information.", + "items": { + "type": "object", + "properties": { + "currency": { + "type": "string", + "description": "Currency code (e.g., USD)." + }, + "value": { + "type": "string", + "description": "Price value as a string." + }, + "lastUpdatedAt": { + "type": "string", + "format": "date-time", + "description": "Time when the price was last updated." + } + }, + "required": [ + "currency", + "value", + "lastUpdatedAt" + ] + } + }, + "error": { + "type": "string", + "description": "Error message if applicable." + } + }, + "required": [ + "network", + "address", + "prices", + "error" + ] + } + } + }, + "required": [ + "data" + ] + }, + "AddressPriceResponseItem": { + "type": "object", + "properties": { + "network": { + "type": "string", + "description": "Network identifier." + }, + "address": { + "type": "string", + "description": "Token contract address." + }, + "prices": { + "type": "array", + "description": "List of price information.", + "items": { + "type": "object", + "properties": { + "currency": { + "type": "string", + "description": "Currency code (e.g., USD)." + }, + "value": { + "type": "string", + "description": "Price value as a string." + }, + "lastUpdatedAt": { + "type": "string", + "format": "date-time", + "description": "Time when the price was last updated." + } + }, + "required": [ + "currency", + "value", + "lastUpdatedAt" + ] + } + }, + "error": { + "type": "string", + "description": "Error message if applicable." + } + }, + "required": [ + "network", + "address", + "prices", + "error" + ] + }, + "HistoricalPricesResponse": { + "type": "object", + "description": "Response containing historical price data. It will either include `symbol` or both `network` and `address` based on the request.\n", + "oneOf": [ + { + "type": "object", + "properties": { + "symbol": { + "type": "string", + "description": "Token symbol.", + "example": "ETH", + "default": "ETH" + }, + "currency": { + "type": "string", + "description": "Currency identifier.", + "example": "usd", + "default": "usd" + }, + "data": { + "type": "array", + "description": "List of historical price data points.", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Price value as a string.", + "example": "1900.00" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Timestamp of the price data point.", + "example": "2024-01-01T00:00:00Z" + }, + "marketCap": { + "type": "string", + "description": "Total market capitalization at the timestamp", + "example": "274292310008.21802" + }, + "totalVolume": { + "type": "string", + "description": "Volume traded during the defined interval", + "example": "6715146404.608721" + } + }, + "required": [ + "value", + "timestamp" + ] + } + } + }, + "required": [ + "symbol", + "currency", + "data" + ] + }, + { + "type": "object", + "properties": { + "network": { + "type": "string", + "description": "Network identifier.", + "example": "eth-mainnet", + "default": "eth-mainnet" + }, + "address": { + "type": "string", + "description": "Token contract address.", + "example": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "default": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + }, + "currency": { + "type": "string", + "description": "Currency identifier.", + "example": "usd", + "default": "usd" + }, + "data": { + "type": "array", + "description": "List of historical price data points.", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Price value as a string.", + "example": "1900.00" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Timestamp of the price data point.", + "example": "2024-01-01T00:00:00Z" + }, + "marketCap": { + "type": "string", + "description": "Total market capitalization at the timestamp", + "example": "274292310008.21802" + }, + "totalVolume": { + "type": "string", + "description": "Volume traded during the defined interval", + "example": "6715146404.608721" + } + }, + "required": [ + "value", + "timestamp" + ] + } + } + }, + "required": [ + "network", + "address", + "currency", + "data" + ] + } + ] + }, + "HistoricalPricesBySymbol": { + "type": "object", + "properties": { + "symbol": { + "type": "string", + "description": "Token symbol.", + "example": "ETH", + "default": "ETH" + }, + "currency": { + "type": "string", + "description": "Currency identifier.", + "example": "usd", + "default": "usd" + }, + "data": { + "type": "array", + "description": "List of historical price data points.", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Price value as a string.", + "example": "1900.00" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Timestamp of the price data point.", + "example": "2024-01-01T00:00:00Z" + }, + "marketCap": { + "type": "string", + "description": "Total market capitalization at the timestamp", + "example": "274292310008.21802" + }, + "totalVolume": { + "type": "string", + "description": "Volume traded during the defined interval", + "example": "6715146404.608721" + } + }, + "required": [ + "value", + "timestamp" + ] + } + } + }, + "required": [ + "symbol", + "currency", + "data" + ] + }, + "HistoricalPricesByAddress": { + "type": "object", + "properties": { + "network": { + "type": "string", + "description": "Network identifier.", + "example": "eth-mainnet", + "default": "eth-mainnet" + }, + "address": { + "type": "string", + "description": "Token contract address.", + "example": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "default": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + }, + "currency": { + "type": "string", + "description": "Currency identifier.", + "example": "usd", + "default": "usd" + }, + "data": { + "type": "array", + "description": "List of historical price data points.", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Price value as a string.", + "example": "1900.00" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Timestamp of the price data point.", + "example": "2024-01-01T00:00:00Z" + }, + "marketCap": { + "type": "string", + "description": "Total market capitalization at the timestamp", + "example": "274292310008.21802" + }, + "totalVolume": { + "type": "string", + "description": "Volume traded during the defined interval", + "example": "6715146404.608721" + } + }, + "required": [ + "value", + "timestamp" + ] + } + } + }, + "required": [ + "network", + "address", + "currency", + "data" + ] + } + } + } +} diff --git a/packages/api-codegen/specs/specs.lock.json b/packages/api-codegen/specs/specs.lock.json index c6953038ec..9302ddfc41 100644 --- a/packages/api-codegen/specs/specs.lock.json +++ b/packages/api-codegen/specs/specs.lock.json @@ -1,12 +1,14 @@ { "docs": { "repository": "alchemyplatform/docs", - "sha": "374030e02046972b4158612036af3aed9130b675", - "branch": "codex/improve-agent-wallet-docs" + "sha": "becfd7140371eb947dc0bcfe826922493ce6a48d", + "branch": "HEAD" }, "specs": { "nft.json": "c90282cfc4959f48534aaccceccdd31202ca1410888d7d0daafdd20e0f8c4885", - "portfolio.json": "16bad2001183fada4147f445640555e22a0c58ad44844eefdf0a06e3f794774c", + "portfolio.json": "7d5aee47eab3856186d9d323788e1721277a20aa2622e78659dd721fc2577d85", + "prices.json": "febf3652f81e6053bd19994c66f9f5709ce25d18f3ff8cddd7d5e173f5a3bdc0", + "token.json": "2a5b02c5400909e7670fb59b6b77b1c1d75f0275155d1ff9c24c5c21c985dda5", "transfers.json": "17626a215d7453b41a868e434d5d86a9a1505d8e648ca91c7df1b44c97af9cf5" } } diff --git a/packages/api-codegen/specs/token.json b/packages/api-codegen/specs/token.json new file mode 100644 index 0000000000..51af78aebe --- /dev/null +++ b/packages/api-codegen/specs/token.json @@ -0,0 +1,561 @@ +{ + "$schema": "https://meta.open-rpc.org/", + "openrpc": "1.2.4", + "info": { + "title": "Alchemy Token JSON-RPC Specification", + "description": "A specification of the standard JSON-RPC methods for Token API.", + "version": "0.0.0" + }, + "servers": [ + { + "url": "https://eth-mainnet.g.alchemy.com/v2", + "name": "Ethereum Mainnet" + }, + { + "url": "https://eth-mainnetbeacon.g.alchemy.com/v2", + "name": "Ethereum Mainnet Beacon" + }, + { + "url": "https://eth-hoodi.g.alchemy.com/v2", + "name": "Ethereum Hoodi" + }, + { + "url": "https://eth-hoodibeacon.g.alchemy.com/v2", + "name": "Ethereum Hoodi Beacon" + }, + { + "url": "https://eth-sepolia.g.alchemy.com/v2", + "name": "Ethereum Sepolia" + }, + { + "url": "https://eth-sepoliabeacon.g.alchemy.com/v2", + "name": "Ethereum Sepolia Beacon" + }, + { + "url": "https://abstract-mainnet.g.alchemy.com/v2", + "name": "Abstract Mainnet" + }, + { + "url": "https://abstract-testnet.g.alchemy.com/v2", + "name": "Abstract Testnet" + }, + { + "url": "https://anime-mainnet.g.alchemy.com/v2", + "name": "Anime Mainnet" + }, + { + "url": "https://anime-sepolia.g.alchemy.com/v2", + "name": "Anime Sepolia" + }, + { + "url": "https://apechain-mainnet.g.alchemy.com/v2", + "name": "ApeChain Mainnet" + }, + { + "url": "https://apechain-curtis.g.alchemy.com/v2", + "name": "ApeChain Curtis" + }, + { + "url": "https://arb-mainnet.g.alchemy.com/v2", + "name": "Arbitrum Mainnet" + }, + { + "url": "https://arb-sepolia.g.alchemy.com/v2", + "name": "Arbitrum Sepolia" + }, + { + "url": "https://avax-mainnet.g.alchemy.com/v2", + "name": "Avalanche Mainnet" + }, + { + "url": "https://avax-fuji.g.alchemy.com/v2", + "name": "Avalanche Fuji" + }, + { + "url": "https://base-mainnet.g.alchemy.com/v2", + "name": "Base Mainnet" + }, + { + "url": "https://base-sepolia.g.alchemy.com/v2", + "name": "Base Sepolia" + }, + { + "url": "https://berachain-mainnet.g.alchemy.com/v2", + "name": "Berachain Mainnet" + }, + { + "url": "https://berachain-bepolia.g.alchemy.com/v2", + "name": "Berachain Bepolia" + }, + { + "url": "https://blast-mainnet.g.alchemy.com/v2", + "name": "Blast Mainnet" + }, + { + "url": "https://blast-sepolia.g.alchemy.com/v2", + "name": "Blast Sepolia" + }, + { + "url": "https://bnb-mainnet.g.alchemy.com/v2", + "name": "BNB Smart Chain Mainnet" + }, + { + "url": "https://bnb-testnet.g.alchemy.com/v2", + "name": "BNB Smart Chain Testnet" + }, + { + "url": "https://celo-mainnet.g.alchemy.com/v2", + "name": "Celo Mainnet" + }, + { + "url": "https://celo-sepolia.g.alchemy.com/v2", + "name": "Celo Sepolia" + }, + { + "url": "https://gensyn-mainnet.g.alchemy.com/v2", + "name": "Gensyn Mainnet" + }, + { + "url": "https://gensyn-testnet.g.alchemy.com/v2", + "name": "Gensyn Testnet" + }, + { + "url": "https://gnosis-mainnet.g.alchemy.com/v2", + "name": "Gnosis Mainnet" + }, + { + "url": "https://gnosis-chiado.g.alchemy.com/v2", + "name": "Gnosis Chiado" + }, + { + "url": "https://hyperliquid-mainnet.g.alchemy.com/v2", + "name": "Hyperliquid Mainnet" + }, + { + "url": "https://hyperliquid-testnet.g.alchemy.com/v2", + "name": "Hyperliquid Testnet" + }, + { + "url": "https://ink-mainnet.g.alchemy.com/v2", + "name": "Ink Mainnet" + }, + { + "url": "https://ink-sepolia.g.alchemy.com/v2", + "name": "Ink Sepolia" + }, + { + "url": "https://lens-mainnet.g.alchemy.com/v2", + "name": "Lens Mainnet" + }, + { + "url": "https://lens-sepolia.g.alchemy.com/v2", + "name": "Lens Sepolia" + }, + { + "url": "https://linea-mainnet.g.alchemy.com/v2", + "name": "Linea Mainnet" + }, + { + "url": "https://linea-sepolia.g.alchemy.com/v2", + "name": "Linea Sepolia" + }, + { + "url": "https://polygon-mainnet.g.alchemy.com/v2", + "name": "Polygon Mainnet" + }, + { + "url": "https://polygon-amoy.g.alchemy.com/v2", + "name": "Polygon Amoy" + }, + { + "url": "https://monad-mainnet.g.alchemy.com/v2", + "name": "Monad Mainnet" + }, + { + "url": "https://monad-testnet.g.alchemy.com/v2", + "name": "Monad Testnet" + }, + { + "url": "https://mythos-mainnet.g.alchemy.com/v2", + "name": "Mythos Mainnet" + }, + { + "url": "https://opt-mainnet.g.alchemy.com/v2", + "name": "OP Mainnet Mainnet" + }, + { + "url": "https://opt-sepolia.g.alchemy.com/v2", + "name": "OP Mainnet Sepolia" + }, + { + "url": "https://robinhood-testnet.g.alchemy.com/v2", + "name": "Robinhood Chain Testnet" + }, + { + "url": "https://ronin-mainnet.g.alchemy.com/v2", + "name": "Ronin Mainnet" + }, + { + "url": "https://ronin-saigon.g.alchemy.com/v2", + "name": "Ronin Saigon" + }, + { + "url": "https://rootstock-mainnet.g.alchemy.com/v2", + "name": "Rootstock Mainnet" + }, + { + "url": "https://rootstock-testnet.g.alchemy.com/v2", + "name": "Rootstock Testnet" + }, + { + "url": "https://scroll-mainnet.g.alchemy.com/v2", + "name": "Scroll Mainnet" + }, + { + "url": "https://scroll-sepolia.g.alchemy.com/v2", + "name": "Scroll Sepolia" + }, + { + "url": "https://settlus-mainnet.g.alchemy.com/v2", + "name": "Settlus Mainnet" + }, + { + "url": "https://settlus-septestnet.g.alchemy.com/v2", + "name": "Settlus Sepolia" + }, + { + "url": "https://shape-mainnet.g.alchemy.com/v2", + "name": "Shape Mainnet" + }, + { + "url": "https://shape-sepolia.g.alchemy.com/v2", + "name": "Shape Sepolia" + }, + { + "url": "https://soneium-mainnet.g.alchemy.com/v2", + "name": "Soneium Mainnet" + }, + { + "url": "https://soneium-minato.g.alchemy.com/v2", + "name": "Soneium Minato" + }, + { + "url": "https://story-mainnet.g.alchemy.com/v2", + "name": "Story Mainnet" + }, + { + "url": "https://story-aeneid.g.alchemy.com/v2", + "name": "Story Aeneid" + }, + { + "url": "https://unichain-mainnet.g.alchemy.com/v2", + "name": "Unichain Mainnet" + }, + { + "url": "https://unichain-sepolia.g.alchemy.com/v2", + "name": "Unichain Sepolia" + }, + { + "url": "https://worldchain-mainnet.g.alchemy.com/v2", + "name": "World Chain Mainnet" + }, + { + "url": "https://worldchain-sepolia.g.alchemy.com/v2", + "name": "World Chain Sepolia" + }, + { + "url": "https://zetachain-mainnet.g.alchemy.com/v2", + "name": "ZetaChain Mainnet" + }, + { + "url": "https://zetachain-testnet.g.alchemy.com/v2", + "name": "ZetaChain Testnet" + }, + { + "url": "https://zksync-mainnet.g.alchemy.com/v2", + "name": "ZKsync Mainnet" + }, + { + "url": "https://zksync-sepolia.g.alchemy.com/v2", + "name": "ZKsync Sepolia" + }, + { + "url": "https://zora-mainnet.g.alchemy.com/v2", + "name": "Zora Mainnet" + }, + { + "url": "https://zora-sepolia.g.alchemy.com/v2", + "name": "Zora Sepolia" + } + ], + "methods": [ + { + "name": "alchemy_getTokenAllowance", + "description": "Returns the token allowance by spender for a given owner.", + "x-compute-units": 20, + "params": [ + { + "name": "tokenAllowanceRequest", + "required": true, + "description": "An object specifying the token contract address, the owner address, and the spender address.\n", + "schema": { + "type": "object", + "required": [ + "contract", + "owner", + "spender" + ], + "properties": { + "contract": { + "description": "20-byte token contract address.", + "title": "hex encoded address", + "type": "string", + "pattern": "^0x[0-9a-fA-F]{40}$" + }, + "owner": { + "description": "20-byte address of the owner.", + "title": "hex encoded address", + "type": "string", + "pattern": "^0x[0-9a-fA-F]{40}$" + }, + "spender": { + "description": "20-byte address of the spender.", + "title": "hex encoded address", + "type": "string", + "pattern": "^0x[0-9a-fA-F]{40}$" + } + } + } + } + ], + "result": { + "name": "Allowance", + "description": "The amount that the spender is allowed to withdraw from the owner.", + "schema": { + "type": "string", + "description": "A decimal string representing the allowance." + } + }, + "examples": [ + { + "name": "Basic allowance example", + "params": [ + { + "name": "tokenAllowanceRequest", + "value": { + "contract": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270", + "owner": "0xf1a726210550c306a9964b251cbcd3fa5ecb275d", + "spender": "0xdef1c0ded9bec7f1a1670819833240f027b25eff" + } + } + ], + "result": { + "name": "Allowance", + "value": "0" + } + } + ] + }, + { + "name": "alchemy_getTokenBalances", + "description": "Returns ERC-20 token balances for a given address.", + "x-compute-units": 20, + "params": [ + { + "name": "address", + "required": true, + "description": "A 20-byte wallet address.\n", + "schema": { + "type": "string", + "title": "Address", + "default": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + } + }, + { + "name": "tokenSpec", + "required": false, + "description": "A token specification: - The string \"erc20\" - \"NATIVE_TOKEN\" - \"DEFAULT_TOKENS\" (deprecated) - An array of token contract addresses.\n", + "schema": { + "oneOf": [ + { + "type": "string", + "title": "Token Specification", + "enum": [ + "erc20", + "DEFAULT_TOKENS", + "NATIVE_TOKEN" + ], + "default": "erc20" + }, + { + "type": "array", + "title": "Array of Contract Addresses", + "items": { + "type": "string", + "pattern": "^0[xX][0-9a-fA-F]{40}$" + } + } + ] + } + }, + { + "name": "options", + "required": false, + "description": "Optional pagination options.\n", + "schema": { + "type": "object", + "title": "Options", + "properties": { + "pageKey": { + "type": "string", + "description": "Used for pagination if more results are available." + }, + "maxCount": { + "type": "integer", + "description": "Maximum number of token balances to return per call (capped at 100).", + "default": 100 + } + } + } + } + ], + "result": { + "name": "Token Balances", + "description": "An object containing the queried address and an array of token balance objects.", + "schema": { + "type": "object", + "properties": { + "address": { + "description": "Address for which token balances were returned.", + "title": "hex encoded address", + "type": "string", + "pattern": "^0x[0-9a-fA-F]{40}$" + }, + "tokenBalances": { + "type": "array", + "description": "Array of token balance objects. Exactly one of tokenBalance or error is non-null.", + "items": { + "type": "object", + "properties": { + "contractAddress": { + "description": "The ERC-20 contract address.", + "title": "hex encoded address", + "type": "string", + "pattern": "^0x[0-9a-fA-F]{40}$" + }, + "tokenBalance": { + "type": "string", + "description": "Hex-encoded string of the token balance, or null if error is present." + } + } + } + } + } + } + }, + "examples": [ + { + "name": "Single token balance example", + "params": [ + { + "name": "address", + "value": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + }, + { + "name": "tokenSpec", + "value": [ + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + ] + } + ], + "result": { + "name": "Token Balances", + "value": { + "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + "tokenBalances": [ + { + "contractAddress": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "tokenBalance": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + ] + } + } + } + ] + }, + { + "name": "alchemy_getTokenMetadata", + "description": "Returns metadata for a given token contract (name, symbol, decimals, logo).", + "x-compute-units": 10, + "params": [ + { + "name": "contractAddress", + "required": true, + "description": "A single 20-byte token contract address.", + "schema": { + "title": "hex encoded address", + "type": "string", + "pattern": "^0x[0-9a-fA-F]{40}$" + } + } + ], + "result": { + "name": "Token Metadata", + "description": "Object with name, symbol, decimals, and an optional logo URL.", + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Token's name, or null if not found." + }, + "symbol": { + "type": "string", + "description": "Token's symbol, or null if not found." + }, + "decimals": { + "type": "number", + "description": "Number of decimals the token uses, or null if not found." + }, + "logo": { + "type": "string", + "description": "URL of the token's logo image, or null if none available." + } + } + } + }, + "examples": [ + { + "name": "USDC metadata example", + "params": [ + { + "name": "contractAddress", + "value": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + } + ], + "result": { + "name": "Token Metadata", + "value": { + "name": "USD Coin", + "symbol": "USDC", + "decimals": 6, + "logo": "https://static.alchemyapi.io/images/assets/3408.png" + } + } + } + ] + } + ], + "x-auth-params": [ + { + "name": "apiKey", + "in": "path", + "schema": { + "type": "string", + "default": "docs-demo", + "description": "For higher throughput, [create your own API key](https://dashboard.alchemy.com/signup)" + }, + "required": true + } + ] +} diff --git a/packages/api-codegen/src/rpc/openrpcWalker.ts b/packages/api-codegen/src/rpc/openrpcWalker.ts index 481b8985cf..c8abc9fe2f 100644 --- a/packages/api-codegen/src/rpc/openrpcWalker.ts +++ b/packages/api-codegen/src/rpc/openrpcWalker.ts @@ -41,6 +41,32 @@ export function closeObjectSchemas(node: unknown): unknown { return copy; } +/** + * Recursively removes `title` from schema nodes. json-schema-to-typescript + * hoists title-named subschemas as standalone exported types; when multiple + * methods in one spec share a titled subschema (e.g. "Hex Encoded Address"), + * per-method compilation emits duplicate identifiers. Stripping titles inlines + * the subschemas instead — generated internals don't need the pretty names. + * The emitter re-applies a root title to pin the exported type name. + * + * @param {unknown} node A JSON Schema node (or fragment) + * @returns {unknown} A deep copy without title fields + */ +export function stripTitles(node: unknown): unknown { + if (Array.isArray(node)) { + return node.map(stripTitles); + } + if (node === null || typeof node !== "object") { + return node; + } + const copy: Record = {}; + for (const [key, value] of Object.entries(node)) { + if (key === "title") continue; + copy[key] = stripTitles(value); + } + return copy; +} + /** * Extracts a method's param and result schemas from a bundled (dereferenced) * OpenRPC document, with object schemas closed for clean type compilation. @@ -73,7 +99,7 @@ export function extractMethod( return { name: param.name ?? `param${index}`, required: param.required === true, - schema: closeObjectSchemas(param.schema) as JsonSchema, + schema: stripTitles(closeObjectSchemas(param.schema)) as JsonSchema, }; }); @@ -84,6 +110,6 @@ export function extractMethod( return { name: methodName, params, - result: closeObjectSchemas(method.result.schema) as JsonSchema, + result: stripTitles(closeObjectSchemas(method.result.schema)) as JsonSchema, }; } diff --git a/packages/data-apis/codegen.manifest.ts b/packages/data-apis/codegen.manifest.ts index ce041ddaaf..c35720042b 100644 --- a/packages/data-apis/codegen.manifest.ts +++ b/packages/data-apis/codegen.manifest.ts @@ -20,6 +20,28 @@ export default { }, ], }, + { + spec: "prices", + schemaTypeName: "PricesRestSchema", + // Spec paths: /{apiKey}/tokens/by-symbol etc. Runtime auth is + // header-based against https://api.g.alchemy.com/prices/v1. + pathRules: { stripApiKeySegment: true }, + operations: [ + { + operationId: "get-token-prices-by-symbol", + exportBaseName: "GetTokenPricesBySymbol", + emitQueryType: true, + }, + { + operationId: "get-token-prices-by-address", + exportBaseName: "GetTokenPricesByAddress", + }, + { + operationId: "get-historical-token-prices", + exportBaseName: "GetHistoricalTokenPrices", + }, + ], + }, { spec: "nft", schemaTypeName: "NftRestSchema", @@ -46,5 +68,23 @@ export default { }, ], }, + { + spec: "token", + schemaTypeName: "TokenRpcSchema", + methods: [ + { + method: "alchemy_getTokenBalances", + exportBaseName: "AlchemyGetTokenBalances", + }, + { + method: "alchemy_getTokenMetadata", + exportBaseName: "AlchemyGetTokenMetadata", + }, + { + method: "alchemy_getTokenAllowance", + exportBaseName: "AlchemyGetTokenAllowance", + }, + ], + }, ], } satisfies CodegenManifest; diff --git a/packages/data-apis/src/generated/rest/portfolio.types.ts b/packages/data-apis/src/generated/rest/portfolio.types.ts index 5cacfec454..b0adf078e7 100644 --- a/packages/data-apis/src/generated/rest/portfolio.types.ts +++ b/packages/data-apis/src/generated/rest/portfolio.types.ts @@ -163,7 +163,7 @@ export interface components { */ includeNativeTokens: boolean; /** - * @description Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @description Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. * @default true */ includeErc20Tokens: boolean; @@ -188,12 +188,12 @@ export interface components { networks: string[]; }[]; /** - * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. * @default true */ withMetadata: boolean; /** - * @description Boolean - if set to `true`, returns token prices. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @description Boolean - if set to `true`, returns token prices. Setting this to false will reduce payload size and may result in a faster API call. * @default true */ withPrices: boolean; @@ -204,7 +204,7 @@ export interface components { */ includeNativeTokens: boolean; /** - * @description Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @description Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. * @default true */ includeErc20Tokens: boolean; @@ -267,7 +267,7 @@ export interface components { spamConfidenceLevel?: "VERY_HIGH" | "HIGH" | "MEDIUM" | "LOW"; }[]; /** - * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. * @default true */ withMetadata: boolean; @@ -295,7 +295,7 @@ export interface components { networks: string[]; }[]; /** - * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. * @default true */ withMetadata: boolean; @@ -1038,12 +1038,12 @@ export interface operations { networks: string[]; }[]; /** - * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. * @default true */ withMetadata?: boolean; /** - * @description Boolean - if set to `true`, returns token prices. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @description Boolean - if set to `true`, returns token prices. Setting this to false will reduce payload size and may result in a faster API call. * @default true */ withPrices?: boolean; @@ -1054,7 +1054,7 @@ export interface operations { */ includeNativeTokens?: boolean; /** - * @description Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @description Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. * @default true */ includeErc20Tokens?: boolean; @@ -1195,7 +1195,7 @@ export interface operations { */ includeNativeTokens?: boolean; /** - * @description Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @description Boolean - if set to `true`, returns ERC-20 tokens. Setting this to false will reduce payload size and may result in a faster API call. * @default true */ includeErc20Tokens?: boolean; @@ -1328,7 +1328,7 @@ export interface operations { spamConfidenceLevel?: "VERY_HIGH" | "HIGH" | "MEDIUM" | "LOW"; }[]; /** - * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. * @default true */ withMetadata?: boolean; @@ -1589,7 +1589,7 @@ export interface operations { spamConfidenceLevel?: "VERY_HIGH" | "HIGH" | "MEDIUM" | "LOW"; }[]; /** - * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. Defaults to `true`. + * @description Boolean - if set to `true`, returns metadata. Setting this to false will reduce payload size and may result in a faster API call. * @default true */ withMetadata?: boolean; diff --git a/packages/data-apis/src/generated/rest/prices.schema.ts b/packages/data-apis/src/generated/rest/prices.schema.ts new file mode 100644 index 0000000000..556f6cca20 --- /dev/null +++ b/packages/data-apis/src/generated/rest/prices.schema.ts @@ -0,0 +1,68 @@ +/* eslint-disable -- machine-generated file */ +// AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. +// Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) +// is recorded in packages/api-codegen/specs/specs.lock.json. + +import type { RestRequestSchema } from "@alchemy/common"; +import type { operations } from "./prices.types.js"; + +/** 200 response for get-token-prices-by-symbol. */ +export type GetTokenPricesBySymbolResponse = + operations["get-token-prices-by-symbol"]["responses"]["200"]["content"]["application/json"]; + +/** + * Query params for get-token-prices-by-symbol. Sent via the URL at runtime; + * RestRequestSchema has no query channel, so this is exposed as a + * standalone type for the SDK's params layer. + */ +export type GetTokenPricesBySymbolQuery = NonNullable< + operations["get-token-prices-by-symbol"]["parameters"]["query"] +>; + +/** Request body for get-token-prices-by-address. */ +export type GetTokenPricesByAddressBody = NonNullable< + operations["get-token-prices-by-address"]["requestBody"] +>["content"]["application/json"]; + +/** 200 response for get-token-prices-by-address. */ +export type GetTokenPricesByAddressResponse = + operations["get-token-prices-by-address"]["responses"]["200"]["content"]["application/json"]; + +/** Request body for get-historical-token-prices. */ +export type GetHistoricalTokenPricesBody = NonNullable< + operations["get-historical-token-prices"]["requestBody"] +>["content"]["application/json"]; + +/** 200 response for get-historical-token-prices. */ +export type GetHistoricalTokenPricesResponse = + operations["get-historical-token-prices"]["responses"]["200"]["content"]["application/json"]; + +/** RestRequestSchema entries for the prices REST API. */ +export type PricesRestSchema = readonly [ + { + /** GET /{apiKey}/tokens/by-symbol (operationId: get-token-prices-by-symbol) */ + Route: "tokens/by-symbol"; + Method: "GET"; + Body?: undefined; + Response: GetTokenPricesBySymbolResponse; + }, + { + /** POST /{apiKey}/tokens/by-address (operationId: get-token-prices-by-address) */ + Route: "tokens/by-address"; + Method: "POST"; + Body: GetTokenPricesByAddressBody; + Response: GetTokenPricesByAddressResponse; + }, + { + /** POST /{apiKey}/tokens/historical (operationId: get-historical-token-prices) */ + Route: "tokens/historical"; + Method: "POST"; + Body: GetHistoricalTokenPricesBody; + Response: GetHistoricalTokenPricesResponse; + }, +]; + +/** Compile-time guard that the emitted tuple satisfies the shared constraint. */ +export type _AssertPricesRestSchema = PricesRestSchema extends RestRequestSchema + ? true + : never; diff --git a/packages/data-apis/src/generated/rest/prices.types.ts b/packages/data-apis/src/generated/rest/prices.types.ts new file mode 100644 index 0000000000..8afa73c9f2 --- /dev/null +++ b/packages/data-apis/src/generated/rest/prices.types.ts @@ -0,0 +1,735 @@ +/* eslint-disable -- machine-generated file */ +// AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. +// Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) +// is recorded in packages/api-codegen/specs/specs.lock.json. + +export interface paths { + "/{apiKey}/tokens/by-symbol": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Token Prices By Symbol + * @description Fetches current prices for multiple tokens using their symbols. Returns a list of token prices, each containing the symbol, prices, and an optional error field. + */ + get: operations["get-token-prices-by-symbol"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{apiKey}/tokens/by-address": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Token Prices By Address + * @description Fetches current prices for multiple tokens using network and address pairs. Returns a list of token prices, each containing the network, address, prices, and an optional error field. + */ + post: operations["get-token-prices-by-address"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{apiKey}/tokens/historical": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Historical Token Prices + * @description Provides historical price data for a single token over a time range. You can identify the token by symbol or by network and contract address. + */ + post: operations["get-historical-token-prices"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + TokenPricesResponse: { + /** @description List of token price data. */ + data: { + /** @description Token symbol. */ + symbol: string; + /** @description List of price information. */ + prices: { + /** @description Currency code (e.g., USD). */ + currency: string; + /** @description Price value as a string. */ + value: string; + /** + * Format: date-time + * @description Time when the price was last updated. + */ + lastUpdatedAt: string; + }[]; + /** @description Error message if applicable. */ + error: string; + }[]; + }; + TokenPriceResponseItem: { + /** @description Token symbol. */ + symbol: string; + /** @description List of price information. */ + prices: { + /** @description Currency code (e.g., USD). */ + currency: string; + /** @description Price value as a string. */ + value: string; + /** + * Format: date-time + * @description Time when the price was last updated. + */ + lastUpdatedAt: string; + }[]; + /** @description Error message if applicable. */ + error: string; + }; + ByAddressRequest: { + /** @description Array of token network and address pairs (limit 25 addresses, max 3 networks). Networks should match network enums. */ + addresses: { + /** + * @description Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default eth-mainnet + * @example eth-mainnet + */ + network: string; + /** + * @description Token contract address. + * @default 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + * @example 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + */ + address: string; + }[]; + }; + AddressPricesResponse: { + /** @description List of token prices by address. */ + data: { + /** @description Network identifier. */ + network: string; + /** @description Token contract address. */ + address: string; + /** @description List of price information. */ + prices: { + /** @description Currency code (e.g., USD). */ + currency: string; + /** @description Price value as a string. */ + value: string; + /** + * Format: date-time + * @description Time when the price was last updated. + */ + lastUpdatedAt: string; + }[]; + /** @description Error message if applicable. */ + error: string; + }[]; + }; + AddressPriceResponseItem: { + /** @description Network identifier. */ + network: string; + /** @description Token contract address. */ + address: string; + /** @description List of price information. */ + prices: { + /** @description Currency code (e.g., USD). */ + currency: string; + /** @description Price value as a string. */ + value: string; + /** + * Format: date-time + * @description Time when the price was last updated. + */ + lastUpdatedAt: string; + }[]; + /** @description Error message if applicable. */ + error: string; + }; + /** @description Response containing historical price data. It will either include `symbol` or both `network` and `address` based on the request. */ + HistoricalPricesResponse: + | { + /** + * @description Token symbol. + * @default ETH + * @example ETH + */ + symbol: string; + /** + * @description Currency identifier. + * @default usd + * @example usd + */ + currency: string; + /** @description List of historical price data points. */ + data: { + /** + * @description Price value as a string. + * @example 1900.00 + */ + value: string; + /** + * Format: date-time + * @description Timestamp of the price data point. + * @example 2024-01-01T00:00:00Z + */ + timestamp: string; + /** + * @description Total market capitalization at the timestamp + * @example 274292310008.21802 + */ + marketCap?: string; + /** + * @description Volume traded during the defined interval + * @example 6715146404.608721 + */ + totalVolume?: string; + }[]; + } + | { + /** + * @description Network identifier. + * @default eth-mainnet + * @example eth-mainnet + */ + network: string; + /** + * @description Token contract address. + * @default 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + * @example 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + */ + address: string; + /** + * @description Currency identifier. + * @default usd + * @example usd + */ + currency: string; + /** @description List of historical price data points. */ + data: { + /** + * @description Price value as a string. + * @example 1900.00 + */ + value: string; + /** + * Format: date-time + * @description Timestamp of the price data point. + * @example 2024-01-01T00:00:00Z + */ + timestamp: string; + /** + * @description Total market capitalization at the timestamp + * @example 274292310008.21802 + */ + marketCap?: string; + /** + * @description Volume traded during the defined interval + * @example 6715146404.608721 + */ + totalVolume?: string; + }[]; + }; + HistoricalPricesBySymbol: { + /** + * @description Token symbol. + * @default ETH + * @example ETH + */ + symbol: string; + /** + * @description Currency identifier. + * @default usd + * @example usd + */ + currency: string; + /** @description List of historical price data points. */ + data: { + /** + * @description Price value as a string. + * @example 1900.00 + */ + value: string; + /** + * Format: date-time + * @description Timestamp of the price data point. + * @example 2024-01-01T00:00:00Z + */ + timestamp: string; + /** + * @description Total market capitalization at the timestamp + * @example 274292310008.21802 + */ + marketCap?: string; + /** + * @description Volume traded during the defined interval + * @example 6715146404.608721 + */ + totalVolume?: string; + }[]; + }; + HistoricalPricesByAddress: { + /** + * @description Network identifier. + * @default eth-mainnet + * @example eth-mainnet + */ + network: string; + /** + * @description Token contract address. + * @default 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + * @example 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + */ + address: string; + /** + * @description Currency identifier. + * @default usd + * @example usd + */ + currency: string; + /** @description List of historical price data points. */ + data: { + /** + * @description Price value as a string. + * @example 1900.00 + */ + value: string; + /** + * Format: date-time + * @description Timestamp of the price data point. + * @example 2024-01-01T00:00:00Z + */ + timestamp: string; + /** + * @description Total market capitalization at the timestamp + * @example 274292310008.21802 + */ + marketCap?: string; + /** + * @description Volume traded during the defined interval + * @example 6715146404.608721 + */ + totalVolume?: string; + }[]; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + "get-token-prices-by-symbol": { + parameters: { + query: { + /** @description Array of token symbols (limit 25). Example: symbols=[ETH,BTC] */ + symbols: string[]; + }; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful response, even if some tokens are missing. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of token price data. */ + data: { + /** @description Token symbol. */ + symbol: string; + /** @description List of price information. */ + prices: { + /** @description Currency code (e.g., USD). */ + currency: string; + /** @description Price value as a string. */ + value: string; + /** + * Format: date-time + * @description Time when the price was last updated. + */ + lastUpdatedAt: string; + }[]; + /** @description Error message if applicable. */ + error: string; + }[]; + }; + }; + }; + /** @description Bad Request: Malformed request or missing parameters. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + /** @description Too Many Requests: Rate limit exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + }; + }; + "get-token-prices-by-address": { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Array of token network and address pairs (limit 25 addresses, max 3 networks). Networks should match network enums. */ + addresses: { + /** + * @description Network identifier (e.g., eth-mainnet). Find more network enums [here](https://dashboard.alchemy.com/chains) + * @default eth-mainnet + * @example eth-mainnet + */ + network: string; + /** + * @description Token contract address. + * @default 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + * @example 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + */ + address: string; + }[]; + }; + }; + }; + responses: { + /** @description Successful response, even if some prices are missing. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description List of token prices by address. */ + data: { + /** @description Network identifier. */ + network: string; + /** @description Token contract address. */ + address: string; + /** @description List of price information. */ + prices: { + /** @description Currency code (e.g., USD). */ + currency: string; + /** @description Price value as a string. */ + value: string; + /** + * Format: date-time + * @description Time when the price was last updated. + */ + lastUpdatedAt: string; + }[]; + /** @description Error message if applicable. */ + error: string; + }[]; + }; + }; + }; + /** @description Bad Request: Invalid input (e.g., malformed JSON). */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + /** @description Too Many Requests: Rate limit exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + }; + }; + "get-historical-token-prices": { + parameters: { + query?: never; + header?: never; + path: { + apiKey: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": + | { + /** + * @description Token symbol (e.g., ETH, BTC). + * @default ETH + * @example ETH + */ + symbol: string; + /** + * @description Start of the time range. + * @example 2024-01-01T00:00:00Z + */ + startTime: string | number; + /** + * @description End of the time range. + * @example 2024-01-31T23:59:59Z + */ + endTime: string | number; + /** + * @description Time interval for data points. Max ranges: (5m, 7d), (1h, 30d), (1d, 1yr) + * @default 1d + * @example 1d + * @enum {string} + */ + interval?: "5m" | "1h" | "1d"; + /** + * @description Whether to include market cap and volume for each token + * @default false + * @example true + */ + withMarketData?: boolean; + } + | { + /** + * @description Network identifier (e.g., eth-mainnet). + * @default eth-mainnet + * @example eth-mainnet + */ + network: string; + /** + * @description Token contract address. + * @default 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + * @example 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + */ + address: string; + /** + * @description Start of the time range. + * @example 2024-01-01T00:00:00Z + */ + startTime: string | number; + /** + * @description End of the time range. + * @example 2024-01-31T23:59:59Z + */ + endTime: string | number; + /** + * @description Time interval for data points. Max ranges: (5m, 7d), (1h, 30d), (1d, 1yr) + * @default 1d + * @example 1d + * @enum {string} + */ + interval?: "5m" | "1h" | "1d"; + /** + * @description Whether to include market cap and volume for each token + * @default false + * @example true + */ + withMarketData?: boolean; + }; + }; + }; + responses: { + /** @description Successful response with historical price data. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": + | { + /** + * @description Token symbol. + * @default ETH + * @example ETH + */ + symbol: string; + /** + * @description Currency identifier. + * @default usd + * @example usd + */ + currency: string; + /** @description List of historical price data points. */ + data: { + /** + * @description Price value as a string. + * @example 1900.00 + */ + value: string; + /** + * Format: date-time + * @description Timestamp of the price data point. + * @example 2024-01-01T00:00:00Z + */ + timestamp: string; + /** + * @description Total market capitalization at the timestamp + * @example 274292310008.21802 + */ + marketCap?: string; + /** + * @description Volume traded during the defined interval + * @example 6715146404.608721 + */ + totalVolume?: string; + }[]; + } + | { + /** + * @description Network identifier. + * @default eth-mainnet + * @example eth-mainnet + */ + network: string; + /** + * @description Token contract address. + * @default 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + * @example 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + */ + address: string; + /** + * @description Currency identifier. + * @default usd + * @example usd + */ + currency: string; + /** @description List of historical price data points. */ + data: { + /** + * @description Price value as a string. + * @example 1900.00 + */ + value: string; + /** + * Format: date-time + * @description Timestamp of the price data point. + * @example 2024-01-01T00:00:00Z + */ + timestamp: string; + /** + * @description Total market capitalization at the timestamp + * @example 274292310008.21802 + */ + marketCap?: string; + /** + * @description Volume traded during the defined interval + * @example 6715146404.608721 + */ + totalVolume?: string; + }[]; + }; + }; + }; + /** @description Bad Request: Invalid input (e.g., malformed request, missing parameters). */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + /** @description Not Found: Token not found. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + /** @description Too Many Requests: Rate limit exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Error details. */ + error: { + /** @description Detailed error message. */ + message: string; + }; + }; + }; + }; + }; + }; +} diff --git a/packages/data-apis/src/generated/rpc/token.ts b/packages/data-apis/src/generated/rpc/token.ts new file mode 100644 index 0000000000..036e4dcde4 --- /dev/null +++ b/packages/data-apis/src/generated/rpc/token.ts @@ -0,0 +1,105 @@ +/* eslint-disable -- machine-generated file */ +// AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. +// Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) +// is recorded in packages/api-codegen/specs/specs.lock.json. + +export type AlchemyGetTokenBalancesAddressParam = string; + +export type AlchemyGetTokenBalancesTokenSpecParam = + | ("erc20" | "DEFAULT_TOKENS" | "NATIVE_TOKEN") + | string[]; + +export interface AlchemyGetTokenBalancesOptionsParam { + /** + * Used for pagination if more results are available. + */ + pageKey?: string; + /** + * Maximum number of token balances to return per call (capped at 100). + */ + maxCount?: number; +} + +export interface AlchemyGetTokenBalancesResult { + /** + * Address for which token balances were returned. + */ + address?: string; + /** + * Array of token balance objects. Exactly one of tokenBalance or error is non-null. + */ + tokenBalances?: { + /** + * The ERC-20 contract address. + */ + contractAddress?: string; + /** + * Hex-encoded string of the token balance, or null if error is present. + */ + tokenBalance?: string; + }[]; +} + +export type AlchemyGetTokenMetadataParams = string; + +export interface AlchemyGetTokenMetadataResult { + /** + * Token's name, or null if not found. + */ + name?: string; + /** + * Token's symbol, or null if not found. + */ + symbol?: string; + /** + * Number of decimals the token uses, or null if not found. + */ + decimals?: number; + /** + * URL of the token's logo image, or null if none available. + */ + logo?: string; +} + +export interface AlchemyGetTokenAllowanceParams { + /** + * 20-byte token contract address. + */ + contract: string; + /** + * 20-byte address of the owner. + */ + owner: string; + /** + * 20-byte address of the spender. + */ + spender: string; +} + +/** + * A decimal string representing the allowance. + */ +export type AlchemyGetTokenAllowanceResult = string; + +/** viem RpcSchema entries for the token JSON-RPC methods. */ +export type TokenRpcSchema = [ + { + Method: "alchemy_getTokenBalances"; + Parameters: [ + address: AlchemyGetTokenBalancesAddressParam, + tokenSpec?: AlchemyGetTokenBalancesTokenSpecParam, + options?: AlchemyGetTokenBalancesOptionsParam, + ]; + ReturnType: AlchemyGetTokenBalancesResult; + }, + { + Method: "alchemy_getTokenMetadata"; + Parameters: [contractAddress: AlchemyGetTokenMetadataParams]; + ReturnType: AlchemyGetTokenMetadataResult; + }, + { + Method: "alchemy_getTokenAllowance"; + Parameters: [tokenAllowanceRequest: AlchemyGetTokenAllowanceParams]; + ReturnType: AlchemyGetTokenAllowanceResult; + }, +]; diff --git a/packages/data-apis/src/generated/rpc/transfers.ts b/packages/data-apis/src/generated/rpc/transfers.ts index 9cb54064dc..19b69c30be 100644 --- a/packages/data-apis/src/generated/rpc/transfers.ts +++ b/packages/data-apis/src/generated/rpc/transfers.ts @@ -34,7 +34,7 @@ export interface AlchemyGetAssetTransfersParams { } export type AlchemyGetAssetTransfersResult = - | NotFoundNull + | string | { /** * Uuid of next page of results (if exists, else blank). @@ -113,7 +113,6 @@ export type AlchemyGetAssetTransfersResult = }; }[]; }; -export type NotFoundNull = string; /** viem RpcSchema entries for the transfers JSON-RPC methods. */ export type TransfersRpcSchema = [ From c117d02b7fd24af0668ca94bccf6ce0a2b8840f8 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Wed, 10 Jun 2026 10:25:43 -0400 Subject: [PATCH 07/20] feat(common): harden AlchemyRestClient (query, retries, timeout, request-id) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RestRequestSchema gains an optional Query channel; RestRequestParams maps it via a keyof guard so legacy entries without Query keep compiling and reject query payloads. Route-literal Response narrowing unchanged. - Bounded retries with exponential backoff on 429/5xx/network failures only, honoring Retry-After; per-attempt AbortSignal.timeout merged with the caller's signal (caller aborts are never retried); per-request UUID sent as X-Alchemy-Client-Request-Id on every attempt. - New AlchemyApiError base (status/code/requestId/retryAfter) under BaseError; ServerError/FetchError re-parented onto it with an additive trailing details param — existing instanceof checks and constructor calls are unaffected. - composeSignals/sleep utils; 11 new rest client tests (query serialization, retry/backoff/Retry-After, abort, request-id propagation, error fields). Co-Authored-By: Claude Fable 5 --- packages/common/src/errors/AlchemyApiError.ts | 72 +++++++ packages/common/src/errors/FetchError.ts | 18 +- packages/common/src/errors/ServerError.ts | 19 +- packages/common/src/index.ts | 3 + packages/common/src/rest/restClient.ts | 172 ++++++++++++++-- packages/common/src/rest/types.ts | 40 +++- packages/common/src/utils/signals.ts | 55 +++++ packages/common/tests/rest/restClient.test.ts | 191 ++++++++++++++++++ 8 files changed, 543 insertions(+), 27 deletions(-) create mode 100644 packages/common/src/errors/AlchemyApiError.ts create mode 100644 packages/common/src/utils/signals.ts create mode 100644 packages/common/tests/rest/restClient.test.ts diff --git a/packages/common/src/errors/AlchemyApiError.ts b/packages/common/src/errors/AlchemyApiError.ts new file mode 100644 index 0000000000..006ca134e1 --- /dev/null +++ b/packages/common/src/errors/AlchemyApiError.ts @@ -0,0 +1,72 @@ +import { BaseError } from "./BaseError.js"; + +/** Normalized failure metadata shared across REST and JSON-RPC channels. */ +export type AlchemyApiErrorDetails = { + /** HTTP status code, when the failure was an HTTP response. */ + status?: number; + /** Provider error code: JSON-RPC `error.code` or a REST error-body code. */ + code?: number | string; + /** The client-generated X-Alchemy-Client-Request-Id sent with the request. */ + requestId?: string; + /** Parsed Retry-After hint in milliseconds, when the server provided one. */ + retryAfter?: number; +}; + +/** + * The normalized error family for Alchemy API failures. Both the REST channel + * (AlchemyRestClient → ServerError/FetchError, which extend this class) and + * SDK JSON-RPC actions surface failures as AlchemyApiError, so consumers can + * handle status/code/requestId/retryAfter uniformly: + * + * ```ts + * try { ... } catch (e) { + * if (e instanceof AlchemyApiError && e.status === 429) { + * await sleep(e.retryAfter ?? 1_000); + * } + * } + * ``` + */ +export class AlchemyApiError extends BaseError { + override name = "AlchemyApiError"; + + /** HTTP status code, when the failure was an HTTP response. */ + readonly status?: number; + /** Provider error code: JSON-RPC `error.code` or a REST error-body code. */ + readonly code?: number | string; + /** The client-generated X-Alchemy-Client-Request-Id sent with the request. */ + readonly requestId?: string; + /** Parsed Retry-After hint in milliseconds, when the server provided one. */ + readonly retryAfter?: number; + + /** + * Creates a normalized API error. + * + * @param {string} shortMessage The headline error message + * @param {AlchemyApiErrorDetails & { cause?: Error; details?: string; metaMessages?: string[] }} [args] Failure metadata plus BaseError options + */ + constructor( + shortMessage: string, + args: AlchemyApiErrorDetails & { + cause?: Error; + details?: string; + metaMessages?: string[]; + } = {}, + ) { + const { status, code, requestId, retryAfter, ...baseArgs } = args; + const metaMessages = [ + ...(baseArgs.metaMessages ?? []), + ...(requestId ? [`Request ID: ${requestId}`] : []), + ]; + // BaseError takes cause XOR details; forward whichever was provided. + super( + shortMessage, + baseArgs.cause + ? { cause: baseArgs.cause, metaMessages } + : { details: baseArgs.details, metaMessages }, + ); + this.status = status; + this.code = code; + this.requestId = requestId; + this.retryAfter = retryAfter; + } +} diff --git a/packages/common/src/errors/FetchError.ts b/packages/common/src/errors/FetchError.ts index e6b014b89e..08b65a55a0 100644 --- a/packages/common/src/errors/FetchError.ts +++ b/packages/common/src/errors/FetchError.ts @@ -1,21 +1,31 @@ -import { BaseError } from "./BaseError.js"; +import { + AlchemyApiError, + type AlchemyApiErrorDetails, +} from "./AlchemyApiError.js"; /** * Error class representing a "Fetch Error" error, typically thrown when a fetch request fails. */ -export class FetchError extends BaseError { +export class FetchError extends AlchemyApiError { override name = "FetchError"; /** - * Initializes a new instance of the error message with a default message indicating that no chain was supplied to the client. + * Initializes a new fetch error for a request that failed before a response. * * @param {string} route - The route that failed to fetch. * @param {string} method - The HTTP method that was used. * @param {Error} cause - The cause of the error. + * @param {AlchemyApiErrorDetails} apiDetails - Normalized failure metadata (requestId). */ - constructor(route: string, method: string, cause?: Error) { + constructor( + route: string, + method: string, + cause?: Error, + apiDetails?: AlchemyApiErrorDetails, + ) { super(`[${method}] ${route} failed to fetch`, { cause, + ...apiDetails, }); } } diff --git a/packages/common/src/errors/ServerError.ts b/packages/common/src/errors/ServerError.ts index 8be8d719c8..575fd792e2 100644 --- a/packages/common/src/errors/ServerError.ts +++ b/packages/common/src/errors/ServerError.ts @@ -1,21 +1,32 @@ -import { BaseError } from "./BaseError.js"; +import { + AlchemyApiError, + type AlchemyApiErrorDetails, +} from "./AlchemyApiError.js"; /** * Error class representing a "Server Error" error, typically thrown when a server request fails. */ -export class ServerError extends BaseError { +export class ServerError extends AlchemyApiError { override name = "ServerError"; /** - * Initializes a new instance of the error message with a default message indicating that no chain was supplied to the client. + * Initializes a new server error for a failed HTTP response. * * @param {string} message - The error message. * @param {number} status - The HTTP status code of the error. * @param {Error} cause - The cause of the error. + * @param {AlchemyApiErrorDetails} apiDetails - Normalized failure metadata (requestId, retryAfter, code). */ - constructor(message: string, status: number, cause?: Error) { + constructor( + message: string, + status: number, + cause?: Error, + apiDetails?: AlchemyApiErrorDetails, + ) { super(`HTTP request failed with status ${status}: ${message}`, { cause, + ...apiDetails, + status, }); } } diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 2d927a9751..93516801a0 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -25,6 +25,7 @@ export { resolveNetwork } from "./networks/networkRegistry.js"; // utils export type * from "./utils/types.js"; +export { composeSignals, sleep } from "./utils/signals.js"; export { assertNever } from "./utils/assertNever.js"; export { raise } from "./utils/raise.js"; export { bigIntMultiply, bigIntMax } from "./utils/bigint.js"; @@ -40,6 +41,8 @@ export { // errors export { BaseError } from "./errors/BaseError.js"; +export type { AlchemyApiErrorDetails } from "./errors/AlchemyApiError.js"; +export { AlchemyApiError } from "./errors/AlchemyApiError.js"; export { ChainNotFoundError } from "./errors/ChainNotFoundError.js"; export { AccountNotFoundError } from "./errors/AccountNotFoundError.js"; export { ConnectionConfigError } from "./errors/ConnectionConfigError.js"; diff --git a/packages/common/src/rest/restClient.ts b/packages/common/src/rest/restClient.ts index 014508d651..b8fc3bd4bf 100644 --- a/packages/common/src/rest/restClient.ts +++ b/packages/common/src/rest/restClient.ts @@ -1,10 +1,15 @@ import { FetchError } from "../errors/FetchError.js"; import { ServerError } from "../errors/ServerError.js"; import { withAlchemyHeaders } from "../utils/headers.js"; -import type { RestRequestFn, RestRequestSchema } from "./types.js"; +import { composeSignals, sleep } from "../utils/signals.js"; +import type { QueryParams, RestRequestFn, RestRequestSchema } from "./types.js"; const ALCHEMY_API_URL = "https://api.g.alchemy.com"; +const DEFAULT_RETRY_COUNT = 3; +const DEFAULT_RETRY_DELAY_MS = 150; +const DEFAULT_TIMEOUT_MS = 10_000; + /** * Parameters for creating an AlchemyRestClient instance. */ @@ -17,48 +22,181 @@ export type AlchemyRestClientParams = { url?: string; /** Custom headers to be sent with requests */ headers?: HeadersInit; + /** Max retry attempts after the initial request (default 3; retries 429/5xx/network only) */ + retryCount?: number; + /** Base backoff delay in ms, doubled per attempt (default 150) */ + retryDelay?: number; + /** Per-attempt timeout in ms (default 10000) */ + timeout?: number; }; /** - * A client for making requests to Alchemy's non-JSON-RPC endpoints. + * Serializes a query object to a URL search string. Skips null/undefined + * values; array values append the key repeatedly (wire keys that need the + * bracketed form, e.g. "contractAddresses[]", carry the brackets in the key + * itself). + * + * @param {QueryParams | undefined} query The query object + * @returns {string} A "?key=value..." string, or "" when there is nothing to send + */ +function serializeQuery(query: QueryParams | undefined): string { + if (!query) return ""; + const search = new URLSearchParams(); + for (const [key, value] of Object.entries(query)) { + if (value == null) continue; + if (Array.isArray(value)) { + for (const entry of value) { + if (entry != null) search.append(key, String(entry)); + } + } else { + search.append(key, String(value)); + } + } + const serialized = search.toString(); + return serialized ? `?${serialized}` : ""; +} + +/** + * Parses a Retry-After response header into milliseconds (integer seconds or + * HTTP-date forms). + * + * @param {Response} response The HTTP response + * @returns {number | undefined} Milliseconds to wait, or undefined when absent/unparseable + */ +function parseRetryAfter(response: Response): number | undefined { + const header = response.headers.get("Retry-After"); + if (!header) return undefined; + const seconds = Number(header); + if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000); + const date = Date.parse(header); + if (!Number.isNaN(date)) return Math.max(0, date - Date.now()); + return undefined; +} + +/** + * Best-effort extraction of an error code from a JSON error body. + * + * @param {string} bodyText The raw response body + * @returns {number | string | undefined} The error code, when present + */ +function parseErrorCode(bodyText: string): number | string | undefined { + try { + const parsed = JSON.parse(bodyText); + const code = parsed?.error?.code ?? parsed?.code; + return typeof code === "number" || typeof code === "string" + ? code + : undefined; + } catch { + return undefined; + } +} + +/** + * A client for making requests to Alchemy's non-JSON-RPC endpoints, with + * typed routes/bodies/queries (via a RestRequestSchema), bounded retries with + * exponential backoff (429/5xx/network only, honoring Retry-After), + * per-attempt timeouts, abort support, and a per-request idempotency id sent + * as X-Alchemy-Client-Request-Id and surfaced on thrown errors. */ export class AlchemyRestClient { private readonly url: string; private readonly headers: Headers; + private readonly retryCount: number; + private readonly retryDelay: number; + private readonly timeout: number; /** * Creates a new instance of AlchemyRestClient. * - * @param {AlchemyRestClientParams} params - The parameters for configuring the client, including API key, JWT, custom URL, and headers. + * @param {AlchemyRestClientParams} params - The parameters for configuring the client, including API key, JWT, custom URL, headers, and retry/timeout defaults. */ - constructor({ apiKey, jwt, url, headers }: AlchemyRestClientParams) { + constructor({ + apiKey, + jwt, + url, + headers, + retryCount, + retryDelay, + timeout, + }: AlchemyRestClientParams) { this.url = url ?? ALCHEMY_API_URL; this.headers = new Headers(withAlchemyHeaders({ headers, apiKey, jwt })); + this.retryCount = retryCount ?? DEFAULT_RETRY_COUNT; + this.retryDelay = retryDelay ?? DEFAULT_RETRY_DELAY_MS; + this.timeout = timeout ?? DEFAULT_TIMEOUT_MS; } /** - * Makes an HTTP request to an Alchemy non-JSON-RPC endpoint. + * Makes an HTTP request to an Alchemy non-JSON-RPC endpoint. Retries + * 429/5xx/network failures with exponential backoff (honoring Retry-After); + * other statuses throw immediately. A caller-initiated abort is never + * retried. * * @param {RestRequestFn} params - The parameters for the request * @returns {Promise} The response from the request */ public request: RestRequestFn = async (params) => { - const response = await fetch(`${this.url}/${params.route}`, { - method: params.method, - body: params.body ? JSON.stringify(params.body) : undefined, - headers: this.headers, - }).catch((error) => { - throw new FetchError(params.route, params.method, error); - }); - - if (!response.ok) { + const requestId = crypto.randomUUID(); + const retryCount = params.retryCount ?? this.retryCount; + const retryDelay = params.retryDelay ?? this.retryDelay; + const timeout = params.timeout ?? this.timeout; + + const headers = new Headers(this.headers); + headers.set("X-Alchemy-Client-Request-Id", requestId); + + const url = `${this.url}/${params.route}${serializeQuery( + params.query as QueryParams | undefined, + )}`; + + for (let attempt = 0; ; attempt++) { + // Per-attempt timeout; the caller's signal spans all attempts. + const signal = composeSignals( + params.signal, + AbortSignal.timeout(timeout), + ); + + let response: Response; + try { + response = await fetch(url, { + method: params.method, + body: params.body ? JSON.stringify(params.body) : undefined, + headers, + signal, + }); + } catch (error) { + // A caller-initiated abort propagates as-is, immediately. + if (params.signal?.aborted) throw params.signal.reason; + // Timeout or network failure: retryable. + if (attempt < retryCount) { + await sleep((1 << attempt) * retryDelay, params.signal); + continue; + } + throw new FetchError( + params.route, + params.method, + error instanceof Error ? error : undefined, + { requestId }, + ); + } + + if (response.ok) { + return response.json(); + } + + const retryAfter = parseRetryAfter(response); + const retryable = response.status === 429 || response.status >= 500; + if (retryable && attempt < retryCount) { + await sleep(retryAfter ?? (1 << attempt) * retryDelay, params.signal); + continue; + } + + const bodyText = await response.text(); throw new ServerError( - await response.text(), + bodyText, response.status, new Error(response.statusText), + { requestId, retryAfter, code: parseErrorCode(bodyText) }, ); } - - return response.json(); }; } diff --git a/packages/common/src/rest/types.ts b/packages/common/src/rest/types.ts index ebd6ca6c8c..5520e0d36a 100644 --- a/packages/common/src/rest/types.ts +++ b/packages/common/src/rest/types.ts @@ -1,12 +1,39 @@ import type { Prettify } from "viem"; +/** Values the query serializer accepts (null/undefined entries are skipped). */ +export type QueryValue = string | number | boolean | null | undefined; + +/** A query-params object; array values serialize as repeated keys. */ +export type QueryParams = Record; + export type RestRequestSchema = readonly { Route: string; Method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS" | "HEAD"; Body?: unknown | undefined; + Query?: unknown | undefined; Response: unknown; }[]; +/** Per-request runtime options; values override the client-level defaults. */ +export type RestRequestOptions = { + /** Abort the request (and any pending retries). */ + signal?: AbortSignal; + /** Max retry attempts after the initial request (client default applies when omitted). */ + retryCount?: number; + /** Base backoff delay in ms (client default applies when omitted). */ + retryDelay?: number; + /** Per-attempt timeout in ms (client default applies when omitted). */ + timeout?: number; +}; + +/** + * Reads a schema entry's Query member without indexed access, so legacy + * entries that never declared Query resolve to undefined instead of erroring. + */ +type EntryQuery = "Query" extends keyof Entry + ? Entry["Query"] + : undefined; + export type RestRequestParams< Schema extends RestRequestSchema | undefined = undefined, > = Schema extends RestRequestSchema @@ -21,14 +48,23 @@ export type RestRequestParams< ? Schema[K]["Body"] extends undefined ? { body?: undefined } : { body: Schema[K]["Body"] } - : never) + : never) & + (Schema[K] extends Schema[number] + ? EntryQuery extends undefined + ? { query?: undefined } + : undefined extends EntryQuery + ? { query?: EntryQuery } + : { query: EntryQuery } + : never) & + RestRequestOptions >; }[number] : { route: string; method: RestRequestSchema[number]["Method"]; body?: unknown | undefined; - }; + query?: QueryParams | undefined; + } & RestRequestOptions; export type RestRequestFn< Schema extends RestRequestSchema | undefined = undefined, diff --git a/packages/common/src/utils/signals.ts b/packages/common/src/utils/signals.ts new file mode 100644 index 0000000000..e6bd7cfbdb --- /dev/null +++ b/packages/common/src/utils/signals.ts @@ -0,0 +1,55 @@ +/** + * Combines multiple abort signals into one that aborts when any input aborts. + * Uses AbortSignal.any where available, with an addEventListener fallback. + * + * @param {...(AbortSignal | undefined)} signals Signals to combine (undefined entries are skipped) + * @returns {AbortSignal | undefined} The combined signal, or undefined if none were provided + */ +export function composeSignals( + ...signals: Array +): AbortSignal | undefined { + const present = signals.filter((s): s is AbortSignal => s != null); + if (present.length === 0) return undefined; + if (present.length === 1) return present[0]; + if (typeof AbortSignal.any === "function") { + return AbortSignal.any(present); + } + const controller = new AbortController(); + for (const signal of present) { + if (signal.aborted) { + controller.abort(signal.reason); + break; + } + signal.addEventListener("abort", () => controller.abort(signal.reason), { + once: true, + signal: controller.signal, + }); + } + return controller.signal; +} + +/** + * Waits for a duration, rejecting immediately with the signal's reason if the + * signal aborts first. + * + * @param {number} ms Milliseconds to wait + * @param {AbortSignal} [signal] Optional abort signal + * @returns {Promise} Resolves after the delay + */ +export function sleep(ms: number, signal?: AbortSignal): Promise { + return new Promise((resolve, reject) => { + if (signal?.aborted) { + reject(signal.reason); + return; + } + const timer = setTimeout(() => { + signal?.removeEventListener("abort", onAbort); + resolve(); + }, ms); + const onAbort = () => { + clearTimeout(timer); + reject(signal?.reason); + }; + signal?.addEventListener("abort", onAbort, { once: true }); + }); +} diff --git a/packages/common/tests/rest/restClient.test.ts b/packages/common/tests/rest/restClient.test.ts new file mode 100644 index 0000000000..ec8d503882 --- /dev/null +++ b/packages/common/tests/rest/restClient.test.ts @@ -0,0 +1,191 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { AlchemyRestClient } from "../../src/rest/restClient.js"; +import { AlchemyApiError } from "../../src/errors/AlchemyApiError.js"; +import { ServerError } from "../../src/errors/ServerError.js"; +import { FetchError } from "../../src/errors/FetchError.js"; + +type TestSchema = readonly [ + { + Route: "things"; + Method: "GET"; + Body?: undefined; + Query?: { owner?: string; "tags[]"?: string[]; limit?: number } | undefined; + Response: { ok: boolean }; + }, + { + Route: "things/create"; + Method: "POST"; + Body: { name: string }; + Response: { id: string }; + }, +]; + +const fetchMock = vi.fn(); + +const jsonResponse = (body: unknown, init?: ResponseInit) => + new Response(JSON.stringify(body), { + status: 200, + headers: { "Content-Type": "application/json" }, + ...init, + }); + +const makeClient = ( + overrides?: ConstructorParameters[0], +) => + new AlchemyRestClient({ + apiKey: "test-key", + url: "https://example.test/api", + retryDelay: 1, // keep retry tests fast + ...overrides, + }); + +beforeEach(() => { + vi.stubGlobal("fetch", fetchMock); + fetchMock.mockReset(); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe("AlchemyRestClient query serialization", () => { + it("serializes scalars, repeats array keys, and skips nullish values", async () => { + fetchMock.mockImplementation(async () => jsonResponse({ ok: true })); + const client = makeClient(); + await client.request({ + route: "things", + method: "GET", + query: { owner: "0xabc", "tags[]": ["a", "b"], limit: undefined }, + }); + const url = String(fetchMock.mock.calls[0]![0]); + expect(url).toBe( + "https://example.test/api/things?owner=0xabc&tags%5B%5D=a&tags%5B%5D=b", + ); + }); + + it("sends no query string when the query object is empty", async () => { + fetchMock.mockImplementation(async () => jsonResponse({ ok: true })); + await makeClient().request({ route: "things", method: "GET", query: {} }); + expect(String(fetchMock.mock.calls[0]![0])).toBe( + "https://example.test/api/things", + ); + }); +}); + +describe("AlchemyRestClient request id", () => { + it("sends X-Alchemy-Client-Request-Id and keeps it stable across retries", async () => { + fetchMock + .mockImplementationOnce(async () => jsonResponse({}, { status: 500 })) + .mockImplementationOnce(async () => jsonResponse({ ok: true })); + await makeClient().request({ route: "things", method: "GET" }); + + const ids = fetchMock.mock.calls.map((call) => + (call[1].headers as Headers).get("X-Alchemy-Client-Request-Id"), + ); + expect(ids[0]).toMatch(/^[0-9a-f-]{36}$/); + expect(ids[1]).toBe(ids[0]); + }); + + it("surfaces the request id on thrown ServerError", async () => { + fetchMock.mockImplementation(async () => jsonResponse({}, { status: 400 })); + const error = await makeClient() + .request({ route: "things", method: "GET" }) + .catch((e) => e); + expect(error).toBeInstanceOf(ServerError); + expect(error).toBeInstanceOf(AlchemyApiError); + expect(error.requestId).toMatch(/^[0-9a-f-]{36}$/); + expect(error.status).toBe(400); + }); +}); + +describe("AlchemyRestClient retries", () => { + it("retries 429 and 5xx up to retryCount, then succeeds", async () => { + fetchMock + .mockImplementationOnce(async () => jsonResponse({}, { status: 429 })) + .mockImplementationOnce(async () => jsonResponse({}, { status: 503 })) + .mockImplementationOnce(async () => jsonResponse({ ok: true })); + const result = await makeClient().request({ + route: "things", + method: "GET", + }); + expect(result).toEqual({ ok: true }); + expect(fetchMock).toHaveBeenCalledTimes(3); + }); + + it("does not retry non-429 4xx", async () => { + fetchMock.mockImplementation(async () => + jsonResponse({ error: { code: "bad-owner" } }, { status: 400 }), + ); + const error = await makeClient() + .request({ route: "things", method: "GET" }) + .catch((e) => e); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(error.code).toBe("bad-owner"); + }); + + it("honors Retry-After seconds and exposes retryAfter on the final error", async () => { + fetchMock.mockImplementation( + async () => + new Response("{}", { + status: 429, + headers: { "Retry-After": "0" }, + }), + ); + const error = await makeClient({ retryCount: 1, retryDelay: 1 }) + .request({ route: "things", method: "GET" }) + .catch((e) => e); + expect(fetchMock).toHaveBeenCalledTimes(2); + expect(error.status).toBe(429); + expect(error.retryAfter).toBe(0); + }); + + it("retries network errors and throws FetchError with requestId when exhausted", async () => { + fetchMock.mockImplementation(async () => { + throw new TypeError("fetch failed"); + }); + const error = await makeClient({ retryCount: 2 }) + .request({ route: "things", method: "GET" }) + .catch((e) => e); + expect(fetchMock).toHaveBeenCalledTimes(3); + expect(error).toBeInstanceOf(FetchError); + expect(error.requestId).toMatch(/^[0-9a-f-]{36}$/); + }); + + it("respects per-request retryCount override of 0", async () => { + fetchMock.mockImplementation(async () => jsonResponse({}, { status: 500 })); + await makeClient() + .request({ route: "things", method: "GET", retryCount: 0 }) + .catch(() => {}); + expect(fetchMock).toHaveBeenCalledTimes(1); + }); +}); + +describe("AlchemyRestClient abort", () => { + it("propagates a caller abort immediately without retrying", async () => { + const controller = new AbortController(); + fetchMock.mockImplementation(async (_url, init: RequestInit) => { + controller.abort(new Error("user-cancelled")); + throw (init.signal as AbortSignal).reason; + }); + const error = await makeClient() + .request({ route: "things", method: "GET", signal: controller.signal }) + .catch((e) => e); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(error.message).toBe("user-cancelled"); + }); +}); + +describe("AlchemyRestClient bodies", () => { + it("posts JSON bodies as before", async () => { + fetchMock.mockImplementation(async () => jsonResponse({ id: "1" })); + const result = await makeClient().request({ + route: "things/create", + method: "POST", + body: { name: "x" }, + }); + expect(result).toEqual({ id: "1" }); + expect(fetchMock.mock.calls[0]![1].body).toBe( + JSON.stringify({ name: "x" }), + ); + }); +}); From 82622c41b297951bcc4e6834269c5ec910f5fea4 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Wed, 10 Jun 2026 10:36:33 -0400 Subject: [PATCH 08/20] feat(api-codegen): emit Query channel, validate pagination, guard deprecated ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - REST tuples now carry the Query channel (required when any query param is required); standalone Query alias emitted whenever query params exist. - Manifest gains optional pagination metadata per operation/method (pageParam / responseCursorField / itemsField, dotted paths for nested cursors); validated against snapshots at generate time — drift in cursor fields fails generation. Consumed by hand-written *Pages actions; emits no runtime code. - Referencing a spec-deprecated operation hard-fails generation. - NFT action drops the URLSearchParams route cast: typed query channel + abort signal via the hardened AlchemyRestClient. Actions accept an optional RequestOptions third arg. - Snapshot lockfile labels detached-HEAD checkouts '(detached)'. Co-Authored-By: Claude Fable 5 --- packages/api-codegen/src/manifest.ts | 20 +++- .../api-codegen/src/rest/schemaEmitter.ts | 93 ++++++++++++++++--- packages/api-codegen/src/rpc/rpcEmitter.ts | 44 ++++++++- packages/api-codegen/src/schemaWalk.ts | 55 +++++++++++ packages/api-codegen/src/snapshot.ts | 3 +- packages/api-codegen/tests/rpcEmitter.test.ts | 33 +++++++ .../api-codegen/tests/schemaEmitter.test.ts | 82 ++++++++++++++++ packages/data-apis/codegen.manifest.ts | 11 ++- .../src/actions/nft/getNftsForOwner.ts | 19 ++-- .../src/generated/rest/nft.schema.ts | 7 +- .../src/generated/rest/portfolio.schema.ts | 1 + .../src/generated/rest/prices.schema.ts | 9 +- .../data-apis/src/internal/clientHelpers.ts | 6 ++ 13 files changed, 344 insertions(+), 39 deletions(-) create mode 100644 packages/api-codegen/src/schemaWalk.ts diff --git a/packages/api-codegen/src/manifest.ts b/packages/api-codegen/src/manifest.ts index f4b1f09d09..9b718bae7a 100644 --- a/packages/api-codegen/src/manifest.ts +++ b/packages/api-codegen/src/manifest.ts @@ -9,14 +9,30 @@ export type PathRules = { stripPrefix?: string; }; +/** + * Cursor-pagination metadata for a method. Validated against the spec + * snapshot at generate time; consumed by hand-written `*Pages` companion + * actions (no runtime code is generated from it). + */ +export type PaginationConfig = { + /** Request param carrying the cursor; dotted path for nested params (e.g. "options.pageKey"). */ + pageParam: string; + /** Success-response field carrying the next cursor (e.g. "pageKey"). */ + responseCursorField: string; + /** Success-response field holding the page's items (e.g. "ownedNfts"). */ + itemsField: string; +}; + /** A single OpenAPI operation the target consumes. */ export type RestOperationConfig = { /** operationId as it appears in the spec (e.g. "getNFTsForOwner-v3"). */ operationId: string; /** Base for emitted type names (e.g. "GetNftsForOwner" → GetNftsForOwnerResponse). */ exportBaseName: string; - /** Also emit a named query-params type (GET endpoints; RestRequestSchema has no query channel). */ + /** Also emit a named query-params type (redundant since the Query channel; kept for compatibility). */ emitQueryType?: boolean; + /** Cursor pagination metadata, when the operation paginates. */ + pagination?: PaginationConfig; }; /** One REST spec consumed by the target. */ @@ -35,6 +51,8 @@ export type RpcMethodConfig = { method: string; /** Base for emitted type names (e.g. "AlchemyGetAssetTransfers"). */ exportBaseName: string; + /** Cursor pagination metadata, when the method paginates. */ + pagination?: PaginationConfig; }; /** One OpenRPC spec consumed by the target. */ diff --git a/packages/api-codegen/src/rest/schemaEmitter.ts b/packages/api-codegen/src/rest/schemaEmitter.ts index ffd16f2ecc..d31df2d799 100644 --- a/packages/api-codegen/src/rest/schemaEmitter.ts +++ b/packages/api-codegen/src/rest/schemaEmitter.ts @@ -1,6 +1,7 @@ import { CodegenError } from "../errors.js"; import { normalizePath } from "./normalizePath.js"; -import type { RestSpecConfig } from "../manifest.js"; +import { schemaHasPath } from "../schemaWalk.js"; +import type { PaginationConfig, RestSpecConfig } from "../manifest.js"; const REST_METHODS = [ "get", @@ -14,9 +15,13 @@ const REST_METHODS = [ type SpecOperation = { operationId?: string; - requestBody?: { content?: Record }; - responses?: Record }>; - parameters?: Array<{ in?: string }>; + deprecated?: boolean; + requestBody?: { content?: Record }; + responses?: Record< + string, + { content?: Record } + >; + parameters?: Array<{ in?: string; name?: string }>; }; type LocatedOperation = { @@ -88,6 +93,51 @@ function pickSuccessResponse( return { status, contentType }; } +/** + * Validates a REST operation's declared pagination metadata against the spec: + * the cursor param must exist among query params or body properties, and the + * cursor/items fields must exist in the success response schema. + * + * @param {string} operationId For error messages + * @param {SpecOperation} operation The located spec operation + * @param {PaginationConfig} pagination The manifest's pagination declaration + * @param {object} success The success response key to inspect + * @param {string} success.status The 2xx status code key + * @param {string} success.contentType The response content type key + */ +function validateRestPagination( + operationId: string, + operation: SpecOperation, + pagination: PaginationConfig, + success: { status: string; contentType: string }, +): void { + const bodySchema = + operation.requestBody?.content?.["application/json"]?.schema; + const inQuery = (operation.parameters ?? []).some( + (param) => param.in === "query" && param.name === pagination.pageParam, + ); + const inBody = bodySchema + ? schemaHasPath(bodySchema, pagination.pageParam) + : false; + if (!inQuery && !inBody) { + throw new CodegenError( + `Operation "${operationId}": pagination pageParam "${pagination.pageParam}" not found among query params or body properties.`, + ); + } + + const responseSchema = + operation.responses?.[success.status]?.content?.[success.contentType] + ?.schema; + for (const field of [pagination.responseCursorField, pagination.itemsField]) { + // Dotted paths supported: portfolio responses nest cursors under "data". + if (!schemaHasPath(responseSchema, field)) { + throw new CodegenError( + `Operation "${operationId}": pagination field "${field}" not found in the ${success.status} response schema.`, + ); + } + } +} + /** * Emits the .schema.ts source for a REST spec: named Body/Response * (and optional Query) aliases indexed into the openapi-typescript output, @@ -105,13 +155,33 @@ export function emitRestSchema( const tupleEntries: string[] = []; for (const opConfig of config.operations) { - const { operationId, exportBaseName, emitQueryType } = opConfig; + const { operationId, exportBaseName, pagination } = opConfig; const { path, method, operation } = locateOperation(spec, operationId); + if (operation.deprecated) { + throw new CodegenError( + `Operation "${operationId}" is marked deprecated in the spec. ` + + `Drop it from the manifest, or map the replacement operation instead.`, + ); + } const route = normalizePath(path, config.pathRules); const hasBody = operation.requestBody != null; + const queryParams = (operation.parameters ?? []).filter( + (param) => param.in === "query", + ); + const hasQuery = queryParams.length > 0; + const hasRequiredQuery = queryParams.some( + (param) => (param as { required?: boolean }).required === true, + ); const { status, contentType } = pickSuccessResponse(operation, operationId); const opIndex = JSON.stringify(operationId); + if (pagination) { + validateRestPagination(operationId, operation, pagination, { + status, + contentType, + }); + } + if (hasBody) { const bodyContentType = Object.keys( operation.requestBody?.content ?? {}, @@ -132,13 +202,9 @@ export function emitRestSchema( ` operations[${opIndex}]["responses"][${JSON.stringify(status)}]["content"][${JSON.stringify(contentType)}];`, ); - if (emitQueryType) { + if (hasQuery) { aliasBlocks.push( - `/**\n` + - ` * Query params for ${operationId}. Sent via the URL at runtime;\n` + - ` * RestRequestSchema has no query channel, so this is exposed as a\n` + - ` * standalone type for the SDK's params layer.\n` + - ` */\n` + + `/** Query params for ${operationId}. */\n` + `export type ${exportBaseName}Query = NonNullable<\n` + ` operations[${opIndex}]["parameters"]["query"]\n` + `>;`, @@ -153,6 +219,11 @@ export function emitRestSchema( (hasBody ? ` Body: ${exportBaseName}Body;\n` : ` Body?: undefined;\n`) + + (hasQuery + ? hasRequiredQuery + ? ` Query: ${exportBaseName}Query;\n` + : ` Query?: ${exportBaseName}Query | undefined;\n` + : ` Query?: undefined;\n`) + ` Response: ${exportBaseName}Response;\n` + ` },`, ); diff --git a/packages/api-codegen/src/rpc/rpcEmitter.ts b/packages/api-codegen/src/rpc/rpcEmitter.ts index 6ce21a2540..111065c685 100644 --- a/packages/api-codegen/src/rpc/rpcEmitter.ts +++ b/packages/api-codegen/src/rpc/rpcEmitter.ts @@ -1,6 +1,8 @@ import { compile } from "json-schema-to-typescript"; -import { extractMethod } from "./openrpcWalker.js"; -import type { RpcSpecConfig } from "../manifest.js"; +import { CodegenError } from "../errors.js"; +import { schemaHasPath } from "../schemaWalk.js"; +import { extractMethod, type ExtractedMethod } from "./openrpcWalker.js"; +import type { PaginationConfig, RpcSpecConfig } from "../manifest.js"; /** * Converts a param name to PascalCase for type naming. @@ -12,6 +14,39 @@ function pascal(name: string): string { return name.charAt(0).toUpperCase() + name.slice(1); } +/** + * Validates an RPC method's declared pagination metadata against the spec: + * the cursor param path must resolve within one of the method's params + * (dotted paths traverse nested object params, e.g. "options.pageKey" on + * alchemy_getTokenBalances), and the cursor/items fields must exist in the + * result schema (combinator branches included). + * + * @param {ExtractedMethod} extracted The extracted method schemas + * @param {PaginationConfig} pagination The manifest's pagination declaration + */ +function validateRpcPagination( + extracted: ExtractedMethod, + pagination: PaginationConfig, +): void { + const [head, ...rest] = pagination.pageParam.split("."); + const param = extracted.params.find((p) => p.name === head); + const paramOk = + param != null && + (rest.length === 0 || schemaHasPath(param.schema, rest.join("."))); + if (!paramOk) { + throw new CodegenError( + `Method "${extracted.name}": pagination pageParam "${pagination.pageParam}" not found among the method's params.`, + ); + } + for (const field of [pagination.responseCursorField, pagination.itemsField]) { + if (!schemaHasPath(extracted.result, field)) { + throw new CodegenError( + `Method "${extracted.name}": pagination field "${field}" not found in the result schema.`, + ); + } + } +} + /** * Emits the .ts source for an OpenRPC spec: per-method param/result * types compiled from their JSON Schemas (json-schema-to-typescript), plus a @@ -29,8 +64,11 @@ export async function emitRpcSchema( const tupleEntries: string[] = []; for (const methodConfig of config.methods) { - const { method, exportBaseName } = methodConfig; + const { method, exportBaseName, pagination } = methodConfig; const extracted = extractMethod(spec, method); + if (pagination) { + validateRpcPagination(extracted, pagination); + } // Single-param methods get the plain "Params" name; multi-param // methods are disambiguated by param name. diff --git a/packages/api-codegen/src/schemaWalk.ts b/packages/api-codegen/src/schemaWalk.ts new file mode 100644 index 0000000000..340b7eebaf --- /dev/null +++ b/packages/api-codegen/src/schemaWalk.ts @@ -0,0 +1,55 @@ +/** + * Checks whether a JSON Schema declares a property, looking through + * oneOf/anyOf/allOf combinators (the transfers result, for example, is a + * oneOf whose object branch carries pageKey/transfers). + * + * @param {unknown} schema A JSON Schema node + * @param {string} property The property name to find + * @returns {boolean} true if any (combinator) branch declares the property + */ +export function schemaHasProperty(schema: unknown, property: string): boolean { + if (schema === null || typeof schema !== "object") return false; + const node = schema as Record; + const properties = node.properties as Record | undefined; + if (properties && property in properties) return true; + for (const combinator of ["oneOf", "anyOf", "allOf"] as const) { + const branches = node[combinator]; + if (Array.isArray(branches)) { + if (branches.some((branch) => schemaHasProperty(branch, property))) { + return true; + } + } + } + return false; +} + +/** + * Resolves a dotted property path within a JSON Schema's properties + * (e.g. "options.pageKey" → properties.options.properties.pageKey). + * + * @param {unknown} schema A JSON Schema node + * @param {string} path Dotted property path + * @returns {boolean} true if the full path resolves + */ +export function schemaHasPath(schema: unknown, path: string): boolean { + const [head, ...rest] = path.split("."); + if (!head) return false; + if (schema === null || typeof schema !== "object") return false; + const properties = (schema as Record).properties as + | Record + | undefined; + if (!properties || !(head in properties)) { + // Look through combinators at this level too. + for (const combinator of ["oneOf", "anyOf", "allOf"] as const) { + const branches = (schema as Record)[combinator]; + if (Array.isArray(branches)) { + if (branches.some((branch) => schemaHasPath(branch, path))) { + return true; + } + } + } + return false; + } + if (rest.length === 0) return true; + return schemaHasPath(properties[head], rest.join(".")); +} diff --git a/packages/api-codegen/src/snapshot.ts b/packages/api-codegen/src/snapshot.ts index 01a25842f6..1e34e167a1 100644 --- a/packages/api-codegen/src/snapshot.ts +++ b/packages/api-codegen/src/snapshot.ts @@ -80,7 +80,8 @@ export async function snapshot(options: SnapshotOptions = {}): Promise { ); } const sha = git(docsDir, ["rev-parse", "HEAD"]); - const branch = git(docsDir, ["rev-parse", "--abbrev-ref", "HEAD"]); + const branchName = git(docsDir, ["rev-parse", "--abbrev-ref", "HEAD"]); + const branch = branchName === "HEAD" ? "(detached)" : branchName; const { rest, rpc } = await collectSpecNames(); console.log( diff --git a/packages/api-codegen/tests/rpcEmitter.test.ts b/packages/api-codegen/tests/rpcEmitter.test.ts index 1459b723e0..0c42ef8862 100644 --- a/packages/api-codegen/tests/rpcEmitter.test.ts +++ b/packages/api-codegen/tests/rpcEmitter.test.ts @@ -53,4 +53,37 @@ describe("emitRpcSchema", () => { ); expect(source).toContain("ReturnType: FixtureGetThingsResult;"); }); + + it("validates dotted pagination paths against params and oneOf results", async () => { + const withPagination = (pageParam: string, itemsField: string) => + emitRpcSchema( + { + spec: "rpc.fixture", + schemaTypeName: "FixtureRpcSchema", + methods: [ + { + method: "fixture_getThings", + exportBaseName: "FixtureGetThings", + pagination: { + pageParam, + responseCursorField: "pageKey", + itemsField, + }, + }, + ], + }, + spec, + ); + // pageKey/things live in the object branch of the oneOf result; the + // cursor param is nested under the single object param. + await expect( + withPagination("thingParams.limit", "things"), + ).resolves.toContain("FixtureRpcSchema"); + await expect(withPagination("thingParams.bogus", "things")).rejects.toThrow( + /thingParams.bogus/, + ); + await expect( + withPagination("thingParams.limit", "bogusItems"), + ).rejects.toThrow(/bogusItems/); + }); }); diff --git a/packages/api-codegen/tests/schemaEmitter.test.ts b/packages/api-codegen/tests/schemaEmitter.test.ts index 45d15daa93..411b7a2018 100644 --- a/packages/api-codegen/tests/schemaEmitter.test.ts +++ b/packages/api-codegen/tests/schemaEmitter.test.ts @@ -55,5 +55,87 @@ describe("emitRestSchema", () => { ); expect(source).toContain(`Route: "getBar";`); expect(source).toContain(`Body?: undefined;`); + // owner is a required query param → Query is required in the tuple + expect(source).toContain(`Query: GetBarQuery;`); + }); + + it("emits Query?: undefined for operations without query params", () => { + const source = emitRestSchema( + { + spec: "rest.fixture", + schemaTypeName: "FooRestSchema", + pathRules: { stripApiKeySegment: true }, + operations: [ + { operationId: "create-foo", exportBaseName: "CreateFoo" }, + ], + }, + spec, + ); + expect(source).toContain(`Query?: undefined;`); + expect(source).not.toContain(`CreateFooQuery`); + }); + + it("validates pagination metadata and hard-errors on unknown fields", () => { + const config = (pagination: { + pageParam: string; + responseCursorField: string; + itemsField: string; + }) => ({ + spec: "rest.fixture", + schemaTypeName: "BarRestSchema", + pathRules: { stripApiKeySegment: true, stripPrefix: "/v3" }, + operations: [ + { operationId: "getBar-v3", exportBaseName: "GetBar", pagination }, + ], + }); + // valid: owner is a query param; items is a response property + expect(() => + emitRestSchema( + config({ + pageParam: "owner", + responseCursorField: "items", + itemsField: "items", + }), + spec, + ), + ).not.toThrow(); + // invalid pageParam + expect(() => + emitRestSchema( + config({ + pageParam: "bogusCursor", + responseCursorField: "items", + itemsField: "items", + }), + spec, + ), + ).toThrow(/bogusCursor/); + // invalid response field + expect(() => + emitRestSchema( + config({ + pageParam: "owner", + responseCursorField: "bogusField", + itemsField: "items", + }), + spec, + ), + ).toThrow(/bogusField/); + }); + + it("hard-errors when the manifest references a deprecated operation", () => { + const deprecatedSpec = JSON.parse(JSON.stringify(spec)); + deprecatedSpec.paths["/v3/{apiKey}/getBar"].get.deprecated = true; + expect(() => + emitRestSchema( + { + spec: "rest.fixture", + schemaTypeName: "BarRestSchema", + pathRules: { stripApiKeySegment: true, stripPrefix: "/v3" }, + operations: [{ operationId: "getBar-v3", exportBaseName: "GetBar" }], + }, + deprecatedSpec, + ), + ).toThrow(/deprecated/); }); }); diff --git a/packages/data-apis/codegen.manifest.ts b/packages/data-apis/codegen.manifest.ts index c35720042b..a2b33f5d12 100644 --- a/packages/data-apis/codegen.manifest.ts +++ b/packages/data-apis/codegen.manifest.ts @@ -52,7 +52,11 @@ export default { { operationId: "getNFTsForOwner-v3", exportBaseName: "GetNftsForOwner", - emitQueryType: true, + pagination: { + pageParam: "pageKey", + responseCursorField: "pageKey", + itemsField: "ownedNfts", + }, }, ], }, @@ -65,6 +69,11 @@ export default { { method: "alchemy_getAssetTransfers", exportBaseName: "AlchemyGetAssetTransfers", + pagination: { + pageParam: "assetTransferParams.pageKey", + responseCursorField: "pageKey", + itemsField: "transfers", + }, }, ], }, diff --git a/packages/data-apis/src/actions/nft/getNftsForOwner.ts b/packages/data-apis/src/actions/nft/getNftsForOwner.ts index ad038ba464..b8a9736f92 100644 --- a/packages/data-apis/src/actions/nft/getNftsForOwner.ts +++ b/packages/data-apis/src/actions/nft/getNftsForOwner.ts @@ -3,6 +3,7 @@ import { getRestClient, resolveRequestNetwork, type DataClient, + type RequestOptions, } from "../../internal/clientHelpers.js"; import type { NftRestSchema } from "../../schema/rest.js"; import type { @@ -17,28 +18,22 @@ import type { * * @param {DataClient} client A client configured with an Alchemy transport * @param {GetNftsForOwnerParams} params Owner address, optional network override, and filters + * @param {RequestOptions} [options] Per-request options (abort signal) * @returns {Promise} The owned NFTs and pagination cursor */ export async function getNftsForOwner( client: DataClient, params: GetNftsForOwnerParams, + options?: RequestOptions, ): Promise { - const { network, owner, contractAddresses, ...rest } = params; + const { network, contractAddresses, ...query } = params; const { slug } = resolveRequestNetwork(client, network); - const query = new URLSearchParams({ owner }); - for (const [key, value] of Object.entries(rest)) { - if (value != null) query.set(key, String(value)); - } - for (const address of contractAddresses ?? []) { - query.append("contractAddresses[]", address); - } - const restClient = getRestClient(client, getNftApiUrl(slug)); - // TODO(common-hardening): AlchemyRestClient should take query params - // first-class instead of this cast; tracked in the data SDK plan. return restClient.request({ - route: `getNFTsForOwner?${query.toString()}` as "getNFTsForOwner", + route: "getNFTsForOwner", method: "GET", + query: { ...query, "contractAddresses[]": contractAddresses }, + signal: options?.signal, }); } diff --git a/packages/data-apis/src/generated/rest/nft.schema.ts b/packages/data-apis/src/generated/rest/nft.schema.ts index c543c83c66..c52bf3c614 100644 --- a/packages/data-apis/src/generated/rest/nft.schema.ts +++ b/packages/data-apis/src/generated/rest/nft.schema.ts @@ -10,11 +10,7 @@ import type { operations } from "./nft.types.js"; export type GetNftsForOwnerResponse = operations["getNFTsForOwner-v3"]["responses"]["200"]["content"]["application/json"]; -/** - * Query params for getNFTsForOwner-v3. Sent via the URL at runtime; - * RestRequestSchema has no query channel, so this is exposed as a - * standalone type for the SDK's params layer. - */ +/** Query params for getNFTsForOwner-v3. */ export type GetNftsForOwnerQuery = NonNullable< operations["getNFTsForOwner-v3"]["parameters"]["query"] >; @@ -26,6 +22,7 @@ export type NftRestSchema = readonly [ Route: "getNFTsForOwner"; Method: "GET"; Body?: undefined; + Query: GetNftsForOwnerQuery; Response: GetNftsForOwnerResponse; }, ]; diff --git a/packages/data-apis/src/generated/rest/portfolio.schema.ts b/packages/data-apis/src/generated/rest/portfolio.schema.ts index 9a41ff79c4..a2860547d4 100644 --- a/packages/data-apis/src/generated/rest/portfolio.schema.ts +++ b/packages/data-apis/src/generated/rest/portfolio.schema.ts @@ -22,6 +22,7 @@ export type PortfolioRestSchema = readonly [ Route: "assets/tokens/by-address"; Method: "POST"; Body: GetTokensByAddressBody; + Query?: undefined; Response: GetTokensByAddressResponse; }, ]; diff --git a/packages/data-apis/src/generated/rest/prices.schema.ts b/packages/data-apis/src/generated/rest/prices.schema.ts index 556f6cca20..4b5b0df657 100644 --- a/packages/data-apis/src/generated/rest/prices.schema.ts +++ b/packages/data-apis/src/generated/rest/prices.schema.ts @@ -10,11 +10,7 @@ import type { operations } from "./prices.types.js"; export type GetTokenPricesBySymbolResponse = operations["get-token-prices-by-symbol"]["responses"]["200"]["content"]["application/json"]; -/** - * Query params for get-token-prices-by-symbol. Sent via the URL at runtime; - * RestRequestSchema has no query channel, so this is exposed as a - * standalone type for the SDK's params layer. - */ +/** Query params for get-token-prices-by-symbol. */ export type GetTokenPricesBySymbolQuery = NonNullable< operations["get-token-prices-by-symbol"]["parameters"]["query"] >; @@ -44,6 +40,7 @@ export type PricesRestSchema = readonly [ Route: "tokens/by-symbol"; Method: "GET"; Body?: undefined; + Query: GetTokenPricesBySymbolQuery; Response: GetTokenPricesBySymbolResponse; }, { @@ -51,6 +48,7 @@ export type PricesRestSchema = readonly [ Route: "tokens/by-address"; Method: "POST"; Body: GetTokenPricesByAddressBody; + Query?: undefined; Response: GetTokenPricesByAddressResponse; }, { @@ -58,6 +56,7 @@ export type PricesRestSchema = readonly [ Route: "tokens/historical"; Method: "POST"; Body: GetHistoricalTokenPricesBody; + Query?: undefined; Response: GetHistoricalTokenPricesResponse; }, ]; diff --git a/packages/data-apis/src/internal/clientHelpers.ts b/packages/data-apis/src/internal/clientHelpers.ts index 8f5fa05ec2..7702afbc55 100644 --- a/packages/data-apis/src/internal/clientHelpers.ts +++ b/packages/data-apis/src/internal/clientHelpers.ts @@ -13,6 +13,12 @@ import type { Chain, Client } from "viem"; /** The minimal client shape data actions operate on. */ export type DataClient = Client; +/** Per-request options accepted by data actions. */ +export type RequestOptions = { + /** Aborts the request (and any pending retries). */ + signal?: AbortSignal; +}; + /** * Reads the AlchemyTransportConfig back off an instantiated client transport. * The transport attaches its creation config to the transport value precisely From bdc4064d9c7bd958b81bd2d6bb3779cea8cf6e70 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Wed, 10 Jun 2026 10:58:56 -0400 Subject: [PATCH 09/20] feat(data-apis): full v1 method surface (31 methods across 5 namespaces) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - portfolio: + getTokenBalancesByAddress, getNftsByAddress, getNftContractsByAddress (global Data API, multi-network bodies; per-entry fields like address-level filters preserved, network-slug enum deliberately widened for the escape hatch) - prices: getTokenPricesBySymbol (chain-agnostic GET), getTokenPricesByAddress (per-entry network resolution), getHistoricalTokenPrices (symbol or network+address forms) - nft: full v3 read surface — 21 actions (ownership/contract/collection listings, metadata + batch, owners, sales, floor price, search, spam/ airdrop/rarity checks). Mutations and deprecated/v2 ops excluded per scope plan. Bracketed wire keys (contractAddresses[], *Filters[]) exposed unbracketed in params and restored by the actions. - token: getTokenBalances (positional optionals trimmed), getTokenMetadata, getTokenAllowance over a shared getRpcRequest helper (also adopted by transfers.getAssetTransfers) - DataRpcSchema now aggregates transfers + token entries; REST actions all accept an options arg with an abort signal - 29 new wire-level tests (URL/method/body/query assertions per namespace) Co-Authored-By: Claude Fable 5 --- packages/data-apis/codegen.manifest.ts | 155 +++++++- .../src/actions/nft/computeRarity.ts | 35 ++ .../src/actions/nft/getCollectionMetadata.ts | 38 ++ .../src/actions/nft/getCollectionsForOwner.ts | 44 +++ .../src/actions/nft/getContractMetadata.ts | 38 ++ .../actions/nft/getContractMetadataBatch.ts | 39 ++ .../src/actions/nft/getContractsForOwner.ts | 44 +++ .../src/actions/nft/getFloorPrice.ts | 35 ++ .../src/actions/nft/getNftMetadata.ts | 38 ++ .../src/actions/nft/getNftMetadataBatch.ts | 39 ++ .../data-apis/src/actions/nft/getNftSales.ts | 35 ++ .../src/actions/nft/getNftsForCollection.ts | 38 ++ .../src/actions/nft/getNftsForContract.ts | 38 ++ .../src/actions/nft/getNftsForOwner.ts | 10 +- .../src/actions/nft/getOwnersForContract.ts | 38 ++ .../src/actions/nft/getOwnersForNft.ts | 38 ++ .../src/actions/nft/getSpamContracts.ts | 37 ++ .../data-apis/src/actions/nft/isAirdropNft.ts | 35 ++ .../src/actions/nft/isHolderOfContract.ts | 38 ++ .../src/actions/nft/isSpamContract.ts | 38 ++ .../data-apis/src/actions/nft/nft.test.ts | 175 +++++++++ .../src/actions/nft/searchContractMetadata.ts | 38 ++ .../src/actions/nft/summarizeNftAttributes.ts | 38 ++ .../portfolio/getNftContractsByAddress.ts | 47 +++ .../src/actions/portfolio/getNftsByAddress.ts | 47 +++ .../portfolio/getTokenBalancesByAddress.ts | 47 +++ .../actions/portfolio/getTokensByAddress.ts | 8 +- .../src/actions/portfolio/portfolio.test.ts | 63 ++++ .../prices/getHistoricalTokenPrices.ts | 44 +++ .../actions/prices/getTokenPricesByAddress.ts | 41 +++ .../actions/prices/getTokenPricesBySymbol.ts | 35 ++ .../src/actions/prices/prices.test.ts | 84 +++++ .../src/actions/token/getTokenAllowance.ts | 30 ++ .../src/actions/token/getTokenBalances.ts | 42 +++ .../src/actions/token/getTokenMetadata.ts | 30 ++ .../data-apis/src/actions/token/token.test.ts | 76 ++++ .../actions/transfers/getAssetTransfers.ts | 21 +- packages/data-apis/src/decorator.ts | 232 ++++++++++-- .../src/generated/rest/nft.schema.ts | 335 ++++++++++++++++++ .../src/generated/rest/portfolio.schema.ts | 51 +++ packages/data-apis/src/index.ts | 38 +- .../data-apis/src/internal/clientHelpers.ts | 30 +- packages/data-apis/src/internal/endpoints.ts | 3 + packages/data-apis/src/internal/query.ts | 19 + packages/data-apis/src/schema/rest.ts | 1 + packages/data-apis/src/schema/rpc.ts | 16 +- packages/data-apis/src/types.ts | 253 ++++++++++++- 47 files changed, 2618 insertions(+), 76 deletions(-) create mode 100644 packages/data-apis/src/actions/nft/computeRarity.ts create mode 100644 packages/data-apis/src/actions/nft/getCollectionMetadata.ts create mode 100644 packages/data-apis/src/actions/nft/getCollectionsForOwner.ts create mode 100644 packages/data-apis/src/actions/nft/getContractMetadata.ts create mode 100644 packages/data-apis/src/actions/nft/getContractMetadataBatch.ts create mode 100644 packages/data-apis/src/actions/nft/getContractsForOwner.ts create mode 100644 packages/data-apis/src/actions/nft/getFloorPrice.ts create mode 100644 packages/data-apis/src/actions/nft/getNftMetadata.ts create mode 100644 packages/data-apis/src/actions/nft/getNftMetadataBatch.ts create mode 100644 packages/data-apis/src/actions/nft/getNftSales.ts create mode 100644 packages/data-apis/src/actions/nft/getNftsForCollection.ts create mode 100644 packages/data-apis/src/actions/nft/getNftsForContract.ts create mode 100644 packages/data-apis/src/actions/nft/getOwnersForContract.ts create mode 100644 packages/data-apis/src/actions/nft/getOwnersForNft.ts create mode 100644 packages/data-apis/src/actions/nft/getSpamContracts.ts create mode 100644 packages/data-apis/src/actions/nft/isAirdropNft.ts create mode 100644 packages/data-apis/src/actions/nft/isHolderOfContract.ts create mode 100644 packages/data-apis/src/actions/nft/isSpamContract.ts create mode 100644 packages/data-apis/src/actions/nft/nft.test.ts create mode 100644 packages/data-apis/src/actions/nft/searchContractMetadata.ts create mode 100644 packages/data-apis/src/actions/nft/summarizeNftAttributes.ts create mode 100644 packages/data-apis/src/actions/portfolio/getNftContractsByAddress.ts create mode 100644 packages/data-apis/src/actions/portfolio/getNftsByAddress.ts create mode 100644 packages/data-apis/src/actions/portfolio/getTokenBalancesByAddress.ts create mode 100644 packages/data-apis/src/actions/portfolio/portfolio.test.ts create mode 100644 packages/data-apis/src/actions/prices/getHistoricalTokenPrices.ts create mode 100644 packages/data-apis/src/actions/prices/getTokenPricesByAddress.ts create mode 100644 packages/data-apis/src/actions/prices/getTokenPricesBySymbol.ts create mode 100644 packages/data-apis/src/actions/prices/prices.test.ts create mode 100644 packages/data-apis/src/actions/token/getTokenAllowance.ts create mode 100644 packages/data-apis/src/actions/token/getTokenBalances.ts create mode 100644 packages/data-apis/src/actions/token/getTokenMetadata.ts create mode 100644 packages/data-apis/src/actions/token/token.test.ts create mode 100644 packages/data-apis/src/internal/query.ts diff --git a/packages/data-apis/codegen.manifest.ts b/packages/data-apis/codegen.manifest.ts index a2b33f5d12..c138187edb 100644 --- a/packages/data-apis/codegen.manifest.ts +++ b/packages/data-apis/codegen.manifest.ts @@ -2,35 +2,61 @@ import type { CodegenManifest } from "@alchemy/api-codegen"; // The hand-maintained overlay mapping spec operations to this package's // generated internals (src/generated/). Validated against the committed spec -// snapshots on every `pnpm generate` — referencing a renamed/removed spec -// operation is a hard error. Public naming and pagination semantics stay -// human decisions; everything type-shaped is generated. +// snapshots on every `pnpm generate` — referencing a renamed/removed/ +// deprecated spec operation is a hard error, and pagination metadata is +// checked against request/response schemas. Public naming and pagination +// semantics stay human decisions; everything type-shaped is generated. +// +// Deliberately excluded: NFT v2 legacy duplicates, and the v3 mutation +// operations (invalidateContract-v3, reportSpam-v3, refreshNftMetadata-v3) +// per the data SDK scope plan. export default { rest: [ { spec: "portfolio", schemaTypeName: "PortfolioRestSchema", - // Spec path: POST /{apiKey}/assets/tokens/by-address. Runtime auth is - // header-based against https://api.g.alchemy.com/data/v1. + // Spec paths: POST /{apiKey}/assets/... Runtime auth is header-based + // against https://api.g.alchemy.com/data/v1. pathRules: { stripApiKeySegment: true }, operations: [ { operationId: "get-tokens-by-address", exportBaseName: "GetTokensByAddress", }, + { + operationId: "get-token-balances-by-address", + exportBaseName: "GetTokenBalancesByAddress", + }, + { + operationId: "get-nfts-by-address", + exportBaseName: "GetNftsByAddress", + pagination: { + pageParam: "pageKey", + responseCursorField: "data.pageKey", + itemsField: "data.ownedNfts", + }, + }, + { + operationId: "get-nft-contracts-by-address", + exportBaseName: "GetNftContractsByAddress", + pagination: { + pageParam: "pageKey", + responseCursorField: "data.pageKey", + itemsField: "data.contracts", + }, + }, ], }, { spec: "prices", schemaTypeName: "PricesRestSchema", - // Spec paths: /{apiKey}/tokens/by-symbol etc. Runtime auth is - // header-based against https://api.g.alchemy.com/prices/v1. + // Spec paths: /{apiKey}/tokens/... Runtime auth is header-based + // against https://api.g.alchemy.com/prices/v1. pathRules: { stripApiKeySegment: true }, operations: [ { operationId: "get-token-prices-by-symbol", exportBaseName: "GetTokenPricesBySymbol", - emitQueryType: true, }, { operationId: "get-token-prices-by-address", @@ -45,7 +71,7 @@ export default { { spec: "nft", schemaTypeName: "NftRestSchema", - // Spec path: GET /v3/{apiKey}/getNFTsForOwner. Runtime base URL is + // Spec paths: /v3/{apiKey}/. Runtime base URL is // https://{network}.g.alchemy.com/nft/v3, so /v3 is stripped too. pathRules: { stripApiKeySegment: true, stripPrefix: "/v3" }, operations: [ @@ -58,6 +84,113 @@ export default { itemsField: "ownedNfts", }, }, + { + operationId: "getNFTsForContract-v3", + exportBaseName: "GetNftsForContract", + pagination: { + pageParam: "startToken", + responseCursorField: "pageKey", + itemsField: "nfts", + }, + }, + { + operationId: "getNFTsForCollection-v3", + exportBaseName: "GetNftsForCollection", + pagination: { + pageParam: "startToken", + responseCursorField: "nextToken", + itemsField: "nfts", + }, + }, + { + operationId: "getNFTMetadata-v3", + exportBaseName: "GetNftMetadata", + }, + { + operationId: "getNFTMetadataBatch-v3", + exportBaseName: "GetNftMetadataBatch", + }, + { + operationId: "getContractMetadata-v3", + exportBaseName: "GetContractMetadata", + }, + { + operationId: "getContractMetadataBatch-v3", + exportBaseName: "GetContractMetadataBatch", + }, + { + operationId: "getCollectionMetadata-v3", + exportBaseName: "GetCollectionMetadata", + }, + { + operationId: "getContractsForOwner-v3", + exportBaseName: "GetContractsForOwner", + pagination: { + pageParam: "pageKey", + responseCursorField: "pageKey", + itemsField: "contracts", + }, + }, + { + operationId: "getCollectionsForOwner-v3", + exportBaseName: "GetCollectionsForOwner", + pagination: { + pageParam: "pageKey", + responseCursorField: "pageKey", + itemsField: "collections", + }, + }, + { + operationId: "getOwnersForNFT-v3", + exportBaseName: "GetOwnersForNft", + }, + { + // Note: request takes pageKey but the response declares no cursor + // field, so no pagination metadata (and no Pages companion). + operationId: "getOwnersForContract-v3", + exportBaseName: "GetOwnersForContract", + }, + { + operationId: "getNFTSales-v3", + exportBaseName: "GetNftSales", + pagination: { + pageParam: "pageKey", + responseCursorField: "pageKey", + itemsField: "nftSales", + }, + }, + { + operationId: "getFloorPrice-v3", + exportBaseName: "GetFloorPrice", + }, + { + operationId: "searchContractMetadata-v3", + exportBaseName: "SearchContractMetadata", + }, + { + operationId: "getSpamContracts-v3", + exportBaseName: "GetSpamContracts", + }, + { + operationId: "isSpamContract-v3", + exportBaseName: "IsSpamContract", + }, + { + operationId: "isAirdropNFT-v3", + exportBaseName: "IsAirdropNft", + }, + { + operationId: "isHolderOfContract-v3", + exportBaseName: "IsHolderOfContract", + }, + { + operationId: "computeRarity-v3", + exportBaseName: "ComputeRarity", + }, + { + operationId: "summarizeNFTAttributes-v3", + exportBaseName: "SummarizeNftAttributes", + }, ], }, ], @@ -82,6 +215,10 @@ export default { schemaTypeName: "TokenRpcSchema", methods: [ { + // No pagination metadata: the docs spec omits pageKey from the + // result schema even though the live API returns one when + // paginating — a spec gap to raise with the docs team. Callers can + // still page manually via options.pageKey. method: "alchemy_getTokenBalances", exportBaseName: "AlchemyGetTokenBalances", }, diff --git a/packages/data-apis/src/actions/nft/computeRarity.ts b/packages/data-apis/src/actions/nft/computeRarity.ts new file mode 100644 index 0000000000..b9f09f0a73 --- /dev/null +++ b/packages/data-apis/src/actions/nft/computeRarity.ts @@ -0,0 +1,35 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { ComputeRarityParams, ComputeRarityResult } from "../../types.js"; + +/** + * Computes attribute rarity for a specific NFT. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {ComputeRarityParams} params Contract address, token id, and optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} Rarity scores per attribute + */ +export async function computeRarity( + client: DataClient, + params: ComputeRarityParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "computeRarity", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getCollectionMetadata.ts b/packages/data-apis/src/actions/nft/getCollectionMetadata.ts new file mode 100644 index 0000000000..49b2af2af9 --- /dev/null +++ b/packages/data-apis/src/actions/nft/getCollectionMetadata.ts @@ -0,0 +1,38 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetCollectionMetadataParams, + GetCollectionMetadataResult, +} from "../../types.js"; + +/** + * Fetches metadata for an NFT collection by slug. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetCollectionMetadataParams} params Collection slug and optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The collection's metadata + */ +export async function getCollectionMetadata( + client: DataClient, + params: GetCollectionMetadataParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getCollectionMetadata", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getCollectionsForOwner.ts b/packages/data-apis/src/actions/nft/getCollectionsForOwner.ts new file mode 100644 index 0000000000..16f9350573 --- /dev/null +++ b/packages/data-apis/src/actions/nft/getCollectionsForOwner.ts @@ -0,0 +1,44 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import { bracketArrayKeys } from "../../internal/query.js"; +import type { GetCollectionsForOwnerQuery } from "../../generated/rest/nft.schema.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetCollectionsForOwnerParams, + GetCollectionsForOwnerResult, +} from "../../types.js"; + +/** + * Fetches all NFT collections an address holds tokens from. The network is + * resolved per request: an explicit `network` param wins, otherwise the + * client's configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetCollectionsForOwnerParams} params Owner address, optional network override, and filters + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The owned collections and pagination cursor + */ +export async function getCollectionsForOwner( + client: DataClient, + params: GetCollectionsForOwnerParams, + options?: RequestOptions, +): Promise { + const { network, ...rest } = params; + const { slug } = resolveRequestNetwork(client, network); + + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getCollectionsForOwner", + method: "GET", + query: bracketArrayKeys(rest, [ + "excludeFilters", + "includeFilters", + ]) as GetCollectionsForOwnerQuery, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getContractMetadata.ts b/packages/data-apis/src/actions/nft/getContractMetadata.ts new file mode 100644 index 0000000000..ed82ccc272 --- /dev/null +++ b/packages/data-apis/src/actions/nft/getContractMetadata.ts @@ -0,0 +1,38 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetContractMetadataParams, + GetContractMetadataResult, +} from "../../types.js"; + +/** + * Fetches metadata for an NFT contract. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetContractMetadataParams} params Contract address and optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The contract's metadata + */ +export async function getContractMetadata( + client: DataClient, + params: GetContractMetadataParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getContractMetadata", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getContractMetadataBatch.ts b/packages/data-apis/src/actions/nft/getContractMetadataBatch.ts new file mode 100644 index 0000000000..bc04a522ef --- /dev/null +++ b/packages/data-apis/src/actions/nft/getContractMetadataBatch.ts @@ -0,0 +1,39 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetContractMetadataBatchParams, + GetContractMetadataBatchResult, +} from "../../types.js"; + +/** + * Fetches metadata for multiple NFT contracts in one call. The network is + * resolved per request: an explicit `network` param wins, otherwise the + * client's configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetContractMetadataBatchParams} params The contract addresses and optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} Metadata for each requested contract + */ +export async function getContractMetadataBatch( + client: DataClient, + params: GetContractMetadataBatchParams, + options?: RequestOptions, +): Promise { + const { network, ...body } = params; + const { slug } = resolveRequestNetwork(client, network); + + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getContractMetadataBatch", + method: "POST", + body, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getContractsForOwner.ts b/packages/data-apis/src/actions/nft/getContractsForOwner.ts new file mode 100644 index 0000000000..67c38fe9c6 --- /dev/null +++ b/packages/data-apis/src/actions/nft/getContractsForOwner.ts @@ -0,0 +1,44 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import { bracketArrayKeys } from "../../internal/query.js"; +import type { GetContractsForOwnerQuery } from "../../generated/rest/nft.schema.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetContractsForOwnerParams, + GetContractsForOwnerResult, +} from "../../types.js"; + +/** + * Fetches all NFT contracts an address holds tokens from. The network is + * resolved per request: an explicit `network` param wins, otherwise the + * client's configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetContractsForOwnerParams} params Owner address, optional network override, and filters + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The owned contracts and pagination cursor + */ +export async function getContractsForOwner( + client: DataClient, + params: GetContractsForOwnerParams, + options?: RequestOptions, +): Promise { + const { network, ...rest } = params; + const { slug } = resolveRequestNetwork(client, network); + + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getContractsForOwner", + method: "GET", + query: bracketArrayKeys(rest, [ + "excludeFilters", + "includeFilters", + ]) as GetContractsForOwnerQuery, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getFloorPrice.ts b/packages/data-apis/src/actions/nft/getFloorPrice.ts new file mode 100644 index 0000000000..0520bb938a --- /dev/null +++ b/packages/data-apis/src/actions/nft/getFloorPrice.ts @@ -0,0 +1,35 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { GetFloorPriceParams, GetFloorPriceResult } from "../../types.js"; + +/** + * Fetches marketplace floor prices for an NFT contract or collection. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetFloorPriceParams} params Contract address or collection slug, and optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} Floor prices per marketplace + */ +export async function getFloorPrice( + client: DataClient, + params: GetFloorPriceParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getFloorPrice", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getNftMetadata.ts b/packages/data-apis/src/actions/nft/getNftMetadata.ts new file mode 100644 index 0000000000..21f52fa43a --- /dev/null +++ b/packages/data-apis/src/actions/nft/getNftMetadata.ts @@ -0,0 +1,38 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetNftMetadataParams, + GetNftMetadataResult, +} from "../../types.js"; + +/** + * Fetches metadata for a single NFT. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftMetadataParams} params Contract address, token id, optional network override, and cache options + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The NFT's metadata + */ +export async function getNftMetadata( + client: DataClient, + params: GetNftMetadataParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getNFTMetadata", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getNftMetadataBatch.ts b/packages/data-apis/src/actions/nft/getNftMetadataBatch.ts new file mode 100644 index 0000000000..344ad6fe1a --- /dev/null +++ b/packages/data-apis/src/actions/nft/getNftMetadataBatch.ts @@ -0,0 +1,39 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetNftMetadataBatchParams, + GetNftMetadataBatchResult, +} from "../../types.js"; + +/** + * Fetches metadata for up to 100 NFTs in one call. The network is resolved + * per request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftMetadataBatchParams} params The tokens to look up, cache options, and optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} Metadata for each requested NFT + */ +export async function getNftMetadataBatch( + client: DataClient, + params: GetNftMetadataBatchParams, + options?: RequestOptions, +): Promise { + const { network, ...body } = params; + const { slug } = resolveRequestNetwork(client, network); + + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getNFTMetadataBatch", + method: "POST", + body, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getNftSales.ts b/packages/data-apis/src/actions/nft/getNftSales.ts new file mode 100644 index 0000000000..b7e43d680c --- /dev/null +++ b/packages/data-apis/src/actions/nft/getNftSales.ts @@ -0,0 +1,35 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { GetNftSalesParams, GetNftSalesResult } from "../../types.js"; + +/** + * Fetches NFT sales matching the given filters. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftSalesParams} params Sale filters, optional network override, and paging options + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The matching sales and pagination cursor + */ +export async function getNftSales( + client: DataClient, + params: GetNftSalesParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getNFTSales", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getNftsForCollection.ts b/packages/data-apis/src/actions/nft/getNftsForCollection.ts new file mode 100644 index 0000000000..f2bcdaffe8 --- /dev/null +++ b/packages/data-apis/src/actions/nft/getNftsForCollection.ts @@ -0,0 +1,38 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetNftsForCollectionParams, + GetNftsForCollectionResult, +} from "../../types.js"; + +/** + * Fetches all NFTs for a given collection (by contract address or collection slug). The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftsForCollectionParams} params Collection identifier, optional network override, and paging filters + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The collection's NFTs and next-page token + */ +export async function getNftsForCollection( + client: DataClient, + params: GetNftsForCollectionParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getNFTsForCollection", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getNftsForContract.ts b/packages/data-apis/src/actions/nft/getNftsForContract.ts new file mode 100644 index 0000000000..9ac9047379 --- /dev/null +++ b/packages/data-apis/src/actions/nft/getNftsForContract.ts @@ -0,0 +1,38 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetNftsForContractParams, + GetNftsForContractResult, +} from "../../types.js"; + +/** + * Fetches all NFTs for a given contract. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftsForContractParams} params Contract address, optional network override, and paging filters + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The contract's NFTs and pagination cursor + */ +export async function getNftsForContract( + client: DataClient, + params: GetNftsForContractParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getNFTsForContract", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getNftsForOwner.ts b/packages/data-apis/src/actions/nft/getNftsForOwner.ts index b8a9736f92..8eda9d1c0a 100644 --- a/packages/data-apis/src/actions/nft/getNftsForOwner.ts +++ b/packages/data-apis/src/actions/nft/getNftsForOwner.ts @@ -5,6 +5,8 @@ import { type DataClient, type RequestOptions, } from "../../internal/clientHelpers.js"; +import { bracketArrayKeys } from "../../internal/query.js"; +import type { GetNftsForOwnerQuery } from "../../generated/rest/nft.schema.js"; import type { NftRestSchema } from "../../schema/rest.js"; import type { GetNftsForOwnerParams, @@ -26,14 +28,18 @@ export async function getNftsForOwner( params: GetNftsForOwnerParams, options?: RequestOptions, ): Promise { - const { network, contractAddresses, ...query } = params; + const { network, ...rest } = params; const { slug } = resolveRequestNetwork(client, network); const restClient = getRestClient(client, getNftApiUrl(slug)); return restClient.request({ route: "getNFTsForOwner", method: "GET", - query: { ...query, "contractAddresses[]": contractAddresses }, + query: bracketArrayKeys(rest, [ + "contractAddresses", + "excludeFilters", + "includeFilters", + ]) as GetNftsForOwnerQuery, signal: options?.signal, }); } diff --git a/packages/data-apis/src/actions/nft/getOwnersForContract.ts b/packages/data-apis/src/actions/nft/getOwnersForContract.ts new file mode 100644 index 0000000000..74550d8f72 --- /dev/null +++ b/packages/data-apis/src/actions/nft/getOwnersForContract.ts @@ -0,0 +1,38 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetOwnersForContractParams, + GetOwnersForContractResult, +} from "../../types.js"; + +/** + * Fetches all owners for an NFT contract. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetOwnersForContractParams} params Contract address, optional network override, and balance options + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The contract's owners + */ +export async function getOwnersForContract( + client: DataClient, + params: GetOwnersForContractParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getOwnersForContract", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getOwnersForNft.ts b/packages/data-apis/src/actions/nft/getOwnersForNft.ts new file mode 100644 index 0000000000..715abd9e9e --- /dev/null +++ b/packages/data-apis/src/actions/nft/getOwnersForNft.ts @@ -0,0 +1,38 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetOwnersForNftParams, + GetOwnersForNftResult, +} from "../../types.js"; + +/** + * Fetches the owners of a specific NFT. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetOwnersForNftParams} params Contract address, token id, and optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The NFT's owners + */ +export async function getOwnersForNft( + client: DataClient, + params: GetOwnersForNftParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getOwnersForNFT", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/getSpamContracts.ts b/packages/data-apis/src/actions/nft/getSpamContracts.ts new file mode 100644 index 0000000000..a750336758 --- /dev/null +++ b/packages/data-apis/src/actions/nft/getSpamContracts.ts @@ -0,0 +1,37 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + GetSpamContractsParams, + GetSpamContractsResult, +} from "../../types.js"; + +/** + * Fetches the full list of contracts classified as spam on a network. The + * network is resolved per request: an explicit `network` param wins, + * otherwise the client's configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetSpamContractsParams} [params] Optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The spam contract addresses + */ +export async function getSpamContracts( + client: DataClient, + params: GetSpamContractsParams = {}, + options?: RequestOptions, +): Promise { + const { slug } = resolveRequestNetwork(client, params.network); + + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "getSpamContracts", + method: "GET", + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/isAirdropNft.ts b/packages/data-apis/src/actions/nft/isAirdropNft.ts new file mode 100644 index 0000000000..2f27da3b75 --- /dev/null +++ b/packages/data-apis/src/actions/nft/isAirdropNft.ts @@ -0,0 +1,35 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { IsAirdropNftParams, IsAirdropNftResult } from "../../types.js"; + +/** + * Checks whether an NFT was airdropped to its owner. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {IsAirdropNftParams} params Contract address, token id, and optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The airdrop classification + */ +export async function isAirdropNft( + client: DataClient, + params: IsAirdropNftParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "isAirdropNFT", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/isHolderOfContract.ts b/packages/data-apis/src/actions/nft/isHolderOfContract.ts new file mode 100644 index 0000000000..1da25701e1 --- /dev/null +++ b/packages/data-apis/src/actions/nft/isHolderOfContract.ts @@ -0,0 +1,38 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + IsHolderOfContractParams, + IsHolderOfContractResult, +} from "../../types.js"; + +/** + * Checks whether a wallet holds any NFT from a contract. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {IsHolderOfContractParams} params Wallet address, contract address, and optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The holder check result + */ +export async function isHolderOfContract( + client: DataClient, + params: IsHolderOfContractParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "isHolderOfContract", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/isSpamContract.ts b/packages/data-apis/src/actions/nft/isSpamContract.ts new file mode 100644 index 0000000000..efddcd46a7 --- /dev/null +++ b/packages/data-apis/src/actions/nft/isSpamContract.ts @@ -0,0 +1,38 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + IsSpamContractParams, + IsSpamContractResult, +} from "../../types.js"; + +/** + * Checks whether a contract is classified as spam. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {IsSpamContractParams} params Contract address and optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The spam classification + */ +export async function isSpamContract( + client: DataClient, + params: IsSpamContractParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "isSpamContract", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/nft.test.ts b/packages/data-apis/src/actions/nft/nft.test.ts new file mode 100644 index 0000000000..6a2ed6bfce --- /dev/null +++ b/packages/data-apis/src/actions/nft/nft.test.ts @@ -0,0 +1,175 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { createDataClient } from "../../client.js"; + +const fetchMock = vi.fn(); + +const jsonResponse = (body: unknown) => + new Response(JSON.stringify(body), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + +const makeClient = () => + createDataClient({ apiKey: "test-key", network: "eth-mainnet" }); + +beforeEach(() => { + vi.stubGlobal("fetch", fetchMock); + fetchMock.mockReset(); + fetchMock.mockImplementation(async () => jsonResponse({})); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +const NFT_BASE = "https://eth-mainnet.g.alchemy.com/nft/v3"; + +describe("nft namespace routing", () => { + // [method call, expected URL] — wire-level checks for the patterned GETs + const cases: Array< + [string, (c: ReturnType) => Promise, string] + > = [ + [ + "getNftsForContract", + (c) => c.nft.getNftsForContract({ contractAddress: "0xc" }), + `${NFT_BASE}/getNFTsForContract?contractAddress=0xc`, + ], + [ + "getNftsForCollection", + (c) => c.nft.getNftsForCollection({ collectionSlug: "slug" }), + `${NFT_BASE}/getNFTsForCollection?collectionSlug=slug`, + ], + [ + "getNftMetadata", + (c) => c.nft.getNftMetadata({ contractAddress: "0xc", tokenId: "1" }), + `${NFT_BASE}/getNFTMetadata?contractAddress=0xc&tokenId=1`, + ], + [ + "getContractMetadata", + (c) => c.nft.getContractMetadata({ contractAddress: "0xc" }), + `${NFT_BASE}/getContractMetadata?contractAddress=0xc`, + ], + [ + "getCollectionMetadata", + (c) => c.nft.getCollectionMetadata({ collectionSlug: "slug" }), + `${NFT_BASE}/getCollectionMetadata?collectionSlug=slug`, + ], + [ + "getOwnersForNft", + (c) => c.nft.getOwnersForNft({ contractAddress: "0xc", tokenId: "1" }), + `${NFT_BASE}/getOwnersForNFT?contractAddress=0xc&tokenId=1`, + ], + [ + "getOwnersForContract", + (c) => c.nft.getOwnersForContract({ contractAddress: "0xc" }), + `${NFT_BASE}/getOwnersForContract?contractAddress=0xc`, + ], + [ + "getNftSales", + (c) => c.nft.getNftSales({ contractAddress: "0xc", limit: 5 }), + `${NFT_BASE}/getNFTSales?contractAddress=0xc&limit=5`, + ], + [ + "getFloorPrice", + (c) => c.nft.getFloorPrice({ contractAddress: "0xc" }), + `${NFT_BASE}/getFloorPrice?contractAddress=0xc`, + ], + [ + "searchContractMetadata", + (c) => c.nft.searchContractMetadata({ query: "apes" }), + `${NFT_BASE}/searchContractMetadata?query=apes`, + ], + [ + "getSpamContracts", + (c) => c.nft.getSpamContracts(), + `${NFT_BASE}/getSpamContracts`, + ], + [ + "isSpamContract", + (c) => c.nft.isSpamContract({ contractAddress: "0xc" }), + `${NFT_BASE}/isSpamContract?contractAddress=0xc`, + ], + [ + "isAirdropNft", + (c) => c.nft.isAirdropNft({ contractAddress: "0xc", tokenId: "1" }), + `${NFT_BASE}/isAirdropNFT?contractAddress=0xc&tokenId=1`, + ], + [ + "isHolderOfContract", + (c) => + c.nft.isHolderOfContract({ wallet: "0xw", contractAddress: "0xc" }), + `${NFT_BASE}/isHolderOfContract?wallet=0xw&contractAddress=0xc`, + ], + [ + "computeRarity", + (c) => c.nft.computeRarity({ contractAddress: "0xc", tokenId: "1" }), + `${NFT_BASE}/computeRarity?contractAddress=0xc&tokenId=1`, + ], + [ + "summarizeNftAttributes", + (c) => c.nft.summarizeNftAttributes({ contractAddress: "0xc" }), + `${NFT_BASE}/summarizeNFTAttributes?contractAddress=0xc`, + ], + ]; + + it.each(cases)("%s hits the expected URL", async (_name, call, expected) => { + await call(makeClient()); + expect(String(fetchMock.mock.calls[0]![0])).toBe(expected); + expect(fetchMock.mock.calls[0]![1].method).toBe("GET"); + }); + + it("re-brackets filter keys for owner-scoped listings", async () => { + const data = makeClient(); + await data.nft.getContractsForOwner({ + owner: "0xo", + excludeFilters: ["SPAM"], + includeFilters: ["AIRDROPS"], + }); + const url = new URL(String(fetchMock.mock.calls[0]![0])); + expect(url.pathname.endsWith("/getContractsForOwner")).toBe(true); + expect(url.searchParams.getAll("excludeFilters[]")).toEqual(["SPAM"]); + expect(url.searchParams.getAll("includeFilters[]")).toEqual(["AIRDROPS"]); + + await data.nft.getCollectionsForOwner({ + owner: "0xo", + excludeFilters: ["SPAM"], + }); + const url2 = new URL(String(fetchMock.mock.calls[1]![0])); + expect(url2.searchParams.getAll("excludeFilters[]")).toEqual(["SPAM"]); + }); + + it("posts batch metadata bodies", async () => { + const data = makeClient(); + await data.nft.getNftMetadataBatch({ + tokens: [{ contractAddress: "0xc", tokenId: "1" }], + }); + expect(String(fetchMock.mock.calls[0]![0])).toBe( + `${NFT_BASE}/getNFTMetadataBatch`, + ); + expect(fetchMock.mock.calls[0]![1].method).toBe("POST"); + expect(JSON.parse(fetchMock.mock.calls[0]![1].body)).toEqual({ + tokens: [{ contractAddress: "0xc", tokenId: "1" }], + }); + + await data.nft.getContractMetadataBatch({ + contractAddresses: ["0xc1", "0xc2"], + }); + expect(String(fetchMock.mock.calls[1]![0])).toBe( + `${NFT_BASE}/getContractMetadataBatch`, + ); + expect(JSON.parse(fetchMock.mock.calls[1]![1].body)).toEqual({ + contractAddresses: ["0xc1", "0xc2"], + }); + }); + + it("honors per-request network overrides", async () => { + const data = makeClient(); + await data.nft.getContractMetadata({ + contractAddress: "0xc", + network: "base-mainnet", + }); + expect(String(fetchMock.mock.calls[0]![0])).toBe( + "https://base-mainnet.g.alchemy.com/nft/v3/getContractMetadata?contractAddress=0xc", + ); + }); +}); diff --git a/packages/data-apis/src/actions/nft/searchContractMetadata.ts b/packages/data-apis/src/actions/nft/searchContractMetadata.ts new file mode 100644 index 0000000000..2fe894274c --- /dev/null +++ b/packages/data-apis/src/actions/nft/searchContractMetadata.ts @@ -0,0 +1,38 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + SearchContractMetadataParams, + SearchContractMetadataResult, +} from "../../types.js"; + +/** + * Searches NFT contract metadata by keyword. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {SearchContractMetadataParams} params Search query and optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The matching contracts + */ +export async function searchContractMetadata( + client: DataClient, + params: SearchContractMetadataParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "searchContractMetadata", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/nft/summarizeNftAttributes.ts b/packages/data-apis/src/actions/nft/summarizeNftAttributes.ts new file mode 100644 index 0000000000..057edaf64a --- /dev/null +++ b/packages/data-apis/src/actions/nft/summarizeNftAttributes.ts @@ -0,0 +1,38 @@ +import { getNftApiUrl } from "../../internal/endpoints.js"; +import { + getRestClient, + resolveRequestNetwork, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { NftRestSchema } from "../../schema/rest.js"; +import type { + SummarizeNftAttributesParams, + SummarizeNftAttributesResult, +} from "../../types.js"; + +/** + * Summarizes attribute prevalence for an NFT contract. The network is resolved per + * request: an explicit `network` param wins, otherwise the client's + * configured network/chain applies. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {SummarizeNftAttributesParams} params Contract address and optional network override + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The attribute summary + */ +export async function summarizeNftAttributes( + client: DataClient, + params: SummarizeNftAttributesParams, + options?: RequestOptions, +): Promise { + const { network, ...query } = params; + const { slug } = resolveRequestNetwork(client, network); + const restClient = getRestClient(client, getNftApiUrl(slug)); + return restClient.request({ + route: "summarizeNFTAttributes", + method: "GET", + query, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/portfolio/getNftContractsByAddress.ts b/packages/data-apis/src/actions/portfolio/getNftContractsByAddress.ts new file mode 100644 index 0000000000..1440eca92f --- /dev/null +++ b/packages/data-apis/src/actions/portfolio/getNftContractsByAddress.ts @@ -0,0 +1,47 @@ +import { resolveNetwork } from "@alchemy/common"; +import { DATA_API_URL } from "../../internal/endpoints.js"; +import { + getRestClient, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { GetNftContractsByAddressBody } from "../../generated/rest/portfolio.schema.js"; +import type { PortfolioRestSchema } from "../../schema/rest.js"; +import type { + GetNftContractsByAddressParams, + GetNftContractsByAddressResult, +} from "../../types.js"; + +/** + * Fetches NFT contracts for one or more addresses across one or more + * networks in a single call. + * This is a multi-network request against the global Data API: networks + * travel in the request body, so the client's chain is not involved. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftContractsByAddressParams} params Addresses paired with networks, plus options + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The owned NFT contracts and pagination cursor + */ +export async function getNftContractsByAddress( + client: DataClient, + params: GetNftContractsByAddressParams, + options?: RequestOptions, +): Promise { + const { addresses, ...rest } = params; + const restClient = getRestClient(client, DATA_API_URL); + return restClient.request({ + route: "assets/nfts/contracts/by-address", + method: "POST", + // The cast widens the spec's network-slug enum: the SDK deliberately + // accepts registry-unknown slugs as an escape hatch. + body: { + ...rest, + addresses: addresses.map(({ networks, ...entry }) => ({ + ...entry, + networks: networks.map((n) => resolveNetwork(n).slug), + })), + } as GetNftContractsByAddressBody, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/portfolio/getNftsByAddress.ts b/packages/data-apis/src/actions/portfolio/getNftsByAddress.ts new file mode 100644 index 0000000000..38c6c8cc52 --- /dev/null +++ b/packages/data-apis/src/actions/portfolio/getNftsByAddress.ts @@ -0,0 +1,47 @@ +import { resolveNetwork } from "@alchemy/common"; +import { DATA_API_URL } from "../../internal/endpoints.js"; +import { + getRestClient, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { GetNftsByAddressBody } from "../../generated/rest/portfolio.schema.js"; +import type { PortfolioRestSchema } from "../../schema/rest.js"; +import type { + GetNftsByAddressParams, + GetNftsByAddressResult, +} from "../../types.js"; + +/** + * Fetches NFTs for one or more addresses across one or more networks in a + * single call. + * This is a multi-network request against the global Data API: networks + * travel in the request body, so the client's chain is not involved. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftsByAddressParams} params Addresses paired with networks, plus options + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The owned NFTs and pagination cursor + */ +export async function getNftsByAddress( + client: DataClient, + params: GetNftsByAddressParams, + options?: RequestOptions, +): Promise { + const { addresses, ...rest } = params; + const restClient = getRestClient(client, DATA_API_URL); + return restClient.request({ + route: "assets/nfts/by-address", + method: "POST", + // The cast widens the spec's network-slug enum: the SDK deliberately + // accepts registry-unknown slugs as an escape hatch. + body: { + ...rest, + addresses: addresses.map(({ networks, ...entry }) => ({ + ...entry, + networks: networks.map((n) => resolveNetwork(n).slug), + })), + } as GetNftsByAddressBody, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/portfolio/getTokenBalancesByAddress.ts b/packages/data-apis/src/actions/portfolio/getTokenBalancesByAddress.ts new file mode 100644 index 0000000000..8c8acb89cc --- /dev/null +++ b/packages/data-apis/src/actions/portfolio/getTokenBalancesByAddress.ts @@ -0,0 +1,47 @@ +import { resolveNetwork } from "@alchemy/common"; +import { DATA_API_URL } from "../../internal/endpoints.js"; +import { + getRestClient, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { GetTokenBalancesByAddressBody } from "../../generated/rest/portfolio.schema.js"; +import type { PortfolioRestSchema } from "../../schema/rest.js"; +import type { + GetTokenBalancesByAddressParams, + GetTokenBalancesByAddressResult, +} from "../../types.js"; + +/** + * Fetches token balances (without metadata or prices) for one or more + * addresses across one or more networks in a single call. + * This is a multi-network request against the global Data API: networks + * travel in the request body, so the client's chain is not involved. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetTokenBalancesByAddressParams} params Addresses paired with networks, plus options + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} Token balances per wallet/network pair + */ +export async function getTokenBalancesByAddress( + client: DataClient, + params: GetTokenBalancesByAddressParams, + options?: RequestOptions, +): Promise { + const { addresses, ...rest } = params; + const restClient = getRestClient(client, DATA_API_URL); + return restClient.request({ + route: "assets/tokens/balances/by-address", + method: "POST", + // The cast widens the spec's network-slug enum: the SDK deliberately + // accepts registry-unknown slugs as an escape hatch. + body: { + ...rest, + addresses: addresses.map(({ networks, ...entry }) => ({ + ...entry, + networks: networks.map((n) => resolveNetwork(n).slug), + })), + } as GetTokenBalancesByAddressBody, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/portfolio/getTokensByAddress.ts b/packages/data-apis/src/actions/portfolio/getTokensByAddress.ts index dde71c5bb0..c18bc31dc5 100644 --- a/packages/data-apis/src/actions/portfolio/getTokensByAddress.ts +++ b/packages/data-apis/src/actions/portfolio/getTokensByAddress.ts @@ -3,6 +3,7 @@ import { DATA_API_URL } from "../../internal/endpoints.js"; import { getRestClient, type DataClient, + type RequestOptions, } from "../../internal/clientHelpers.js"; import type { PortfolioRestSchema } from "../../schema/rest.js"; import type { @@ -27,23 +28,26 @@ import type { * * @param {DataClient} client A client configured with an Alchemy transport * @param {GetTokensByAddressParams} params Addresses paired with networks, plus options + * @param {RequestOptions} [options] Per-request options (abort signal) * @returns {Promise} Token balances, metadata, and prices */ export async function getTokensByAddress( client: DataClient, params: GetTokensByAddressParams, + options?: RequestOptions, ): Promise { - const { addresses, ...options } = params; + const { addresses, ...rest } = params; const restClient = getRestClient(client, DATA_API_URL); return restClient.request({ route: "assets/tokens/by-address", method: "POST", body: { - ...options, + ...rest, addresses: addresses.map(({ address, networks }) => ({ address, networks: networks.map((n) => resolveNetwork(n).slug), })), }, + signal: options?.signal, }); } diff --git a/packages/data-apis/src/actions/portfolio/portfolio.test.ts b/packages/data-apis/src/actions/portfolio/portfolio.test.ts new file mode 100644 index 0000000000..ad0765733f --- /dev/null +++ b/packages/data-apis/src/actions/portfolio/portfolio.test.ts @@ -0,0 +1,63 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { createDataClient } from "../../client.js"; + +const fetchMock = vi.fn(); + +const jsonResponse = (body: unknown) => + new Response(JSON.stringify(body), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + +const makeClient = () => + createDataClient({ apiKey: "test-key", network: "eth-mainnet" }); + +beforeEach(() => { + vi.stubGlobal("fetch", fetchMock); + fetchMock.mockReset(); + fetchMock.mockImplementation(async () => jsonResponse({ data: {} })); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +const DATA_BASE = "https://api.g.alchemy.com/data/v1"; + +describe("portfolio namespace", () => { + it.each([ + ["getTokenBalancesByAddress", "assets/tokens/balances/by-address"], + ["getNftsByAddress", "assets/nfts/by-address"], + ["getNftContractsByAddress", "assets/nfts/contracts/by-address"], + ] as const)("%s posts to the global Data API", async (method, route) => { + const data = makeClient(); + await data.portfolio[method]({ + addresses: [{ address: "0xa", networks: ["eth-mainnet", "eip155:8453"] }], + }); + expect(String(fetchMock.mock.calls[0]![0])).toBe(`${DATA_BASE}/${route}`); + expect(JSON.parse(fetchMock.mock.calls[0]![1].body)).toEqual({ + addresses: [ + { address: "0xa", networks: ["eth-mainnet", "base-mainnet"] }, + ], + }); + }); + + it("preserves per-entry fields beyond address/networks", async () => { + await makeClient().portfolio.getNftsByAddress({ + addresses: [ + { + address: "0xa", + networks: ["eth-mainnet"], + excludeFilters: ["SPAM"], + }, + ], + pageSize: 10, + }); + expect(JSON.parse(fetchMock.mock.calls[0]![1].body)).toEqual({ + addresses: [ + { address: "0xa", networks: ["eth-mainnet"], excludeFilters: ["SPAM"] }, + ], + pageSize: 10, + }); + }); +}); diff --git a/packages/data-apis/src/actions/prices/getHistoricalTokenPrices.ts b/packages/data-apis/src/actions/prices/getHistoricalTokenPrices.ts new file mode 100644 index 0000000000..dbba35e97e --- /dev/null +++ b/packages/data-apis/src/actions/prices/getHistoricalTokenPrices.ts @@ -0,0 +1,44 @@ +import { resolveNetwork } from "@alchemy/common"; +import { PRICES_API_URL } from "../../internal/endpoints.js"; +import { + getRestClient, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { GetHistoricalTokenPricesBody } from "../../generated/rest/prices.schema.js"; +import type { PricesRestSchema } from "../../schema/rest.js"; +import type { + GetHistoricalTokenPricesParams, + GetHistoricalTokenPricesResult, +} from "../../types.js"; + +/** + * Fetches historical prices for a token, identified either by symbol + * (chain-agnostic) or by network + contract address. Runs against the global + * Prices API; when a network is given it travels in the request body, so the + * client's chain is not involved. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetHistoricalTokenPricesParams} params Token identifier, time range, and interval + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} The price series + */ +export async function getHistoricalTokenPrices( + client: DataClient, + params: GetHistoricalTokenPricesParams, + options?: RequestOptions, +): Promise { + const body = ( + "network" in params && params.network != null + ? { ...params, network: resolveNetwork(params.network).slug } + : params + ) as GetHistoricalTokenPricesBody; + + const restClient = getRestClient(client, PRICES_API_URL); + return restClient.request({ + route: "tokens/historical", + method: "POST", + body, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/prices/getTokenPricesByAddress.ts b/packages/data-apis/src/actions/prices/getTokenPricesByAddress.ts new file mode 100644 index 0000000000..47430f495d --- /dev/null +++ b/packages/data-apis/src/actions/prices/getTokenPricesByAddress.ts @@ -0,0 +1,41 @@ +import { resolveNetwork } from "@alchemy/common"; +import { PRICES_API_URL } from "../../internal/endpoints.js"; +import { + getRestClient, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { PricesRestSchema } from "../../schema/rest.js"; +import type { + GetTokenPricesByAddressParams, + GetTokenPricesByAddressResult, +} from "../../types.js"; + +/** + * Fetches current prices for tokens by contract address (max 25 pairs). This + * is a multi-network request against the global Prices API: each entry pairs + * an address with its network, so the client's chain is not involved. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetTokenPricesByAddressParams} params Address/network pairs to price + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} Prices per address/network pair + */ +export async function getTokenPricesByAddress( + client: DataClient, + params: GetTokenPricesByAddressParams, + options?: RequestOptions, +): Promise { + const restClient = getRestClient(client, PRICES_API_URL); + return restClient.request({ + route: "tokens/by-address", + method: "POST", + body: { + addresses: params.addresses.map(({ address, network }) => ({ + address, + network: resolveNetwork(network).slug, + })), + }, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/prices/getTokenPricesBySymbol.ts b/packages/data-apis/src/actions/prices/getTokenPricesBySymbol.ts new file mode 100644 index 0000000000..a87955bc9f --- /dev/null +++ b/packages/data-apis/src/actions/prices/getTokenPricesBySymbol.ts @@ -0,0 +1,35 @@ +import { PRICES_API_URL } from "../../internal/endpoints.js"; +import { + getRestClient, + type DataClient, + type RequestOptions, +} from "../../internal/clientHelpers.js"; +import type { PricesRestSchema } from "../../schema/rest.js"; +import type { + GetTokenPricesBySymbolParams, + GetTokenPricesBySymbolResult, +} from "../../types.js"; + +/** + * Fetches current prices for tokens by symbol (max 25). This is a + * chain-agnostic request against the global Prices API — no network is + * involved at all. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetTokenPricesBySymbolParams} params The token symbols to price + * @param {RequestOptions} [options] Per-request options (abort signal) + * @returns {Promise} Prices per symbol + */ +export async function getTokenPricesBySymbol( + client: DataClient, + params: GetTokenPricesBySymbolParams, + options?: RequestOptions, +): Promise { + const restClient = getRestClient(client, PRICES_API_URL); + return restClient.request({ + route: "tokens/by-symbol", + method: "GET", + query: params, + signal: options?.signal, + }); +} diff --git a/packages/data-apis/src/actions/prices/prices.test.ts b/packages/data-apis/src/actions/prices/prices.test.ts new file mode 100644 index 0000000000..3b6a82a4eb --- /dev/null +++ b/packages/data-apis/src/actions/prices/prices.test.ts @@ -0,0 +1,84 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { mainnet } from "viem/chains"; +import { createDataClient } from "../../client.js"; + +const fetchMock = vi.fn(); + +const jsonResponse = (body: unknown) => + new Response(JSON.stringify(body), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + +const makeClient = () => + createDataClient({ apiKey: "test-key", network: "eth-mainnet" }); + +beforeEach(() => { + vi.stubGlobal("fetch", fetchMock); + fetchMock.mockReset(); + fetchMock.mockImplementation(async () => jsonResponse({ data: [] })); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +const PRICES_BASE = "https://api.g.alchemy.com/prices/v1"; + +describe("prices namespace", () => { + it("by-symbol is a chain-agnostic GET with repeated symbols", async () => { + await makeClient().prices.getTokenPricesBySymbol({ + symbols: ["ETH", "USDC"], + }); + const url = new URL(String(fetchMock.mock.calls[0]![0])); + expect(`${url.origin}${url.pathname}`).toBe( + `${PRICES_BASE}/tokens/by-symbol`, + ); + expect(url.searchParams.getAll("symbols")).toEqual(["ETH", "USDC"]); + expect(fetchMock.mock.calls[0]![1].method).toBe("GET"); + }); + + it("by-address resolves each entry's network to a slug in the body", async () => { + await makeClient().prices.getTokenPricesByAddress({ + addresses: [ + { address: "0xa", network: mainnet }, + { address: "0xb", network: "eip155:8453" }, + { address: "0xc", network: "polygon-mainnet" }, + ], + }); + expect(String(fetchMock.mock.calls[0]![0])).toBe( + `${PRICES_BASE}/tokens/by-address`, + ); + expect(JSON.parse(fetchMock.mock.calls[0]![1].body)).toEqual({ + addresses: [ + { address: "0xa", network: "eth-mainnet" }, + { address: "0xb", network: "base-mainnet" }, + { address: "0xc", network: "polygon-mainnet" }, + ], + }); + }); + + it("historical accepts symbol form untouched and resolves network form", async () => { + const data = makeClient(); + await data.prices.getHistoricalTokenPrices({ + symbol: "ETH", + startTime: "2024-01-01T00:00:00Z", + endTime: "2024-01-02T00:00:00Z", + interval: "1h", + }); + expect(JSON.parse(fetchMock.mock.calls[0]![1].body).symbol).toBe("ETH"); + + await data.prices.getHistoricalTokenPrices({ + network: "eip155:1", + address: "0xa", + startTime: "2024-01-01T00:00:00Z", + endTime: "2024-01-02T00:00:00Z", + interval: "1d", + }); + const body = JSON.parse(fetchMock.mock.calls[1]![1].body); + expect(body.network).toBe("eth-mainnet"); + expect(String(fetchMock.mock.calls[1]![0])).toBe( + `${PRICES_BASE}/tokens/historical`, + ); + }); +}); diff --git a/packages/data-apis/src/actions/token/getTokenAllowance.ts b/packages/data-apis/src/actions/token/getTokenAllowance.ts new file mode 100644 index 0000000000..67cb94315d --- /dev/null +++ b/packages/data-apis/src/actions/token/getTokenAllowance.ts @@ -0,0 +1,30 @@ +import { + getRpcRequest, + type DataClient, +} from "../../internal/clientHelpers.js"; +import type { + GetTokenAllowanceParams, + GetTokenAllowanceResult, +} from "../../types.js"; + +/** + * Fetches the ERC-20 allowance a spender has from an owner via the + * `alchemy_getTokenAllowance` JSON-RPC method. Without a `network` override + * this uses the client's transport; with one, a transport instance is derived + * for the override network. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetTokenAllowanceParams} params Contract, owner, spender, and optional network override + * @returns {Promise} The allowance as a decimal string + */ +export async function getTokenAllowance( + client: DataClient, + params: GetTokenAllowanceParams, +): Promise { + const { network, ...allowanceRequest } = params; + const request = getRpcRequest(client, network); + return request({ + method: "alchemy_getTokenAllowance", + params: [allowanceRequest], + }); +} diff --git a/packages/data-apis/src/actions/token/getTokenBalances.ts b/packages/data-apis/src/actions/token/getTokenBalances.ts new file mode 100644 index 0000000000..c303ef7742 --- /dev/null +++ b/packages/data-apis/src/actions/token/getTokenBalances.ts @@ -0,0 +1,42 @@ +import { + getRpcRequest, + type DataClient, +} from "../../internal/clientHelpers.js"; +import type { TokenRpcSchema } from "../../generated/rpc/token.js"; +import type { + GetTokenBalancesParams, + GetTokenBalancesResult, +} from "../../types.js"; + +/** + * Fetches ERC-20 (and native) token balances for an address via the + * `alchemy_getTokenBalances` JSON-RPC method. Without a `network` override + * this uses the client's transport; with one, a transport instance is derived + * for the override network. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetTokenBalancesParams} params Address, token spec, paging options, and optional network override + * @returns {Promise} The token balances + */ +export async function getTokenBalances( + client: DataClient, + params: GetTokenBalancesParams, +): Promise { + const { network, address, tokenSpec, options } = params; + const request = getRpcRequest(client, network); + + // Positional JSON-RPC params; trailing optionals are omitted when unset. + // "erc20" is the spec's default tokenSpec, filled in only when paging + // options are supplied without one. + const rpcParams: TokenRpcSchema[0]["Parameters"] = + options !== undefined + ? [address, tokenSpec ?? "erc20", options] + : tokenSpec !== undefined + ? [address, tokenSpec] + : [address]; + + return request({ + method: "alchemy_getTokenBalances", + params: rpcParams, + }); +} diff --git a/packages/data-apis/src/actions/token/getTokenMetadata.ts b/packages/data-apis/src/actions/token/getTokenMetadata.ts new file mode 100644 index 0000000000..7b9593fad4 --- /dev/null +++ b/packages/data-apis/src/actions/token/getTokenMetadata.ts @@ -0,0 +1,30 @@ +import { + getRpcRequest, + type DataClient, +} from "../../internal/clientHelpers.js"; +import type { + GetTokenMetadataParams, + GetTokenMetadataResult, +} from "../../types.js"; + +/** + * Fetches metadata (name, symbol, decimals, logo) for a token contract via + * the `alchemy_getTokenMetadata` JSON-RPC method. Without a `network` + * override this uses the client's transport; with one, a transport instance + * is derived for the override network. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetTokenMetadataParams} params The contract address and optional network override + * @returns {Promise} The token metadata + */ +export async function getTokenMetadata( + client: DataClient, + params: GetTokenMetadataParams, +): Promise { + const { network, contractAddress } = params; + const request = getRpcRequest(client, network); + return request({ + method: "alchemy_getTokenMetadata", + params: [contractAddress], + }); +} diff --git a/packages/data-apis/src/actions/token/token.test.ts b/packages/data-apis/src/actions/token/token.test.ts new file mode 100644 index 0000000000..58c3c5a9ce --- /dev/null +++ b/packages/data-apis/src/actions/token/token.test.ts @@ -0,0 +1,76 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { createDataClient } from "../../client.js"; + +const fetchMock = vi.fn(); + +const rpcResponse = (result: unknown) => + new Response(JSON.stringify({ jsonrpc: "2.0", id: 1, result }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + +const makeClient = () => + createDataClient({ apiKey: "test-key", network: "eth-mainnet" }); + +beforeEach(() => { + vi.stubGlobal("fetch", fetchMock); + fetchMock.mockReset(); + fetchMock.mockImplementation(async () => rpcResponse({})); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +const rpcBody = (call: number) => + JSON.parse(fetchMock.mock.calls[call]![1].body); + +describe("token namespace", () => { + it("getTokenBalances sends positional params, omitting unset trailing optionals", async () => { + const data = makeClient(); + await data.token.getTokenBalances({ address: "0xa" }); + expect(rpcBody(0).method).toBe("alchemy_getTokenBalances"); + expect(rpcBody(0).params).toEqual(["0xa"]); + + await data.token.getTokenBalances({ address: "0xa", tokenSpec: "erc20" }); + expect(rpcBody(1).params).toEqual(["0xa", "erc20"]); + + await data.token.getTokenBalances({ + address: "0xa", + options: { pageKey: "next", maxCount: 10 }, + }); + // tokenSpec defaults to "erc20" when paging options are supplied alone + expect(rpcBody(2).params).toEqual([ + "0xa", + "erc20", + { pageKey: "next", maxCount: 10 }, + ]); + }); + + it("getTokenMetadata and getTokenAllowance send spec-shaped params", async () => { + const data = makeClient(); + await data.token.getTokenMetadata({ contractAddress: "0xc" }); + expect(rpcBody(0).method).toBe("alchemy_getTokenMetadata"); + expect(rpcBody(0).params).toEqual(["0xc"]); + + await data.token.getTokenAllowance({ + contract: "0xc", + owner: "0xo", + spender: "0xs", + }); + expect(rpcBody(1).method).toBe("alchemy_getTokenAllowance"); + expect(rpcBody(1).params).toEqual([ + { contract: "0xc", owner: "0xo", spender: "0xs" }, + ]); + }); + + it("honors per-request network overrides", async () => { + await makeClient().token.getTokenMetadata({ + contractAddress: "0xc", + network: "arb-mainnet", + }); + expect(String(fetchMock.mock.calls[0]![0])).toContain( + "https://arb-mainnet.g.alchemy.com/v2", + ); + }); +}); diff --git a/packages/data-apis/src/actions/transfers/getAssetTransfers.ts b/packages/data-apis/src/actions/transfers/getAssetTransfers.ts index f1726e9987..796edad0ab 100644 --- a/packages/data-apis/src/actions/transfers/getAssetTransfers.ts +++ b/packages/data-apis/src/actions/transfers/getAssetTransfers.ts @@ -1,16 +1,11 @@ -import { alchemyTransport } from "@alchemy/common"; -import { getRpcUrl } from "../../internal/endpoints.js"; import { - getTransportConfig, - resolveRequestNetwork, + getRpcRequest, type DataClient, } from "../../internal/clientHelpers.js"; -import type { DataRpcSchema } from "../../schema/rpc.js"; import type { GetAssetTransfersParams, GetAssetTransfersResult, } from "../../types.js"; -import type { EIP1193RequestFn } from "viem"; /** * Fetches historical asset transfers (external, internal, token) for the @@ -30,19 +25,7 @@ export async function getAssetTransfers( params: GetAssetTransfersParams, ): Promise { const { network, ...rpcParams } = params; - - const request = (() => { - if (!network) { - return client.request as EIP1193RequestFn; - } - const { slug } = resolveRequestNetwork(client, network); - const derived = alchemyTransport({ - ...getTransportConfig(client), - url: getRpcUrl(slug), - })({ retryCount: 0 }); - return derived.request as EIP1193RequestFn; - })(); - + const request = getRpcRequest(client, network); return request({ method: "alchemy_getAssetTransfers", params: [rpcParams], diff --git a/packages/data-apis/src/decorator.ts b/packages/data-apis/src/decorator.ts index 27e98ad87d..2d736cc9f5 100644 --- a/packages/data-apis/src/decorator.ts +++ b/packages/data-apis/src/decorator.ts @@ -1,32 +1,165 @@ -import type { - GetAssetTransfersParams, - GetAssetTransfersResult, - GetNftsForOwnerParams, - GetNftsForOwnerResult, - GetTokensByAddressParams, - GetTokensByAddressResult, -} from "./types.js"; -import type { DataClient } from "./internal/clientHelpers.js"; +import type { DataClient, RequestOptions } from "./internal/clientHelpers.js"; +import type * as T from "./types.js"; + import { getTokensByAddress } from "./actions/portfolio/getTokensByAddress.js"; +import { getTokenBalancesByAddress } from "./actions/portfolio/getTokenBalancesByAddress.js"; +import { getNftsByAddress } from "./actions/portfolio/getNftsByAddress.js"; +import { getNftContractsByAddress } from "./actions/portfolio/getNftContractsByAddress.js"; + +import { getTokenPricesBySymbol } from "./actions/prices/getTokenPricesBySymbol.js"; +import { getTokenPricesByAddress } from "./actions/prices/getTokenPricesByAddress.js"; +import { getHistoricalTokenPrices } from "./actions/prices/getHistoricalTokenPrices.js"; + import { getNftsForOwner } from "./actions/nft/getNftsForOwner.js"; +import { getNftsForContract } from "./actions/nft/getNftsForContract.js"; +import { getNftsForCollection } from "./actions/nft/getNftsForCollection.js"; +import { getNftMetadata } from "./actions/nft/getNftMetadata.js"; +import { getNftMetadataBatch } from "./actions/nft/getNftMetadataBatch.js"; +import { getContractMetadata } from "./actions/nft/getContractMetadata.js"; +import { getContractMetadataBatch } from "./actions/nft/getContractMetadataBatch.js"; +import { getCollectionMetadata } from "./actions/nft/getCollectionMetadata.js"; +import { getContractsForOwner } from "./actions/nft/getContractsForOwner.js"; +import { getCollectionsForOwner } from "./actions/nft/getCollectionsForOwner.js"; +import { getOwnersForNft } from "./actions/nft/getOwnersForNft.js"; +import { getOwnersForContract } from "./actions/nft/getOwnersForContract.js"; +import { getNftSales } from "./actions/nft/getNftSales.js"; +import { getFloorPrice } from "./actions/nft/getFloorPrice.js"; +import { searchContractMetadata } from "./actions/nft/searchContractMetadata.js"; +import { getSpamContracts } from "./actions/nft/getSpamContracts.js"; +import { isSpamContract } from "./actions/nft/isSpamContract.js"; +import { isAirdropNft } from "./actions/nft/isAirdropNft.js"; +import { isHolderOfContract } from "./actions/nft/isHolderOfContract.js"; +import { computeRarity } from "./actions/nft/computeRarity.js"; +import { summarizeNftAttributes } from "./actions/nft/summarizeNftAttributes.js"; + +import { getTokenBalances } from "./actions/token/getTokenBalances.js"; +import { getTokenMetadata } from "./actions/token/getTokenMetadata.js"; +import { getTokenAllowance } from "./actions/token/getTokenAllowance.js"; + import { getAssetTransfers } from "./actions/transfers/getAssetTransfers.js"; +type Action = ( + params: Params, + options?: RequestOptions, +) => Promise; + +type RpcAction = (params: Params) => Promise; + /** The namespaced Data API actions attached by the {@link dataActions} decorator. */ export type DataActions = { portfolio: { - getTokensByAddress: ( - params: GetTokensByAddressParams, - ) => Promise; + getTokensByAddress: Action< + T.GetTokensByAddressParams, + T.GetTokensByAddressResult + >; + getTokenBalancesByAddress: Action< + T.GetTokenBalancesByAddressParams, + T.GetTokenBalancesByAddressResult + >; + getNftsByAddress: Action< + T.GetNftsByAddressParams, + T.GetNftsByAddressResult + >; + getNftContractsByAddress: Action< + T.GetNftContractsByAddressParams, + T.GetNftContractsByAddressResult + >; + }; + prices: { + getTokenPricesBySymbol: Action< + T.GetTokenPricesBySymbolParams, + T.GetTokenPricesBySymbolResult + >; + getTokenPricesByAddress: Action< + T.GetTokenPricesByAddressParams, + T.GetTokenPricesByAddressResult + >; + getHistoricalTokenPrices: Action< + T.GetHistoricalTokenPricesParams, + T.GetHistoricalTokenPricesResult + >; }; nft: { - getNftsForOwner: ( - params: GetNftsForOwnerParams, - ) => Promise; + getNftsForOwner: Action; + getNftsForContract: Action< + T.GetNftsForContractParams, + T.GetNftsForContractResult + >; + getNftsForCollection: Action< + T.GetNftsForCollectionParams, + T.GetNftsForCollectionResult + >; + getNftMetadata: Action; + getNftMetadataBatch: Action< + T.GetNftMetadataBatchParams, + T.GetNftMetadataBatchResult + >; + getContractMetadata: Action< + T.GetContractMetadataParams, + T.GetContractMetadataResult + >; + getContractMetadataBatch: Action< + T.GetContractMetadataBatchParams, + T.GetContractMetadataBatchResult + >; + getCollectionMetadata: Action< + T.GetCollectionMetadataParams, + T.GetCollectionMetadataResult + >; + getContractsForOwner: Action< + T.GetContractsForOwnerParams, + T.GetContractsForOwnerResult + >; + getCollectionsForOwner: Action< + T.GetCollectionsForOwnerParams, + T.GetCollectionsForOwnerResult + >; + getOwnersForNft: Action; + getOwnersForContract: Action< + T.GetOwnersForContractParams, + T.GetOwnersForContractResult + >; + getNftSales: Action; + getFloorPrice: Action; + searchContractMetadata: Action< + T.SearchContractMetadataParams, + T.SearchContractMetadataResult + >; + getSpamContracts: Action< + T.GetSpamContractsParams, + T.GetSpamContractsResult + >; + isSpamContract: Action; + isAirdropNft: Action; + isHolderOfContract: Action< + T.IsHolderOfContractParams, + T.IsHolderOfContractResult + >; + computeRarity: Action; + summarizeNftAttributes: Action< + T.SummarizeNftAttributesParams, + T.SummarizeNftAttributesResult + >; + }; + token: { + getTokenBalances: RpcAction< + T.GetTokenBalancesParams, + T.GetTokenBalancesResult + >; + getTokenMetadata: RpcAction< + T.GetTokenMetadataParams, + T.GetTokenMetadataResult + >; + getTokenAllowance: RpcAction< + T.GetTokenAllowanceParams, + T.GetTokenAllowanceResult + >; }; transfers: { - getAssetTransfers: ( - params: GetAssetTransfersParams, - ) => Promise; + getAssetTransfers: RpcAction< + T.GetAssetTransfersParams, + T.GetAssetTransfersResult + >; }; }; @@ -55,10 +188,69 @@ export type DataActions = { export function dataActions(client: DataClient): DataActions { return { portfolio: { - getTokensByAddress: (params) => getTokensByAddress(client, params), + getTokensByAddress: (params, options) => + getTokensByAddress(client, params, options), + getTokenBalancesByAddress: (params, options) => + getTokenBalancesByAddress(client, params, options), + getNftsByAddress: (params, options) => + getNftsByAddress(client, params, options), + getNftContractsByAddress: (params, options) => + getNftContractsByAddress(client, params, options), + }, + prices: { + getTokenPricesBySymbol: (params, options) => + getTokenPricesBySymbol(client, params, options), + getTokenPricesByAddress: (params, options) => + getTokenPricesByAddress(client, params, options), + getHistoricalTokenPrices: (params, options) => + getHistoricalTokenPrices(client, params, options), }, nft: { - getNftsForOwner: (params) => getNftsForOwner(client, params), + getNftsForOwner: (params, options) => + getNftsForOwner(client, params, options), + getNftsForContract: (params, options) => + getNftsForContract(client, params, options), + getNftsForCollection: (params, options) => + getNftsForCollection(client, params, options), + getNftMetadata: (params, options) => + getNftMetadata(client, params, options), + getNftMetadataBatch: (params, options) => + getNftMetadataBatch(client, params, options), + getContractMetadata: (params, options) => + getContractMetadata(client, params, options), + getContractMetadataBatch: (params, options) => + getContractMetadataBatch(client, params, options), + getCollectionMetadata: (params, options) => + getCollectionMetadata(client, params, options), + getContractsForOwner: (params, options) => + getContractsForOwner(client, params, options), + getCollectionsForOwner: (params, options) => + getCollectionsForOwner(client, params, options), + getOwnersForNft: (params, options) => + getOwnersForNft(client, params, options), + getOwnersForContract: (params, options) => + getOwnersForContract(client, params, options), + getNftSales: (params, options) => getNftSales(client, params, options), + getFloorPrice: (params, options) => + getFloorPrice(client, params, options), + searchContractMetadata: (params, options) => + searchContractMetadata(client, params, options), + getSpamContracts: (params, options) => + getSpamContracts(client, params, options), + isSpamContract: (params, options) => + isSpamContract(client, params, options), + isAirdropNft: (params, options) => isAirdropNft(client, params, options), + isHolderOfContract: (params, options) => + isHolderOfContract(client, params, options), + computeRarity: (params, options) => + computeRarity(client, params, options), + summarizeNftAttributes: (params, options) => + summarizeNftAttributes(client, params, options), + }, + token: { + getTokenBalances: (params) => getTokenBalances(client, params), + getTokenMetadata: (params) => getTokenMetadata(client, params), + getTokenAllowance: (params) => getTokenAllowance(client, params), }, transfers: { getAssetTransfers: (params) => getAssetTransfers(client, params), diff --git a/packages/data-apis/src/generated/rest/nft.schema.ts b/packages/data-apis/src/generated/rest/nft.schema.ts index c52bf3c614..624b45287b 100644 --- a/packages/data-apis/src/generated/rest/nft.schema.ts +++ b/packages/data-apis/src/generated/rest/nft.schema.ts @@ -15,6 +15,181 @@ export type GetNftsForOwnerQuery = NonNullable< operations["getNFTsForOwner-v3"]["parameters"]["query"] >; +/** 200 response for getNFTsForContract-v3. */ +export type GetNftsForContractResponse = + operations["getNFTsForContract-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for getNFTsForContract-v3. */ +export type GetNftsForContractQuery = NonNullable< + operations["getNFTsForContract-v3"]["parameters"]["query"] +>; + +/** 200 response for getNFTsForCollection-v3. */ +export type GetNftsForCollectionResponse = + operations["getNFTsForCollection-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for getNFTsForCollection-v3. */ +export type GetNftsForCollectionQuery = NonNullable< + operations["getNFTsForCollection-v3"]["parameters"]["query"] +>; + +/** 200 response for getNFTMetadata-v3. */ +export type GetNftMetadataResponse = + operations["getNFTMetadata-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for getNFTMetadata-v3. */ +export type GetNftMetadataQuery = NonNullable< + operations["getNFTMetadata-v3"]["parameters"]["query"] +>; + +/** Request body for getNFTMetadataBatch-v3. */ +export type GetNftMetadataBatchBody = NonNullable< + operations["getNFTMetadataBatch-v3"]["requestBody"] +>["content"]["application/json"]; + +/** 200 response for getNFTMetadataBatch-v3. */ +export type GetNftMetadataBatchResponse = + operations["getNFTMetadataBatch-v3"]["responses"]["200"]["content"]["application/json"]; + +/** 200 response for getContractMetadata-v3. */ +export type GetContractMetadataResponse = + operations["getContractMetadata-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for getContractMetadata-v3. */ +export type GetContractMetadataQuery = NonNullable< + operations["getContractMetadata-v3"]["parameters"]["query"] +>; + +/** Request body for getContractMetadataBatch-v3. */ +export type GetContractMetadataBatchBody = NonNullable< + operations["getContractMetadataBatch-v3"]["requestBody"] +>["content"]["application/json"]; + +/** 200 response for getContractMetadataBatch-v3. */ +export type GetContractMetadataBatchResponse = + operations["getContractMetadataBatch-v3"]["responses"]["200"]["content"]["application/json"]; + +/** 200 response for getCollectionMetadata-v3. */ +export type GetCollectionMetadataResponse = + operations["getCollectionMetadata-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for getCollectionMetadata-v3. */ +export type GetCollectionMetadataQuery = NonNullable< + operations["getCollectionMetadata-v3"]["parameters"]["query"] +>; + +/** 200 response for getContractsForOwner-v3. */ +export type GetContractsForOwnerResponse = + operations["getContractsForOwner-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for getContractsForOwner-v3. */ +export type GetContractsForOwnerQuery = NonNullable< + operations["getContractsForOwner-v3"]["parameters"]["query"] +>; + +/** 200 response for getCollectionsForOwner-v3. */ +export type GetCollectionsForOwnerResponse = + operations["getCollectionsForOwner-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for getCollectionsForOwner-v3. */ +export type GetCollectionsForOwnerQuery = NonNullable< + operations["getCollectionsForOwner-v3"]["parameters"]["query"] +>; + +/** 200 response for getOwnersForNFT-v3. */ +export type GetOwnersForNftResponse = + operations["getOwnersForNFT-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for getOwnersForNFT-v3. */ +export type GetOwnersForNftQuery = NonNullable< + operations["getOwnersForNFT-v3"]["parameters"]["query"] +>; + +/** 200 response for getOwnersForContract-v3. */ +export type GetOwnersForContractResponse = + operations["getOwnersForContract-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for getOwnersForContract-v3. */ +export type GetOwnersForContractQuery = NonNullable< + operations["getOwnersForContract-v3"]["parameters"]["query"] +>; + +/** 200 response for getNFTSales-v3. */ +export type GetNftSalesResponse = + operations["getNFTSales-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for getNFTSales-v3. */ +export type GetNftSalesQuery = NonNullable< + operations["getNFTSales-v3"]["parameters"]["query"] +>; + +/** 200 response for getFloorPrice-v3. */ +export type GetFloorPriceResponse = + operations["getFloorPrice-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for getFloorPrice-v3. */ +export type GetFloorPriceQuery = NonNullable< + operations["getFloorPrice-v3"]["parameters"]["query"] +>; + +/** 200 response for searchContractMetadata-v3. */ +export type SearchContractMetadataResponse = + operations["searchContractMetadata-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for searchContractMetadata-v3. */ +export type SearchContractMetadataQuery = NonNullable< + operations["searchContractMetadata-v3"]["parameters"]["query"] +>; + +/** 200 response for getSpamContracts-v3. */ +export type GetSpamContractsResponse = + operations["getSpamContracts-v3"]["responses"]["200"]["content"]["application/json"]; + +/** 200 response for isSpamContract-v3. */ +export type IsSpamContractResponse = + operations["isSpamContract-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for isSpamContract-v3. */ +export type IsSpamContractQuery = NonNullable< + operations["isSpamContract-v3"]["parameters"]["query"] +>; + +/** 200 response for isAirdropNFT-v3. */ +export type IsAirdropNftResponse = + operations["isAirdropNFT-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for isAirdropNFT-v3. */ +export type IsAirdropNftQuery = NonNullable< + operations["isAirdropNFT-v3"]["parameters"]["query"] +>; + +/** 200 response for isHolderOfContract-v3. */ +export type IsHolderOfContractResponse = + operations["isHolderOfContract-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for isHolderOfContract-v3. */ +export type IsHolderOfContractQuery = NonNullable< + operations["isHolderOfContract-v3"]["parameters"]["query"] +>; + +/** 200 response for computeRarity-v3. */ +export type ComputeRarityResponse = + operations["computeRarity-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for computeRarity-v3. */ +export type ComputeRarityQuery = NonNullable< + operations["computeRarity-v3"]["parameters"]["query"] +>; + +/** 200 response for summarizeNFTAttributes-v3. */ +export type SummarizeNftAttributesResponse = + operations["summarizeNFTAttributes-v3"]["responses"]["200"]["content"]["application/json"]; + +/** Query params for summarizeNFTAttributes-v3. */ +export type SummarizeNftAttributesQuery = NonNullable< + operations["summarizeNFTAttributes-v3"]["parameters"]["query"] +>; + /** RestRequestSchema entries for the nft REST API. */ export type NftRestSchema = readonly [ { @@ -25,6 +200,166 @@ export type NftRestSchema = readonly [ Query: GetNftsForOwnerQuery; Response: GetNftsForOwnerResponse; }, + { + /** GET /v3/{apiKey}/getNFTsForContract (operationId: getNFTsForContract-v3) */ + Route: "getNFTsForContract"; + Method: "GET"; + Body?: undefined; + Query: GetNftsForContractQuery; + Response: GetNftsForContractResponse; + }, + { + /** GET /v3/{apiKey}/getNFTsForCollection (operationId: getNFTsForCollection-v3) */ + Route: "getNFTsForCollection"; + Method: "GET"; + Body?: undefined; + Query?: GetNftsForCollectionQuery | undefined; + Response: GetNftsForCollectionResponse; + }, + { + /** GET /v3/{apiKey}/getNFTMetadata (operationId: getNFTMetadata-v3) */ + Route: "getNFTMetadata"; + Method: "GET"; + Body?: undefined; + Query: GetNftMetadataQuery; + Response: GetNftMetadataResponse; + }, + { + /** POST /v3/{apiKey}/getNFTMetadataBatch (operationId: getNFTMetadataBatch-v3) */ + Route: "getNFTMetadataBatch"; + Method: "POST"; + Body: GetNftMetadataBatchBody; + Query?: undefined; + Response: GetNftMetadataBatchResponse; + }, + { + /** GET /v3/{apiKey}/getContractMetadata (operationId: getContractMetadata-v3) */ + Route: "getContractMetadata"; + Method: "GET"; + Body?: undefined; + Query: GetContractMetadataQuery; + Response: GetContractMetadataResponse; + }, + { + /** POST /v3/{apiKey}/getContractMetadataBatch (operationId: getContractMetadataBatch-v3) */ + Route: "getContractMetadataBatch"; + Method: "POST"; + Body: GetContractMetadataBatchBody; + Query?: undefined; + Response: GetContractMetadataBatchResponse; + }, + { + /** GET /v3/{apiKey}/getCollectionMetadata (operationId: getCollectionMetadata-v3) */ + Route: "getCollectionMetadata"; + Method: "GET"; + Body?: undefined; + Query: GetCollectionMetadataQuery; + Response: GetCollectionMetadataResponse; + }, + { + /** GET /v3/{apiKey}/getContractsForOwner (operationId: getContractsForOwner-v3) */ + Route: "getContractsForOwner"; + Method: "GET"; + Body?: undefined; + Query: GetContractsForOwnerQuery; + Response: GetContractsForOwnerResponse; + }, + { + /** GET /v3/{apiKey}/getCollectionsForOwner (operationId: getCollectionsForOwner-v3) */ + Route: "getCollectionsForOwner"; + Method: "GET"; + Body?: undefined; + Query: GetCollectionsForOwnerQuery; + Response: GetCollectionsForOwnerResponse; + }, + { + /** GET /v3/{apiKey}/getOwnersForNFT (operationId: getOwnersForNFT-v3) */ + Route: "getOwnersForNFT"; + Method: "GET"; + Body?: undefined; + Query: GetOwnersForNftQuery; + Response: GetOwnersForNftResponse; + }, + { + /** GET /v3/{apiKey}/getOwnersForContract (operationId: getOwnersForContract-v3) */ + Route: "getOwnersForContract"; + Method: "GET"; + Body?: undefined; + Query: GetOwnersForContractQuery; + Response: GetOwnersForContractResponse; + }, + { + /** GET /v3/{apiKey}/getNFTSales (operationId: getNFTSales-v3) */ + Route: "getNFTSales"; + Method: "GET"; + Body?: undefined; + Query?: GetNftSalesQuery | undefined; + Response: GetNftSalesResponse; + }, + { + /** GET /v3/{apiKey}/getFloorPrice (operationId: getFloorPrice-v3) */ + Route: "getFloorPrice"; + Method: "GET"; + Body?: undefined; + Query: GetFloorPriceQuery; + Response: GetFloorPriceResponse; + }, + { + /** GET /v3/{apiKey}/searchContractMetadata (operationId: searchContractMetadata-v3) */ + Route: "searchContractMetadata"; + Method: "GET"; + Body?: undefined; + Query: SearchContractMetadataQuery; + Response: SearchContractMetadataResponse; + }, + { + /** GET /v3/{apiKey}/getSpamContracts (operationId: getSpamContracts-v3) */ + Route: "getSpamContracts"; + Method: "GET"; + Body?: undefined; + Query?: undefined; + Response: GetSpamContractsResponse; + }, + { + /** GET /v3/{apiKey}/isSpamContract (operationId: isSpamContract-v3) */ + Route: "isSpamContract"; + Method: "GET"; + Body?: undefined; + Query: IsSpamContractQuery; + Response: IsSpamContractResponse; + }, + { + /** GET /v3/{apiKey}/isAirdropNFT (operationId: isAirdropNFT-v3) */ + Route: "isAirdropNFT"; + Method: "GET"; + Body?: undefined; + Query: IsAirdropNftQuery; + Response: IsAirdropNftResponse; + }, + { + /** GET /v3/{apiKey}/isHolderOfContract (operationId: isHolderOfContract-v3) */ + Route: "isHolderOfContract"; + Method: "GET"; + Body?: undefined; + Query: IsHolderOfContractQuery; + Response: IsHolderOfContractResponse; + }, + { + /** GET /v3/{apiKey}/computeRarity (operationId: computeRarity-v3) */ + Route: "computeRarity"; + Method: "GET"; + Body?: undefined; + Query: ComputeRarityQuery; + Response: ComputeRarityResponse; + }, + { + /** GET /v3/{apiKey}/summarizeNFTAttributes (operationId: summarizeNFTAttributes-v3) */ + Route: "summarizeNFTAttributes"; + Method: "GET"; + Body?: undefined; + Query: SummarizeNftAttributesQuery; + Response: SummarizeNftAttributesResponse; + }, ]; /** Compile-time guard that the emitted tuple satisfies the shared constraint. */ diff --git a/packages/data-apis/src/generated/rest/portfolio.schema.ts b/packages/data-apis/src/generated/rest/portfolio.schema.ts index a2860547d4..3bfdc519b7 100644 --- a/packages/data-apis/src/generated/rest/portfolio.schema.ts +++ b/packages/data-apis/src/generated/rest/portfolio.schema.ts @@ -15,6 +15,33 @@ export type GetTokensByAddressBody = NonNullable< export type GetTokensByAddressResponse = operations["get-tokens-by-address"]["responses"]["200"]["content"]["application/json"]; +/** Request body for get-token-balances-by-address. */ +export type GetTokenBalancesByAddressBody = NonNullable< + operations["get-token-balances-by-address"]["requestBody"] +>["content"]["application/json"]; + +/** 200 response for get-token-balances-by-address. */ +export type GetTokenBalancesByAddressResponse = + operations["get-token-balances-by-address"]["responses"]["200"]["content"]["application/json"]; + +/** Request body for get-nfts-by-address. */ +export type GetNftsByAddressBody = NonNullable< + operations["get-nfts-by-address"]["requestBody"] +>["content"]["application/json"]; + +/** 200 response for get-nfts-by-address. */ +export type GetNftsByAddressResponse = + operations["get-nfts-by-address"]["responses"]["200"]["content"]["application/json"]; + +/** Request body for get-nft-contracts-by-address. */ +export type GetNftContractsByAddressBody = NonNullable< + operations["get-nft-contracts-by-address"]["requestBody"] +>["content"]["application/json"]; + +/** 200 response for get-nft-contracts-by-address. */ +export type GetNftContractsByAddressResponse = + operations["get-nft-contracts-by-address"]["responses"]["200"]["content"]["application/json"]; + /** RestRequestSchema entries for the portfolio REST API. */ export type PortfolioRestSchema = readonly [ { @@ -25,6 +52,30 @@ export type PortfolioRestSchema = readonly [ Query?: undefined; Response: GetTokensByAddressResponse; }, + { + /** POST /{apiKey}/assets/tokens/balances/by-address (operationId: get-token-balances-by-address) */ + Route: "assets/tokens/balances/by-address"; + Method: "POST"; + Body: GetTokenBalancesByAddressBody; + Query?: undefined; + Response: GetTokenBalancesByAddressResponse; + }, + { + /** POST /{apiKey}/assets/nfts/by-address (operationId: get-nfts-by-address) */ + Route: "assets/nfts/by-address"; + Method: "POST"; + Body: GetNftsByAddressBody; + Query?: undefined; + Response: GetNftsByAddressResponse; + }, + { + /** POST /{apiKey}/assets/nfts/contracts/by-address (operationId: get-nft-contracts-by-address) */ + Route: "assets/nfts/contracts/by-address"; + Method: "POST"; + Body: GetNftContractsByAddressBody; + Query?: undefined; + Response: GetNftContractsByAddressResponse; + }, ]; /** Compile-time guard that the emitted tuple satisfies the shared constraint. */ diff --git a/packages/data-apis/src/index.ts b/packages/data-apis/src/index.ts index 395254ca25..761cbffb7f 100644 --- a/packages/data-apis/src/index.ts +++ b/packages/data-apis/src/index.ts @@ -6,14 +6,50 @@ export { createDataClient } from "./client.js"; export type { DataActions } from "./decorator.js"; export { dataActions } from "./decorator.js"; +// per-request options +export type { RequestOptions } from "./internal/clientHelpers.js"; + // actions (individually importable for tree-shaking / composability) export { getTokensByAddress } from "./actions/portfolio/getTokensByAddress.js"; +export { getTokenBalancesByAddress } from "./actions/portfolio/getTokenBalancesByAddress.js"; +export { getNftsByAddress } from "./actions/portfolio/getNftsByAddress.js"; +export { getNftContractsByAddress } from "./actions/portfolio/getNftContractsByAddress.js"; +export { getTokenPricesBySymbol } from "./actions/prices/getTokenPricesBySymbol.js"; +export { getTokenPricesByAddress } from "./actions/prices/getTokenPricesByAddress.js"; +export { getHistoricalTokenPrices } from "./actions/prices/getHistoricalTokenPrices.js"; export { getNftsForOwner } from "./actions/nft/getNftsForOwner.js"; +export { getNftsForContract } from "./actions/nft/getNftsForContract.js"; +export { getNftsForCollection } from "./actions/nft/getNftsForCollection.js"; +export { getNftMetadata } from "./actions/nft/getNftMetadata.js"; +export { getNftMetadataBatch } from "./actions/nft/getNftMetadataBatch.js"; +export { getContractMetadata } from "./actions/nft/getContractMetadata.js"; +export { getContractMetadataBatch } from "./actions/nft/getContractMetadataBatch.js"; +export { getCollectionMetadata } from "./actions/nft/getCollectionMetadata.js"; +export { getContractsForOwner } from "./actions/nft/getContractsForOwner.js"; +export { getCollectionsForOwner } from "./actions/nft/getCollectionsForOwner.js"; +export { getOwnersForNft } from "./actions/nft/getOwnersForNft.js"; +export { getOwnersForContract } from "./actions/nft/getOwnersForContract.js"; +export { getNftSales } from "./actions/nft/getNftSales.js"; +export { getFloorPrice } from "./actions/nft/getFloorPrice.js"; +export { searchContractMetadata } from "./actions/nft/searchContractMetadata.js"; +export { getSpamContracts } from "./actions/nft/getSpamContracts.js"; +export { isSpamContract } from "./actions/nft/isSpamContract.js"; +export { isAirdropNft } from "./actions/nft/isAirdropNft.js"; +export { isHolderOfContract } from "./actions/nft/isHolderOfContract.js"; +export { computeRarity } from "./actions/nft/computeRarity.js"; +export { summarizeNftAttributes } from "./actions/nft/summarizeNftAttributes.js"; +export { getTokenBalances } from "./actions/token/getTokenBalances.js"; +export { getTokenMetadata } from "./actions/token/getTokenMetadata.js"; +export { getTokenAllowance } from "./actions/token/getTokenAllowance.js"; export { getAssetTransfers } from "./actions/transfers/getAssetTransfers.js"; // schemas export type { DataRpcSchema } from "./schema/rpc.js"; -export type { PortfolioRestSchema, NftRestSchema } from "./schema/rest.js"; +export type { + NftRestSchema, + PortfolioRestSchema, + PricesRestSchema, +} from "./schema/rest.js"; // types export type * from "./types.js"; diff --git a/packages/data-apis/src/internal/clientHelpers.ts b/packages/data-apis/src/internal/clientHelpers.ts index 7702afbc55..c90abde4fd 100644 --- a/packages/data-apis/src/internal/clientHelpers.ts +++ b/packages/data-apis/src/internal/clientHelpers.ts @@ -1,5 +1,6 @@ import { AlchemyRestClient, + alchemyTransport, resolveNetwork, type AlchemyTransport, type AlchemyTransportConfig, @@ -8,7 +9,9 @@ import { type RestRequestSchema, } from "@alchemy/common"; import { BaseError } from "@alchemy/common"; -import type { Chain, Client } from "viem"; +import type { Chain, Client, EIP1193RequestFn } from "viem"; +import { getRpcUrl } from "./endpoints.js"; +import type { DataRpcSchema } from "../schema/rpc.js"; /** The minimal client shape data actions operate on. */ export type DataClient = Client; @@ -78,3 +81,28 @@ export function getRestClient( const { apiKey, jwt } = getTransportConfig(client); return new AlchemyRestClient({ apiKey, jwt, url }); } + +/** + * Resolves the JSON-RPC request function for an action: the client's own + * transport when no override is given, otherwise a transport instance derived + * from the client's transport config and pointed at the override network's + * RPC URL (the same mechanism the transport's tracing support uses). + * + * @param {DataClient} client The client whose transport (and config) is used + * @param {NetworkInput} [network] Optional per-request network override + * @returns {EIP1193RequestFn} A typed JSON-RPC request function + */ +export function getRpcRequest( + client: DataClient, + network?: NetworkInput, +): EIP1193RequestFn { + if (!network) { + return client.request as EIP1193RequestFn; + } + const { slug } = resolveRequestNetwork(client, network); + const derived = alchemyTransport({ + ...getTransportConfig(client), + url: getRpcUrl(slug), + })({ retryCount: 0 }); + return derived.request as EIP1193RequestFn; +} diff --git a/packages/data-apis/src/internal/endpoints.ts b/packages/data-apis/src/internal/endpoints.ts index a870a9182f..8025b38e6b 100644 --- a/packages/data-apis/src/internal/endpoints.ts +++ b/packages/data-apis/src/internal/endpoints.ts @@ -4,6 +4,9 @@ /** Global, chain-agnostic Data API (multi-network request bodies). */ export const DATA_API_URL = "https://api.g.alchemy.com/data/v1"; +/** Global Prices API (chain-agnostic or networks in the request body). */ +export const PRICES_API_URL = "https://api.g.alchemy.com/prices/v1"; + /** * Network-scoped NFT v3 base URL. * diff --git a/packages/data-apis/src/internal/query.ts b/packages/data-apis/src/internal/query.ts new file mode 100644 index 0000000000..1db0f2bdd5 --- /dev/null +++ b/packages/data-apis/src/internal/query.ts @@ -0,0 +1,19 @@ +/** + * Restores wire-format bracketed keys ("contractAddresses[]") that the public + * params surface exposes unbracketed (the inverse of the `Unbracket` mapped + * type in types.ts). Keys not listed pass through untouched. + * + * @param {Record} params The unbracketed params object + * @param {string[]} arrayKeys Keys to re-bracket + * @returns {Record} The wire-format query object + */ +export function bracketArrayKeys( + params: Record, + arrayKeys: readonly string[], +): Record { + const out: Record = {}; + for (const [key, value] of Object.entries(params)) { + out[arrayKeys.includes(key) ? `${key}[]` : key] = value; + } + return out; +} diff --git a/packages/data-apis/src/schema/rest.ts b/packages/data-apis/src/schema/rest.ts index a725eb72c5..ee5c5bc696 100644 --- a/packages/data-apis/src/schema/rest.ts +++ b/packages/data-apis/src/schema/rest.ts @@ -3,3 +3,4 @@ // stable regardless of how generation is organized internally. export type { NftRestSchema } from "../generated/rest/nft.schema.js"; export type { PortfolioRestSchema } from "../generated/rest/portfolio.schema.js"; +export type { PricesRestSchema } from "../generated/rest/prices.schema.js"; diff --git a/packages/data-apis/src/schema/rpc.ts b/packages/data-apis/src/schema/rpc.ts index fb3b5fec9d..510f728ce2 100644 --- a/packages/data-apis/src/schema/rpc.ts +++ b/packages/data-apis/src/schema/rpc.ts @@ -1,4 +1,8 @@ -import type { AlchemyGetAssetTransfersParams } from "../generated/rpc/transfers.js"; +import type { TokenRpcSchema } from "../generated/rpc/token.js"; +import type { + AlchemyGetAssetTransfersParams, + TransfersRpcSchema, +} from "../generated/rpc/transfers.js"; import type { GetAssetTransfersResult } from "../types.js"; // Params/result internals are generated by @alchemy/api-codegen from the docs @@ -10,13 +14,15 @@ export type GetAssetTransfersRpcParams = AlchemyGetAssetTransfersParams; * viem RpcSchema entries for the Data JSON-RPC methods. Attach to a client to * get typed `client.request({ method: "alchemy_getAssetTransfers", ... })`. * - * ReturnType uses the SDK's {@link GetAssetTransfersResult}, which collapses - * the spec's "Not Found (null)" string branch (a docs-spec artifact). + * The transfers ReturnType uses the SDK's {@link GetAssetTransfersResult}, + * which collapses the spec's "Not Found (null)" string branch (a docs-spec + * artifact). */ export type DataRpcSchema = [ { - Method: "alchemy_getAssetTransfers"; - Parameters: [AlchemyGetAssetTransfersParams]; + Method: TransfersRpcSchema[0]["Method"]; + Parameters: TransfersRpcSchema[0]["Parameters"]; ReturnType: GetAssetTransfersResult; }, + ...TokenRpcSchema, ]; diff --git a/packages/data-apis/src/types.ts b/packages/data-apis/src/types.ts index 6c848d7b1d..c50caef5e8 100644 --- a/packages/data-apis/src/types.ts +++ b/packages/data-apis/src/types.ts @@ -1,12 +1,75 @@ import type { NetworkInput } from "@alchemy/common"; import type { + ComputeRarityQuery, + ComputeRarityResponse, + GetCollectionMetadataQuery, + GetCollectionMetadataResponse, + GetCollectionsForOwnerQuery, + GetCollectionsForOwnerResponse, + GetContractMetadataBatchBody, + GetContractMetadataBatchResponse, + GetContractMetadataQuery, + GetContractMetadataResponse, + GetContractsForOwnerQuery, + GetContractsForOwnerResponse, + GetFloorPriceQuery, + GetFloorPriceResponse, + GetNftMetadataBatchBody, + GetNftMetadataBatchResponse, + GetNftMetadataQuery, + GetNftMetadataResponse, + GetNftSalesQuery, + GetNftSalesResponse, + GetNftsForCollectionQuery, + GetNftsForCollectionResponse, + GetNftsForContractQuery, + GetNftsForContractResponse, GetNftsForOwnerQuery, GetNftsForOwnerResponse, + GetOwnersForContractQuery, + GetOwnersForContractResponse, + GetOwnersForNftQuery, + GetOwnersForNftResponse, + GetSpamContractsResponse, + IsAirdropNftQuery, + IsAirdropNftResponse, + IsHolderOfContractQuery, + IsHolderOfContractResponse, + IsSpamContractQuery, + IsSpamContractResponse, + SearchContractMetadataQuery, + SearchContractMetadataResponse, + SummarizeNftAttributesQuery, + SummarizeNftAttributesResponse, } from "./generated/rest/nft.schema.js"; import type { + GetNftContractsByAddressBody, + GetNftContractsByAddressResponse, + GetNftsByAddressBody, + GetNftsByAddressResponse, + GetTokenBalancesByAddressBody, + GetTokenBalancesByAddressResponse, GetTokensByAddressBody, GetTokensByAddressResponse, } from "./generated/rest/portfolio.schema.js"; +import type { + GetHistoricalTokenPricesBody, + GetHistoricalTokenPricesResponse, + GetTokenPricesByAddressBody, + GetTokenPricesByAddressResponse, + GetTokenPricesBySymbolQuery, + GetTokenPricesBySymbolResponse, +} from "./generated/rest/prices.schema.js"; +import type { + AlchemyGetTokenAllowanceParams, + AlchemyGetTokenAllowanceResult, + AlchemyGetTokenBalancesAddressParam, + AlchemyGetTokenBalancesOptionsParam, + AlchemyGetTokenBalancesResult, + AlchemyGetTokenBalancesTokenSpecParam, + AlchemyGetTokenMetadataParams, + AlchemyGetTokenMetadataResult, +} from "./generated/rpc/token.js"; import type { AlchemyGetAssetTransfersParams, AlchemyGetAssetTransfersResult, @@ -18,6 +81,24 @@ import type { // re-exported directly: every public-surface change is a deliberate edit // here, even when the underlying spec moves. +/** Renames wire keys like "contractAddresses[]" to plain names; actions map them back. */ +type Unbracket = { + [K in keyof T as K extends `${infer Base}[]` ? Base : K]: T[K]; +}; + +/** Query params of a network-scoped method, plus the SDK's network override. */ +type NetworkScoped = Unbracket & { + /** Overrides the client-level network for this request. */ + network?: NetworkInput; +}; + +/** Distributes over a union, replacing wire `network: string` with NetworkInput. */ +type WithNetworkInput = T extends { network: string } + ? Omit & { network: NetworkInput } + : T; + +// ─── Portfolio (REST, global, multi-network) ──────────────────────────────── + /** An address paired with the networks to query it on. */ export interface PortfolioAddressEntry { address: string; @@ -26,38 +107,176 @@ export interface PortfolioAddressEntry { } /** - * Generated request body with the wire-format `addresses` (plain string - * networks) replaced by the SDK's three-format {@link PortfolioAddressEntry}. + * Replaces each wire-format address entry's `networks` (slug strings — an + * enum in some specs, deliberately widened here to support the SDK's + * escape-hatch slugs) with the SDK's three-format NetworkInput, preserving + * any other per-entry fields the operation defines (e.g. per-address + * include/exclude filters). */ -export type GetTokensByAddressParams = Omit< - GetTokensByAddressBody, +type PortfolioParams = Omit< + Body, "addresses" > & { - addresses: PortfolioAddressEntry[]; + addresses: Array< + Omit & { + /** Networks to query; accepts viem Chains, Alchemy slugs, or CAIP-2 ids. */ + networks: NetworkInput[]; + } + >; }; +export type GetTokensByAddressParams = PortfolioParams; export type GetTokensByAddressResult = GetTokensByAddressResponse; - export type PortfolioToken = NonNullable< GetTokensByAddressResponse["data"]["tokens"] >[number]; -/** - * Generated query params plus the SDK's network override; the wire's - * bracketed `contractAddresses[]` key is replaced with a plain array (the - * action serializes it back to the bracketed form). - */ -export type GetNftsForOwnerParams = Omit< - GetNftsForOwnerQuery, - "contractAddresses[]" +export type GetTokenBalancesByAddressParams = + PortfolioParams; +export type GetTokenBalancesByAddressResult = GetTokenBalancesByAddressResponse; + +export type GetNftsByAddressParams = PortfolioParams; +export type GetNftsByAddressResult = GetNftsByAddressResponse; + +export type GetNftContractsByAddressParams = + PortfolioParams; +export type GetNftContractsByAddressResult = GetNftContractsByAddressResponse; + +// ─── Prices (REST, global) ─────────────────────────────────────────────────── + +/** Chain-agnostic: token symbols only, no network involved. */ +export type GetTokenPricesBySymbolParams = GetTokenPricesBySymbolQuery; +export type GetTokenPricesBySymbolResult = GetTokenPricesBySymbolResponse; + +/** A token address paired with the network it lives on. */ +export interface PriceAddressEntry { + address: string; + /** Accepts a viem Chain, an Alchemy slug, or a CAIP-2 id. */ + network: NetworkInput; +} + +export type GetTokenPricesByAddressParams = Omit< + GetTokenPricesByAddressBody, + "addresses" > & { + addresses: PriceAddressEntry[]; +}; +export type GetTokenPricesByAddressResult = GetTokenPricesByAddressResponse; + +export type GetHistoricalTokenPricesParams = + WithNetworkInput; +export type GetHistoricalTokenPricesResult = GetHistoricalTokenPricesResponse; + +// ─── NFT (REST, network-scoped) ────────────────────────────────────────────── + +export type GetNftsForOwnerParams = NetworkScoped; +export type GetNftsForOwnerResult = GetNftsForOwnerResponse; + +export type GetNftsForContractParams = NetworkScoped; +export type GetNftsForContractResult = GetNftsForContractResponse; + +export type GetNftsForCollectionParams = + NetworkScoped; +export type GetNftsForCollectionResult = GetNftsForCollectionResponse; + +export type GetNftMetadataParams = NetworkScoped; +export type GetNftMetadataResult = GetNftMetadataResponse; + +export type GetNftMetadataBatchParams = GetNftMetadataBatchBody & { /** Overrides the client-level network for this request. */ network?: NetworkInput; - /** Contract addresses to filter by (max 45). */ - contractAddresses?: string[]; }; +export type GetNftMetadataBatchResult = GetNftMetadataBatchResponse; -export type GetNftsForOwnerResult = GetNftsForOwnerResponse; +export type GetContractMetadataParams = NetworkScoped; +export type GetContractMetadataResult = GetContractMetadataResponse; + +export type GetContractMetadataBatchParams = GetContractMetadataBatchBody & { + /** Overrides the client-level network for this request. */ + network?: NetworkInput; +}; +export type GetContractMetadataBatchResult = GetContractMetadataBatchResponse; + +export type GetCollectionMetadataParams = + NetworkScoped; +export type GetCollectionMetadataResult = GetCollectionMetadataResponse; + +export type GetContractsForOwnerParams = + NetworkScoped; +export type GetContractsForOwnerResult = GetContractsForOwnerResponse; + +export type GetCollectionsForOwnerParams = + NetworkScoped; +export type GetCollectionsForOwnerResult = GetCollectionsForOwnerResponse; + +export type GetOwnersForNftParams = NetworkScoped; +export type GetOwnersForNftResult = GetOwnersForNftResponse; + +export type GetOwnersForContractParams = + NetworkScoped; +export type GetOwnersForContractResult = GetOwnersForContractResponse; + +export type GetNftSalesParams = NetworkScoped; +export type GetNftSalesResult = GetNftSalesResponse; + +export type GetFloorPriceParams = NetworkScoped; +export type GetFloorPriceResult = GetFloorPriceResponse; + +export type SearchContractMetadataParams = + NetworkScoped; +export type SearchContractMetadataResult = SearchContractMetadataResponse; + +export type GetSpamContractsParams = { + /** Overrides the client-level network for this request. */ + network?: NetworkInput; +}; +export type GetSpamContractsResult = GetSpamContractsResponse; + +export type IsSpamContractParams = NetworkScoped; +export type IsSpamContractResult = IsSpamContractResponse; + +export type IsAirdropNftParams = NetworkScoped; +export type IsAirdropNftResult = IsAirdropNftResponse; + +export type IsHolderOfContractParams = NetworkScoped; +export type IsHolderOfContractResult = IsHolderOfContractResponse; + +export type ComputeRarityParams = NetworkScoped; +export type ComputeRarityResult = ComputeRarityResponse; + +export type SummarizeNftAttributesParams = + NetworkScoped; +export type SummarizeNftAttributesResult = SummarizeNftAttributesResponse; + +// ─── Token (JSON-RPC, network-scoped) ──────────────────────────────────────── + +export type GetTokenBalancesParams = { + /** The address to fetch balances for. */ + address: AlchemyGetTokenBalancesAddressParam; + /** "erc20" | "NATIVE_TOKEN" | "DEFAULT_TOKENS" or an explicit contract list. */ + tokenSpec?: AlchemyGetTokenBalancesTokenSpecParam; + /** Paging options (pageKey/maxCount; only valid with the "erc20" spec). */ + options?: AlchemyGetTokenBalancesOptionsParam; + /** Overrides the client-level network for this request. */ + network?: NetworkInput; +}; +export type GetTokenBalancesResult = AlchemyGetTokenBalancesResult; + +export type GetTokenMetadataParams = { + /** The token contract address. */ + contractAddress: AlchemyGetTokenMetadataParams; + /** Overrides the client-level network for this request. */ + network?: NetworkInput; +}; +export type GetTokenMetadataResult = AlchemyGetTokenMetadataResult; + +export type GetTokenAllowanceParams = AlchemyGetTokenAllowanceParams & { + /** Overrides the client-level network for this request. */ + network?: NetworkInput; +}; +export type GetTokenAllowanceResult = AlchemyGetTokenAllowanceResult; + +// ─── Transfers (JSON-RPC, network-scoped) ──────────────────────────────────── /** Generated RPC params plus the SDK's network override. */ export type GetAssetTransfersParams = AlchemyGetAssetTransfersParams & { From 45d136e2f65eb486681c352a952cbe3eea779bd8 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Wed, 10 Jun 2026 11:04:30 -0400 Subject: [PATCH 10/20] feat(data-apis): pagination iterators and normalized errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - paginate(): shared async-generator driver yielding whole pages; stops on missing/empty cursors, throws on a repeated cursor (infinite-loop guard), supports AbortSignal + maxPages - 9 *Pages companion actions for every paginated method (nft owner/contract/ collection listings + sales, portfolio nfts/contracts by address, transfers), wired into the decorator namespaces; cursor wiring (pageKey vs startToken→pageKey/nextToken, body vs query vs rpc-param) follows the manifest's validated pagination metadata - wrapRpcError(): JSON-RPC failures normalize into AlchemyApiError (code/ status), with /v2/ and apiKey query credentials redacted from any URL-bearing text — keys never leak through error chains. REST is already normalized inside AlchemyRestClient. - 10 new tests (cursor walking, repeat-cursor guard, maxPages, consumer break, abort, redaction, error mapping) + a decorator-level paging test Co-Authored-By: Claude Fable 5 --- .../nft/getCollectionsForOwnerPages.ts | 34 ++++++ .../actions/nft/getContractsForOwnerPages.ts | 30 +++++ .../src/actions/nft/getNftSalesPages.ts | 27 +++++ .../actions/nft/getNftsForCollectionPages.ts | 34 ++++++ .../actions/nft/getNftsForContractPages.ts | 30 +++++ .../src/actions/nft/getNftsForOwnerPages.ts | 30 +++++ .../data-apis/src/actions/nft/nft.test.ts | 20 ++++ .../getNftContractsByAddressPages.ts | 34 ++++++ .../portfolio/getNftsByAddressPages.ts | 30 +++++ .../src/actions/token/getTokenAllowance.ts | 3 +- .../src/actions/token/getTokenBalances.ts | 3 +- .../src/actions/token/getTokenMetadata.ts | 3 +- .../actions/transfers/getAssetTransfers.ts | 3 +- .../transfers/getAssetTransfersPages.ts | 31 ++++++ packages/data-apis/src/decorator.ts | 66 +++++++++++ packages/data-apis/src/index.ts | 10 ++ .../data-apis/src/internal/errors.test.ts | 67 +++++++++++ packages/data-apis/src/internal/errors.ts | 46 ++++++++ .../data-apis/src/internal/paginate.test.ts | 104 ++++++++++++++++++ packages/data-apis/src/internal/paginate.ts | 55 +++++++++ 20 files changed, 656 insertions(+), 4 deletions(-) create mode 100644 packages/data-apis/src/actions/nft/getCollectionsForOwnerPages.ts create mode 100644 packages/data-apis/src/actions/nft/getContractsForOwnerPages.ts create mode 100644 packages/data-apis/src/actions/nft/getNftSalesPages.ts create mode 100644 packages/data-apis/src/actions/nft/getNftsForCollectionPages.ts create mode 100644 packages/data-apis/src/actions/nft/getNftsForContractPages.ts create mode 100644 packages/data-apis/src/actions/nft/getNftsForOwnerPages.ts create mode 100644 packages/data-apis/src/actions/portfolio/getNftContractsByAddressPages.ts create mode 100644 packages/data-apis/src/actions/portfolio/getNftsByAddressPages.ts create mode 100644 packages/data-apis/src/actions/transfers/getAssetTransfersPages.ts create mode 100644 packages/data-apis/src/internal/errors.test.ts create mode 100644 packages/data-apis/src/internal/errors.ts create mode 100644 packages/data-apis/src/internal/paginate.test.ts create mode 100644 packages/data-apis/src/internal/paginate.ts diff --git a/packages/data-apis/src/actions/nft/getCollectionsForOwnerPages.ts b/packages/data-apis/src/actions/nft/getCollectionsForOwnerPages.ts new file mode 100644 index 0000000000..5083f9a80f --- /dev/null +++ b/packages/data-apis/src/actions/nft/getCollectionsForOwnerPages.ts @@ -0,0 +1,34 @@ +import { paginate, type PaginateOptions } from "../../internal/paginate.js"; +import type { DataClient } from "../../internal/clientHelpers.js"; +import { getCollectionsForOwner } from "./getCollectionsForOwner.js"; +import type { + GetCollectionsForOwnerParams, + GetCollectionsForOwnerResult, +} from "../../types.js"; + +/** + * Auto-paginating companion to {@link getCollectionsForOwner}: yields whole pages until the + * cursor is exhausted (or `maxPages` is hit), guarding against repeated + * cursors. Iterate items with an inner loop over each page. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetCollectionsForOwnerParams} params Same params as getCollectionsForOwner (the cursor is managed for you) + * @param {PaginateOptions} [options] Abort signal and page cap + * @returns {AsyncGenerator} Pages, in order + */ +export function getCollectionsForOwnerPages( + client: DataClient, + params: GetCollectionsForOwnerParams, + options?: PaginateOptions, +): AsyncGenerator { + return paginate({ + fetchPage: (cursor, signal) => + getCollectionsForOwner( + client, + { ...params, pageKey: cursor }, + { signal }, + ), + nextCursor: (page) => page.pageKey, + options, + }); +} diff --git a/packages/data-apis/src/actions/nft/getContractsForOwnerPages.ts b/packages/data-apis/src/actions/nft/getContractsForOwnerPages.ts new file mode 100644 index 0000000000..12ac4d3737 --- /dev/null +++ b/packages/data-apis/src/actions/nft/getContractsForOwnerPages.ts @@ -0,0 +1,30 @@ +import { paginate, type PaginateOptions } from "../../internal/paginate.js"; +import type { DataClient } from "../../internal/clientHelpers.js"; +import { getContractsForOwner } from "./getContractsForOwner.js"; +import type { + GetContractsForOwnerParams, + GetContractsForOwnerResult, +} from "../../types.js"; + +/** + * Auto-paginating companion to {@link getContractsForOwner}: yields whole pages until the + * cursor is exhausted (or `maxPages` is hit), guarding against repeated + * cursors. Iterate items with an inner loop over each page. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetContractsForOwnerParams} params Same params as getContractsForOwner (the cursor is managed for you) + * @param {PaginateOptions} [options] Abort signal and page cap + * @returns {AsyncGenerator} Pages, in order + */ +export function getContractsForOwnerPages( + client: DataClient, + params: GetContractsForOwnerParams, + options?: PaginateOptions, +): AsyncGenerator { + return paginate({ + fetchPage: (cursor, signal) => + getContractsForOwner(client, { ...params, pageKey: cursor }, { signal }), + nextCursor: (page) => page.pageKey, + options, + }); +} diff --git a/packages/data-apis/src/actions/nft/getNftSalesPages.ts b/packages/data-apis/src/actions/nft/getNftSalesPages.ts new file mode 100644 index 0000000000..9666fecbf4 --- /dev/null +++ b/packages/data-apis/src/actions/nft/getNftSalesPages.ts @@ -0,0 +1,27 @@ +import { paginate, type PaginateOptions } from "../../internal/paginate.js"; +import type { DataClient } from "../../internal/clientHelpers.js"; +import { getNftSales } from "./getNftSales.js"; +import type { GetNftSalesParams, GetNftSalesResult } from "../../types.js"; + +/** + * Auto-paginating companion to {@link getNftSales}: yields whole pages until the + * cursor is exhausted (or `maxPages` is hit), guarding against repeated + * cursors. Iterate items with an inner loop over each page. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftSalesParams} params Same params as getNftSales (the cursor is managed for you) + * @param {PaginateOptions} [options] Abort signal and page cap + * @returns {AsyncGenerator} Pages, in order + */ +export function getNftSalesPages( + client: DataClient, + params: GetNftSalesParams, + options?: PaginateOptions, +): AsyncGenerator { + return paginate({ + fetchPage: (cursor, signal) => + getNftSales(client, { ...params, pageKey: cursor }, { signal }), + nextCursor: (page) => page.pageKey, + options, + }); +} diff --git a/packages/data-apis/src/actions/nft/getNftsForCollectionPages.ts b/packages/data-apis/src/actions/nft/getNftsForCollectionPages.ts new file mode 100644 index 0000000000..36690396fb --- /dev/null +++ b/packages/data-apis/src/actions/nft/getNftsForCollectionPages.ts @@ -0,0 +1,34 @@ +import { paginate, type PaginateOptions } from "../../internal/paginate.js"; +import type { DataClient } from "../../internal/clientHelpers.js"; +import { getNftsForCollection } from "./getNftsForCollection.js"; +import type { + GetNftsForCollectionParams, + GetNftsForCollectionResult, +} from "../../types.js"; + +/** + * Auto-paginating companion to {@link getNftsForCollection}: yields whole pages until the + * cursor is exhausted (or `maxPages` is hit), guarding against repeated + * cursors. Iterate items with an inner loop over each page. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftsForCollectionParams} params Same params as getNftsForCollection (the cursor is managed for you) + * @param {PaginateOptions} [options] Abort signal and page cap + * @returns {AsyncGenerator} Pages, in order + */ +export function getNftsForCollectionPages( + client: DataClient, + params: GetNftsForCollectionParams, + options?: PaginateOptions, +): AsyncGenerator { + return paginate({ + fetchPage: (cursor, signal) => + getNftsForCollection( + client, + { ...params, startToken: cursor }, + { signal }, + ), + nextCursor: (page) => page.nextToken, + options, + }); +} diff --git a/packages/data-apis/src/actions/nft/getNftsForContractPages.ts b/packages/data-apis/src/actions/nft/getNftsForContractPages.ts new file mode 100644 index 0000000000..9a01140267 --- /dev/null +++ b/packages/data-apis/src/actions/nft/getNftsForContractPages.ts @@ -0,0 +1,30 @@ +import { paginate, type PaginateOptions } from "../../internal/paginate.js"; +import type { DataClient } from "../../internal/clientHelpers.js"; +import { getNftsForContract } from "./getNftsForContract.js"; +import type { + GetNftsForContractParams, + GetNftsForContractResult, +} from "../../types.js"; + +/** + * Auto-paginating companion to {@link getNftsForContract}: yields whole pages until the + * cursor is exhausted (or `maxPages` is hit), guarding against repeated + * cursors. Iterate items with an inner loop over each page. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftsForContractParams} params Same params as getNftsForContract (the cursor is managed for you) + * @param {PaginateOptions} [options] Abort signal and page cap + * @returns {AsyncGenerator} Pages, in order + */ +export function getNftsForContractPages( + client: DataClient, + params: GetNftsForContractParams, + options?: PaginateOptions, +): AsyncGenerator { + return paginate({ + fetchPage: (cursor, signal) => + getNftsForContract(client, { ...params, startToken: cursor }, { signal }), + nextCursor: (page) => page.pageKey, + options, + }); +} diff --git a/packages/data-apis/src/actions/nft/getNftsForOwnerPages.ts b/packages/data-apis/src/actions/nft/getNftsForOwnerPages.ts new file mode 100644 index 0000000000..d8621d7fbd --- /dev/null +++ b/packages/data-apis/src/actions/nft/getNftsForOwnerPages.ts @@ -0,0 +1,30 @@ +import { paginate, type PaginateOptions } from "../../internal/paginate.js"; +import type { DataClient } from "../../internal/clientHelpers.js"; +import { getNftsForOwner } from "./getNftsForOwner.js"; +import type { + GetNftsForOwnerParams, + GetNftsForOwnerResult, +} from "../../types.js"; + +/** + * Auto-paginating companion to {@link getNftsForOwner}: yields whole pages until the + * cursor is exhausted (or `maxPages` is hit), guarding against repeated + * cursors. Iterate items with an inner loop over each page. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftsForOwnerParams} params Same params as getNftsForOwner (the cursor is managed for you) + * @param {PaginateOptions} [options] Abort signal and page cap + * @returns {AsyncGenerator} Pages, in order + */ +export function getNftsForOwnerPages( + client: DataClient, + params: GetNftsForOwnerParams, + options?: PaginateOptions, +): AsyncGenerator { + return paginate({ + fetchPage: (cursor, signal) => + getNftsForOwner(client, { ...params, pageKey: cursor }, { signal }), + nextCursor: (page) => page.pageKey, + options, + }); +} diff --git a/packages/data-apis/src/actions/nft/nft.test.ts b/packages/data-apis/src/actions/nft/nft.test.ts index 6a2ed6bfce..4d0d2f8842 100644 --- a/packages/data-apis/src/actions/nft/nft.test.ts +++ b/packages/data-apis/src/actions/nft/nft.test.ts @@ -172,4 +172,24 @@ describe("nft namespace routing", () => { "https://base-mainnet.g.alchemy.com/nft/v3/getContractMetadata?contractAddress=0xc", ); }); + + it("getNftsForOwnerPages threads cursors through query params", async () => { + fetchMock + .mockImplementationOnce(async () => + jsonResponse({ ownedNfts: [{}], pageKey: "page-2" }), + ) + .mockImplementationOnce(async () => jsonResponse({ ownedNfts: [{}] })); + + const pages = []; + for await (const page of makeClient().nft.getNftsForOwnerPages({ + owner: "0xo", + })) { + pages.push(page); + } + expect(pages).toHaveLength(2); + const url1 = new URL(String(fetchMock.mock.calls[0]![0])); + const url2 = new URL(String(fetchMock.mock.calls[1]![0])); + expect(url1.searchParams.get("pageKey")).toBeNull(); + expect(url2.searchParams.get("pageKey")).toBe("page-2"); + }); }); diff --git a/packages/data-apis/src/actions/portfolio/getNftContractsByAddressPages.ts b/packages/data-apis/src/actions/portfolio/getNftContractsByAddressPages.ts new file mode 100644 index 0000000000..9e482ba75d --- /dev/null +++ b/packages/data-apis/src/actions/portfolio/getNftContractsByAddressPages.ts @@ -0,0 +1,34 @@ +import { paginate, type PaginateOptions } from "../../internal/paginate.js"; +import type { DataClient } from "../../internal/clientHelpers.js"; +import { getNftContractsByAddress } from "./getNftContractsByAddress.js"; +import type { + GetNftContractsByAddressParams, + GetNftContractsByAddressResult, +} from "../../types.js"; + +/** + * Auto-paginating companion to {@link getNftContractsByAddress}: yields whole pages until the + * cursor is exhausted (or `maxPages` is hit), guarding against repeated + * cursors. Iterate items with an inner loop over each page. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftContractsByAddressParams} params Same params as getNftContractsByAddress (the cursor is managed for you) + * @param {PaginateOptions} [options] Abort signal and page cap + * @returns {AsyncGenerator} Pages, in order + */ +export function getNftContractsByAddressPages( + client: DataClient, + params: GetNftContractsByAddressParams, + options?: PaginateOptions, +): AsyncGenerator { + return paginate({ + fetchPage: (cursor, signal) => + getNftContractsByAddress( + client, + { ...params, pageKey: cursor }, + { signal }, + ), + nextCursor: (page) => page.data.pageKey, + options, + }); +} diff --git a/packages/data-apis/src/actions/portfolio/getNftsByAddressPages.ts b/packages/data-apis/src/actions/portfolio/getNftsByAddressPages.ts new file mode 100644 index 0000000000..afba5134d4 --- /dev/null +++ b/packages/data-apis/src/actions/portfolio/getNftsByAddressPages.ts @@ -0,0 +1,30 @@ +import { paginate, type PaginateOptions } from "../../internal/paginate.js"; +import type { DataClient } from "../../internal/clientHelpers.js"; +import { getNftsByAddress } from "./getNftsByAddress.js"; +import type { + GetNftsByAddressParams, + GetNftsByAddressResult, +} from "../../types.js"; + +/** + * Auto-paginating companion to {@link getNftsByAddress}: yields whole pages until the + * cursor is exhausted (or `maxPages` is hit), guarding against repeated + * cursors. Iterate items with an inner loop over each page. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetNftsByAddressParams} params Same params as getNftsByAddress (the cursor is managed for you) + * @param {PaginateOptions} [options] Abort signal and page cap + * @returns {AsyncGenerator} Pages, in order + */ +export function getNftsByAddressPages( + client: DataClient, + params: GetNftsByAddressParams, + options?: PaginateOptions, +): AsyncGenerator { + return paginate({ + fetchPage: (cursor, signal) => + getNftsByAddress(client, { ...params, pageKey: cursor }, { signal }), + nextCursor: (page) => page.data.pageKey, + options, + }); +} diff --git a/packages/data-apis/src/actions/token/getTokenAllowance.ts b/packages/data-apis/src/actions/token/getTokenAllowance.ts index 67cb94315d..d4b311e30b 100644 --- a/packages/data-apis/src/actions/token/getTokenAllowance.ts +++ b/packages/data-apis/src/actions/token/getTokenAllowance.ts @@ -2,6 +2,7 @@ import { getRpcRequest, type DataClient, } from "../../internal/clientHelpers.js"; +import { wrapRpcError } from "../../internal/errors.js"; import type { GetTokenAllowanceParams, GetTokenAllowanceResult, @@ -26,5 +27,5 @@ export async function getTokenAllowance( return request({ method: "alchemy_getTokenAllowance", params: [allowanceRequest], - }); + }).catch(wrapRpcError); } diff --git a/packages/data-apis/src/actions/token/getTokenBalances.ts b/packages/data-apis/src/actions/token/getTokenBalances.ts index c303ef7742..1c49066af3 100644 --- a/packages/data-apis/src/actions/token/getTokenBalances.ts +++ b/packages/data-apis/src/actions/token/getTokenBalances.ts @@ -2,6 +2,7 @@ import { getRpcRequest, type DataClient, } from "../../internal/clientHelpers.js"; +import { wrapRpcError } from "../../internal/errors.js"; import type { TokenRpcSchema } from "../../generated/rpc/token.js"; import type { GetTokenBalancesParams, @@ -38,5 +39,5 @@ export async function getTokenBalances( return request({ method: "alchemy_getTokenBalances", params: rpcParams, - }); + }).catch(wrapRpcError); } diff --git a/packages/data-apis/src/actions/token/getTokenMetadata.ts b/packages/data-apis/src/actions/token/getTokenMetadata.ts index 7b9593fad4..5b04484e07 100644 --- a/packages/data-apis/src/actions/token/getTokenMetadata.ts +++ b/packages/data-apis/src/actions/token/getTokenMetadata.ts @@ -2,6 +2,7 @@ import { getRpcRequest, type DataClient, } from "../../internal/clientHelpers.js"; +import { wrapRpcError } from "../../internal/errors.js"; import type { GetTokenMetadataParams, GetTokenMetadataResult, @@ -26,5 +27,5 @@ export async function getTokenMetadata( return request({ method: "alchemy_getTokenMetadata", params: [contractAddress], - }); + }).catch(wrapRpcError); } diff --git a/packages/data-apis/src/actions/transfers/getAssetTransfers.ts b/packages/data-apis/src/actions/transfers/getAssetTransfers.ts index 796edad0ab..3a4aa02e38 100644 --- a/packages/data-apis/src/actions/transfers/getAssetTransfers.ts +++ b/packages/data-apis/src/actions/transfers/getAssetTransfers.ts @@ -2,6 +2,7 @@ import { getRpcRequest, type DataClient, } from "../../internal/clientHelpers.js"; +import { wrapRpcError } from "../../internal/errors.js"; import type { GetAssetTransfersParams, GetAssetTransfersResult, @@ -29,5 +30,5 @@ export async function getAssetTransfers( return request({ method: "alchemy_getAssetTransfers", params: [rpcParams], - }); + }).catch(wrapRpcError); } diff --git a/packages/data-apis/src/actions/transfers/getAssetTransfersPages.ts b/packages/data-apis/src/actions/transfers/getAssetTransfersPages.ts new file mode 100644 index 0000000000..e2ba8df0d7 --- /dev/null +++ b/packages/data-apis/src/actions/transfers/getAssetTransfersPages.ts @@ -0,0 +1,31 @@ +import { paginate, type PaginateOptions } from "../../internal/paginate.js"; +import type { DataClient } from "../../internal/clientHelpers.js"; +import { getAssetTransfers } from "./getAssetTransfers.js"; +import type { + GetAssetTransfersParams, + GetAssetTransfersResult, +} from "../../types.js"; + +/** + * Auto-paginating companion to {@link getAssetTransfers}: yields whole pages + * until the cursor is exhausted (or `maxPages` is hit), guarding against + * repeated cursors. Note: JSON-RPC requests run through the client's viem + * transport, which owns the fetch — the abort signal is honored between + * pages, not mid-request. + * + * @param {DataClient} client A client configured with an Alchemy transport + * @param {GetAssetTransfersParams} params Same params as getAssetTransfers (the cursor is managed for you) + * @param {PaginateOptions} [options] Abort signal (checked between pages) and page cap + * @returns {AsyncGenerator} Pages, in order + */ +export function getAssetTransfersPages( + client: DataClient, + params: GetAssetTransfersParams, + options?: PaginateOptions, +): AsyncGenerator { + return paginate({ + fetchPage: (pageKey) => getAssetTransfers(client, { ...params, pageKey }), + nextCursor: (page) => page.pageKey, + options, + }); +} diff --git a/packages/data-apis/src/decorator.ts b/packages/data-apis/src/decorator.ts index 2d736cc9f5..a1799532dc 100644 --- a/packages/data-apis/src/decorator.ts +++ b/packages/data-apis/src/decorator.ts @@ -1,10 +1,13 @@ import type { DataClient, RequestOptions } from "./internal/clientHelpers.js"; +import type { PaginateOptions } from "./internal/paginate.js"; import type * as T from "./types.js"; import { getTokensByAddress } from "./actions/portfolio/getTokensByAddress.js"; import { getTokenBalancesByAddress } from "./actions/portfolio/getTokenBalancesByAddress.js"; import { getNftsByAddress } from "./actions/portfolio/getNftsByAddress.js"; import { getNftContractsByAddress } from "./actions/portfolio/getNftContractsByAddress.js"; +import { getNftsByAddressPages } from "./actions/portfolio/getNftsByAddressPages.js"; +import { getNftContractsByAddressPages } from "./actions/portfolio/getNftContractsByAddressPages.js"; import { getTokenPricesBySymbol } from "./actions/prices/getTokenPricesBySymbol.js"; import { getTokenPricesByAddress } from "./actions/prices/getTokenPricesByAddress.js"; @@ -31,12 +34,19 @@ import { isAirdropNft } from "./actions/nft/isAirdropNft.js"; import { isHolderOfContract } from "./actions/nft/isHolderOfContract.js"; import { computeRarity } from "./actions/nft/computeRarity.js"; import { summarizeNftAttributes } from "./actions/nft/summarizeNftAttributes.js"; +import { getNftsForOwnerPages } from "./actions/nft/getNftsForOwnerPages.js"; +import { getNftsForContractPages } from "./actions/nft/getNftsForContractPages.js"; +import { getNftsForCollectionPages } from "./actions/nft/getNftsForCollectionPages.js"; +import { getContractsForOwnerPages } from "./actions/nft/getContractsForOwnerPages.js"; +import { getCollectionsForOwnerPages } from "./actions/nft/getCollectionsForOwnerPages.js"; +import { getNftSalesPages } from "./actions/nft/getNftSalesPages.js"; import { getTokenBalances } from "./actions/token/getTokenBalances.js"; import { getTokenMetadata } from "./actions/token/getTokenMetadata.js"; import { getTokenAllowance } from "./actions/token/getTokenAllowance.js"; import { getAssetTransfers } from "./actions/transfers/getAssetTransfers.js"; +import { getAssetTransfersPages } from "./actions/transfers/getAssetTransfersPages.js"; type Action = ( params: Params, @@ -45,6 +55,11 @@ type Action = ( type RpcAction = (params: Params) => Promise; +type PagesAction = ( + params: Params, + options?: PaginateOptions, +) => AsyncGenerator; + /** The namespaced Data API actions attached by the {@link dataActions} decorator. */ export type DataActions = { portfolio: { @@ -64,6 +79,14 @@ export type DataActions = { T.GetNftContractsByAddressParams, T.GetNftContractsByAddressResult >; + getNftsByAddressPages: PagesAction< + T.GetNftsByAddressParams, + T.GetNftsByAddressResult + >; + getNftContractsByAddressPages: PagesAction< + T.GetNftContractsByAddressParams, + T.GetNftContractsByAddressResult + >; }; prices: { getTokenPricesBySymbol: Action< @@ -140,6 +163,27 @@ export type DataActions = { T.SummarizeNftAttributesParams, T.SummarizeNftAttributesResult >; + getNftsForOwnerPages: PagesAction< + T.GetNftsForOwnerParams, + T.GetNftsForOwnerResult + >; + getNftsForContractPages: PagesAction< + T.GetNftsForContractParams, + T.GetNftsForContractResult + >; + getNftsForCollectionPages: PagesAction< + T.GetNftsForCollectionParams, + T.GetNftsForCollectionResult + >; + getContractsForOwnerPages: PagesAction< + T.GetContractsForOwnerParams, + T.GetContractsForOwnerResult + >; + getCollectionsForOwnerPages: PagesAction< + T.GetCollectionsForOwnerParams, + T.GetCollectionsForOwnerResult + >; + getNftSalesPages: PagesAction; }; token: { getTokenBalances: RpcAction< @@ -160,6 +204,10 @@ export type DataActions = { T.GetAssetTransfersParams, T.GetAssetTransfersResult >; + getAssetTransfersPages: PagesAction< + T.GetAssetTransfersParams, + T.GetAssetTransfersResult + >; }; }; @@ -196,6 +244,10 @@ export function dataActions(client: DataClient): DataActions { getNftsByAddress(client, params, options), getNftContractsByAddress: (params, options) => getNftContractsByAddress(client, params, options), + getNftsByAddressPages: (params, options) => + getNftsByAddressPages(client, params, options), + getNftContractsByAddressPages: (params, options) => + getNftContractsByAddressPages(client, params, options), }, prices: { getTokenPricesBySymbol: (params, options) => @@ -246,6 +298,18 @@ export function dataActions(client: DataClient): DataActions { computeRarity(client, params, options), summarizeNftAttributes: (params, options) => summarizeNftAttributes(client, params, options), + getNftsForOwnerPages: (params, options) => + getNftsForOwnerPages(client, params, options), + getNftsForContractPages: (params, options) => + getNftsForContractPages(client, params, options), + getNftsForCollectionPages: (params, options) => + getNftsForCollectionPages(client, params, options), + getContractsForOwnerPages: (params, options) => + getContractsForOwnerPages(client, params, options), + getCollectionsForOwnerPages: (params, options) => + getCollectionsForOwnerPages(client, params, options), + getNftSalesPages: (params, options) => + getNftSalesPages(client, params, options), }, token: { getTokenBalances: (params) => getTokenBalances(client, params), @@ -254,6 +318,8 @@ export function dataActions(client: DataClient): DataActions { }, transfers: { getAssetTransfers: (params) => getAssetTransfers(client, params), + getAssetTransfersPages: (params, options) => + getAssetTransfersPages(client, params, options), }, }; } diff --git a/packages/data-apis/src/index.ts b/packages/data-apis/src/index.ts index 761cbffb7f..d815e9895b 100644 --- a/packages/data-apis/src/index.ts +++ b/packages/data-apis/src/index.ts @@ -8,12 +8,15 @@ export { dataActions } from "./decorator.js"; // per-request options export type { RequestOptions } from "./internal/clientHelpers.js"; +export type { PaginateOptions } from "./internal/paginate.js"; // actions (individually importable for tree-shaking / composability) export { getTokensByAddress } from "./actions/portfolio/getTokensByAddress.js"; export { getTokenBalancesByAddress } from "./actions/portfolio/getTokenBalancesByAddress.js"; export { getNftsByAddress } from "./actions/portfolio/getNftsByAddress.js"; export { getNftContractsByAddress } from "./actions/portfolio/getNftContractsByAddress.js"; +export { getNftsByAddressPages } from "./actions/portfolio/getNftsByAddressPages.js"; +export { getNftContractsByAddressPages } from "./actions/portfolio/getNftContractsByAddressPages.js"; export { getTokenPricesBySymbol } from "./actions/prices/getTokenPricesBySymbol.js"; export { getTokenPricesByAddress } from "./actions/prices/getTokenPricesByAddress.js"; export { getHistoricalTokenPrices } from "./actions/prices/getHistoricalTokenPrices.js"; @@ -38,10 +41,17 @@ export { isAirdropNft } from "./actions/nft/isAirdropNft.js"; export { isHolderOfContract } from "./actions/nft/isHolderOfContract.js"; export { computeRarity } from "./actions/nft/computeRarity.js"; export { summarizeNftAttributes } from "./actions/nft/summarizeNftAttributes.js"; +export { getNftsForOwnerPages } from "./actions/nft/getNftsForOwnerPages.js"; +export { getNftsForContractPages } from "./actions/nft/getNftsForContractPages.js"; +export { getNftsForCollectionPages } from "./actions/nft/getNftsForCollectionPages.js"; +export { getContractsForOwnerPages } from "./actions/nft/getContractsForOwnerPages.js"; +export { getCollectionsForOwnerPages } from "./actions/nft/getCollectionsForOwnerPages.js"; +export { getNftSalesPages } from "./actions/nft/getNftSalesPages.js"; export { getTokenBalances } from "./actions/token/getTokenBalances.js"; export { getTokenMetadata } from "./actions/token/getTokenMetadata.js"; export { getTokenAllowance } from "./actions/token/getTokenAllowance.js"; export { getAssetTransfers } from "./actions/transfers/getAssetTransfers.js"; +export { getAssetTransfersPages } from "./actions/transfers/getAssetTransfersPages.js"; // schemas export type { DataRpcSchema } from "./schema/rpc.js"; diff --git a/packages/data-apis/src/internal/errors.test.ts b/packages/data-apis/src/internal/errors.test.ts new file mode 100644 index 0000000000..90624df8b8 --- /dev/null +++ b/packages/data-apis/src/internal/errors.test.ts @@ -0,0 +1,67 @@ +import { AlchemyApiError } from "@alchemy/common"; +import { HttpRequestError, RpcRequestError } from "viem"; +import { describe, expect, it } from "vitest"; +import { redactUrlCredentials, wrapRpcError } from "./errors.js"; + +const SECRET = "supersecretapikey123"; + +describe("redactUrlCredentials", () => { + it("redacts /v2/ path segments and apiKey query params", () => { + expect( + redactUrlCredentials( + `https://eth-mainnet.g.alchemy.com/v2/${SECRET} and https://x.test/?apiKey=${SECRET}&y=1`, + ), + ).toBe( + "https://eth-mainnet.g.alchemy.com/v2/[redacted] and https://x.test/?apiKey=[redacted]&y=1", + ); + }); +}); + +describe("wrapRpcError", () => { + it("maps RpcRequestError to AlchemyApiError with the rpc code, redacted", () => { + const viemError = new RpcRequestError({ + body: { method: "alchemy_getAssetTransfers" }, + url: `https://eth-mainnet.g.alchemy.com/v2/${SECRET}`, + error: { code: -32602, message: "invalid params" }, + }); + const wrapped = (() => { + try { + wrapRpcError(viemError); + } catch (e) { + return e as AlchemyApiError; + } + throw new Error("did not throw"); + })(); + expect(wrapped).toBeInstanceOf(AlchemyApiError); + expect(wrapped.code).toBe(-32602); + expect(wrapped.message).not.toContain(SECRET); + }); + + it("maps HttpRequestError to AlchemyApiError with the status, redacted", () => { + const viemError = new HttpRequestError({ + url: `https://eth-mainnet.g.alchemy.com/v2/${SECRET}`, + status: 503, + details: `service unavailable at /v2/${SECRET}`, + }); + const wrapped = (() => { + try { + wrapRpcError(viemError); + } catch (e) { + return e as AlchemyApiError; + } + throw new Error("did not throw"); + })(); + expect(wrapped).toBeInstanceOf(AlchemyApiError); + expect(wrapped.status).toBe(503); + expect(wrapped.message).not.toContain(SECRET); + expect(JSON.stringify(wrapped)).not.toContain(SECRET); + }); + + it("passes AlchemyApiError and unknown errors through untouched", () => { + const original = new AlchemyApiError("already normalized", { status: 400 }); + expect(() => wrapRpcError(original)).toThrow(original); + + const unknown = new Error("not a viem error"); + expect(() => wrapRpcError(unknown)).toThrow(unknown); + }); +}); diff --git a/packages/data-apis/src/internal/errors.ts b/packages/data-apis/src/internal/errors.ts new file mode 100644 index 0000000000..81f27a052e --- /dev/null +++ b/packages/data-apis/src/internal/errors.ts @@ -0,0 +1,46 @@ +import { AlchemyApiError } from "@alchemy/common"; +import { HttpRequestError, RpcRequestError } from "viem"; + +/** + * Redacts credentials that can appear in URLs: keys embedded in "/v2/" + * RPC paths (when a caller configured a key-bearing url) and apiKey query + * params. The header-auth paths never put keys in URLs; this protects the + * configured-url escape hatch. + * + * @param {string} text Any error text that may embed a URL + * @returns {string} The text with credentials replaced by "[redacted]" + */ +export function redactUrlCredentials(text: string): string { + return text + .replace(/(\/v2\/)[A-Za-z0-9_-]+/g, "$1[redacted]") + .replace(/([?&]apiKey=)[^&\s]+/gi, "$1[redacted]"); +} + +/** + * Normalizes viem JSON-RPC failures into the {@link AlchemyApiError} family + * (the REST channel is normalized inside AlchemyRestClient), so consumers + * handle one error shape across both channels. URL-bearing viem errors are + * carried as redacted details rather than as a cause, so credential-bearing + * URLs never leak through error chains. + * + * @param {unknown} error The error thrown by `client.request` + * @returns {never} Always throws + */ +export function wrapRpcError(error: unknown): never { + if (error instanceof AlchemyApiError) { + throw error; + } + if (error instanceof RpcRequestError) { + throw new AlchemyApiError(redactUrlCredentials(error.shortMessage), { + code: error.code, + details: redactUrlCredentials(error.details), + }); + } + if (error instanceof HttpRequestError) { + throw new AlchemyApiError(redactUrlCredentials(error.shortMessage), { + status: error.status, + details: redactUrlCredentials(error.details ?? ""), + }); + } + throw error; +} diff --git a/packages/data-apis/src/internal/paginate.test.ts b/packages/data-apis/src/internal/paginate.test.ts new file mode 100644 index 0000000000..19ea4739e6 --- /dev/null +++ b/packages/data-apis/src/internal/paginate.test.ts @@ -0,0 +1,104 @@ +import { describe, expect, it, vi } from "vitest"; +import { paginate } from "./paginate.js"; + +type Page = { items: number[]; pageKey?: string }; + +const collect = async (gen: AsyncGenerator) => { + const pages: Page[] = []; + for await (const page of gen) pages.push(page); + return pages; +}; + +describe("paginate", () => { + it("walks cursors until the response omits one", async () => { + const fetchPage = vi + .fn<(cursor: string | undefined) => Promise>() + .mockResolvedValueOnce({ items: [1], pageKey: "a" }) + .mockResolvedValueOnce({ items: [2], pageKey: "b" }) + .mockResolvedValueOnce({ items: [3] }); + const pages = await collect( + paginate({ fetchPage, nextCursor: (p) => p.pageKey }), + ); + expect(pages.map((p) => p.items[0])).toEqual([1, 2, 3]); + expect(fetchPage.mock.calls.map((c) => c[0])).toEqual([ + undefined, + "a", + "b", + ]); + }); + + it("stops on an empty-string cursor", async () => { + const fetchPage = vi + .fn<(cursor: string | undefined) => Promise>() + .mockResolvedValue({ items: [1], pageKey: "" }); + const pages = await collect( + paginate({ fetchPage, nextCursor: (p) => p.pageKey }), + ); + expect(pages).toHaveLength(1); + }); + + it("throws on a repeated cursor instead of looping forever", async () => { + const fetchPage = vi + .fn<(cursor: string | undefined) => Promise>() + .mockResolvedValue({ items: [1], pageKey: "same" }); + await expect( + collect(paginate({ fetchPage, nextCursor: (p) => p.pageKey })), + ).rejects.toThrow(/repeated/); + expect(fetchPage).toHaveBeenCalledTimes(2); + }); + + it("respects maxPages, yielding the capping page", async () => { + const fetchPage = vi + .fn<(cursor: string | undefined) => Promise>() + .mockImplementation(async (cursor) => ({ + items: [Number(cursor ?? 0)], + pageKey: String(Number(cursor ?? 0) + 1), + })); + const pages = await collect( + paginate({ + fetchPage, + nextCursor: (p) => p.pageKey, + options: { maxPages: 3 }, + }), + ); + expect(pages).toHaveLength(3); + expect(fetchPage).toHaveBeenCalledTimes(3); + }); + + it("stops cleanly when the consumer breaks", async () => { + const fetchPage = vi + .fn<(cursor: string | undefined) => Promise>() + .mockImplementation(async (cursor) => ({ + items: [1], + pageKey: String(Number(cursor ?? 0) + 1), + })); + for await (const page of paginate({ + fetchPage, + nextCursor: (p) => p.pageKey, + })) { + void page; + break; + } + expect(fetchPage).toHaveBeenCalledTimes(1); + }); + + it("throws the abort reason when the signal aborts between pages", async () => { + const controller = new AbortController(); + const fetchPage = vi + .fn<(cursor: string | undefined) => Promise>() + .mockImplementation(async () => { + controller.abort(new Error("stop-now")); + return { items: [1], pageKey: "next" }; + }); + await expect( + collect( + paginate({ + fetchPage, + nextCursor: (p) => p.pageKey, + options: { signal: controller.signal }, + }), + ), + ).rejects.toThrow("stop-now"); + expect(fetchPage).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/data-apis/src/internal/paginate.ts b/packages/data-apis/src/internal/paginate.ts new file mode 100644 index 0000000000..58f8c3e243 --- /dev/null +++ b/packages/data-apis/src/internal/paginate.ts @@ -0,0 +1,55 @@ +import { BaseError } from "@alchemy/common"; + +/** Options accepted by the `*Pages` async-iterator actions. */ +export type PaginateOptions = { + /** Aborts iteration (checked between pages and passed to page requests). */ + signal?: AbortSignal; + /** Stop after this many pages (the page that hits the cap is still yielded). */ + maxPages?: number; +}; + +/** + * Shared cursor-pagination driver behind the `*Pages` actions. Yields whole + * pages (page-level metadata like totalCount/validAt stays available; + * per-item iteration is one inner loop away). Stops on a missing or empty + * cursor, and throws if the server ever repeats a cursor — the guard against + * infinite pagination loops. + * + * @param {object} config The pagination wiring + * @param {Function} config.fetchPage Fetches one page for a cursor (undefined = first page) + * @param {Function} config.nextCursor Extracts the next cursor from a page + * @param {PaginateOptions} [config.options] Abort signal and page cap + * @returns {AsyncGenerator} Pages, in order + * @yields Each page result as returned by fetchPage + */ +export async function* paginate({ + fetchPage, + nextCursor, + options, +}: { + fetchPage: ( + cursor: string | undefined, + signal?: AbortSignal, + ) => Promise; + nextCursor: (page: TPage) => string | null | undefined; + options?: PaginateOptions; +}): AsyncGenerator { + const seen = new Set(); + let cursor: string | undefined; + let pages = 0; + while (true) { + options?.signal?.throwIfAborted(); + const page = await fetchPage(cursor, options?.signal); + yield page; + if (options?.maxPages != null && ++pages >= options.maxPages) return; + const next = nextCursor(page); + if (next == null || next === "") return; + if (seen.has(next)) { + throw new BaseError( + `Pagination cursor "${next}" was repeated by the server; stopping to avoid an infinite loop.`, + ); + } + seen.add(next); + cursor = next; + } +} From 4fc0591ce79b1ff83eff4701d86bcd0392e0a92f Mon Sep 17 00:00:00 2001 From: blake duncan Date: Wed, 10 Jun 2026 11:10:09 -0400 Subject: [PATCH 11/20] =?UTF-8?q?feat(data-apis):=20publish=20readiness=20?= =?UTF-8?q?=E2=80=94=20alpha=20versioning,=20typedoc,=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - version 5.0.3-alpha.0 with publishConfig.tag 'alpha'; deliberately NOT in lerna.json's fixed-version publish set so the regular release can't ship it as latest — alpha publishing + graduation checklist documented in the README - typedoc: data-apis entry point + tsconfig.typedoc include + nav registration in generate-typedoc-yaml.ts; generated reference docs committed under docs/pages/reference/data-apis (CI's docs:sdk drift check now covers the package) - README rewritten: install, both entry points, per-namespace quickstart, three network formats, pagination, error semantics, release process Co-Authored-By: Claude Fable 5 --- docs/docs.yml | 287 ++++++++++ docs/pages/reference/common/src/README.mdx | 77 +-- .../common/src/classes/AlchemyApiError.mdx | 219 ++++++++ .../common/src/classes/AlchemyRestClient.mdx | 115 ++++ .../common/src/classes/BaseError.mdx | 3 +- .../common/src/classes/FetchError.mdx | 108 +++- .../common/src/classes/ServerError.mdx | 108 +++- .../common/src/functions/composeSignals.mdx | 52 ++ .../common/src/functions/resolveNetwork.mdx | 63 +++ .../reference/common/src/functions/sleep.mdx | 66 +++ .../type-aliases/AlchemyApiErrorDetails.mdx | 87 ++++ .../src/type-aliases/AlchemyNetwork.mdx | 18 + .../type-aliases/AlchemyRestClientParams.mdx | 129 +++++ .../src/type-aliases/KnownAlchemyNetwork.mdx | 32 ++ .../common/src/type-aliases/NetworkInput.mdx | 18 + .../common/src/type-aliases/QueryParams.mdx | 16 + .../common/src/type-aliases/QueryValue.mdx | 16 + .../src/type-aliases/ResolvedNetwork.mdx | 51 ++ .../common/src/type-aliases/RestRequestFn.mdx | 104 ++++ .../src/type-aliases/RestRequestOptions.mdx | 87 ++++ .../src/type-aliases/RestRequestParams.mdx | 38 ++ .../src/type-aliases/RestRequestSchema.mdx | 14 + docs/pages/reference/data-apis/src/README.mdx | 204 ++++++++ .../data-apis/src/functions/computeRarity.mdx | 89 ++++ .../src/functions/createDataClient.mdx | 67 +++ .../data-apis/src/functions/dataActions.mdx | 68 +++ .../src/functions/getAssetTransfers.mdx | 80 +++ .../src/functions/getAssetTransfersPages.mdx | 97 ++++ .../src/functions/getCollectionMetadata.mdx | 109 ++++ .../src/functions/getCollectionsForOwner.mdx | 93 ++++ .../functions/getCollectionsForOwnerPages.mdx | 97 ++++ .../src/functions/getContractMetadata.mdx | 129 +++++ .../functions/getContractMetadataBatch.mdx | 81 +++ .../src/functions/getContractsForOwner.mdx | 93 ++++ .../functions/getContractsForOwnerPages.mdx | 97 ++++ .../data-apis/src/functions/getFloorPrice.mdx | 101 ++++ .../functions/getHistoricalTokenPrices.mdx | 109 ++++ .../functions/getNftContractsByAddress.mdx | 98 ++++ .../getNftContractsByAddressPages.mdx | 101 ++++ .../src/functions/getNftMetadata.mdx | 231 +++++++++ .../src/functions/getNftMetadataBatch.mdx | 81 +++ .../data-apis/src/functions/getNftSales.mdx | 101 ++++ .../src/functions/getNftSalesPages.mdx | 105 ++++ .../src/functions/getNftsByAddress.mdx | 98 ++++ .../src/functions/getNftsByAddressPages.mdx | 101 ++++ .../src/functions/getNftsForCollection.mdx | 91 ++++ .../functions/getNftsForCollectionPages.mdx | 95 ++++ .../src/functions/getNftsForContract.mdx | 91 ++++ .../src/functions/getNftsForContractPages.mdx | 95 ++++ .../src/functions/getNftsForOwner.mdx | 103 ++++ .../src/functions/getNftsForOwnerPages.mdx | 107 ++++ .../src/functions/getOwnersForContract.mdx | 89 ++++ .../src/functions/getOwnersForNft.mdx | 91 ++++ .../src/functions/getSpamContracts.mdx | 89 ++++ .../src/functions/getTokenAllowance.mdx | 68 +++ .../src/functions/getTokenBalances.mdx | 71 +++ .../functions/getTokenBalancesByAddress.mdx | 96 ++++ .../src/functions/getTokenMetadata.mdx | 71 +++ .../src/functions/getTokenPricesByAddress.mdx | 89 ++++ .../src/functions/getTokenPricesBySymbol.mdx | 105 ++++ .../src/functions/getTokensByAddress.mdx | 108 ++++ .../data-apis/src/functions/isAirdropNft.mdx | 89 ++++ .../src/functions/isHolderOfContract.mdx | 89 ++++ .../src/functions/isSpamContract.mdx | 89 ++++ .../src/functions/searchContractMetadata.mdx | 81 +++ .../src/functions/summarizeNftAttributes.mdx | 93 ++++ .../src/interfaces/PortfolioAddressEntry.mdx | 55 ++ .../src/interfaces/PriceAddressEntry.mdx | 55 ++ .../src/type-aliases/AlchemyDataClient.mdx | 15 + .../type-aliases/AlchemyDataClientOptions.mdx | 50 ++ .../src/type-aliases/AssetTransfer.mdx | 14 + .../src/type-aliases/ComputeRarityParams.mdx | 14 + .../src/type-aliases/ComputeRarityResult.mdx | 14 + .../src/type-aliases/DataActions.mdx | 490 ++++++++++++++++++ .../src/type-aliases/DataRpcSchema.mdx | 28 + .../type-aliases/GetAssetTransfersParams.mdx | 45 ++ .../type-aliases/GetAssetTransfersResult.mdx | 18 + .../GetCollectionMetadataParams.mdx | 14 + .../GetCollectionMetadataResult.mdx | 14 + .../GetCollectionsForOwnerParams.mdx | 14 + .../GetCollectionsForOwnerResult.mdx | 14 + .../GetContractMetadataBatchParams.mdx | 43 ++ .../GetContractMetadataBatchResult.mdx | 14 + .../GetContractMetadataParams.mdx | 14 + .../GetContractMetadataResult.mdx | 14 + .../GetContractsForOwnerParams.mdx | 14 + .../GetContractsForOwnerResult.mdx | 14 + .../src/type-aliases/GetFloorPriceParams.mdx | 14 + .../src/type-aliases/GetFloorPriceResult.mdx | 14 + .../GetHistoricalTokenPricesParams.mdx | 15 + .../GetHistoricalTokenPricesResult.mdx | 14 + .../GetNftContractsByAddressParams.mdx | 15 + .../GetNftContractsByAddressResult.mdx | 14 + .../GetNftMetadataBatchParams.mdx | 43 ++ .../GetNftMetadataBatchResult.mdx | 14 + .../src/type-aliases/GetNftMetadataParams.mdx | 14 + .../src/type-aliases/GetNftMetadataResult.mdx | 14 + .../src/type-aliases/GetNftSalesParams.mdx | 14 + .../src/type-aliases/GetNftSalesResult.mdx | 14 + .../type-aliases/GetNftsByAddressParams.mdx | 14 + .../type-aliases/GetNftsByAddressResult.mdx | 14 + .../GetNftsForCollectionParams.mdx | 14 + .../GetNftsForCollectionResult.mdx | 14 + .../type-aliases/GetNftsForContractParams.mdx | 14 + .../type-aliases/GetNftsForContractResult.mdx | 14 + .../type-aliases/GetNftsForOwnerParams.mdx | 14 + .../type-aliases/GetNftsForOwnerResult.mdx | 14 + .../GetOwnersForContractParams.mdx | 14 + .../GetOwnersForContractResult.mdx | 14 + .../type-aliases/GetOwnersForNftParams.mdx | 14 + .../type-aliases/GetOwnersForNftResult.mdx | 14 + .../type-aliases/GetSpamContractsParams.mdx | 43 ++ .../type-aliases/GetSpamContractsResult.mdx | 14 + .../type-aliases/GetTokenAllowanceParams.mdx | 43 ++ .../type-aliases/GetTokenAllowanceResult.mdx | 14 + .../GetTokenBalancesByAddressParams.mdx | 15 + .../GetTokenBalancesByAddressResult.mdx | 14 + .../type-aliases/GetTokenBalancesParams.mdx | 85 +++ .../type-aliases/GetTokenBalancesResult.mdx | 14 + .../type-aliases/GetTokenMetadataParams.mdx | 57 ++ .../type-aliases/GetTokenMetadataResult.mdx | 14 + .../GetTokenPricesByAddressParams.mdx | 42 ++ .../GetTokenPricesByAddressResult.mdx | 14 + .../GetTokenPricesBySymbolParams.mdx | 16 + .../GetTokenPricesBySymbolResult.mdx | 14 + .../type-aliases/GetTokensByAddressParams.mdx | 14 + .../type-aliases/GetTokensByAddressResult.mdx | 14 + .../src/type-aliases/IsAirdropNftParams.mdx | 14 + .../src/type-aliases/IsAirdropNftResult.mdx | 14 + .../type-aliases/IsHolderOfContractParams.mdx | 14 + .../type-aliases/IsHolderOfContractResult.mdx | 14 + .../src/type-aliases/IsSpamContractParams.mdx | 14 + .../src/type-aliases/IsSpamContractResult.mdx | 14 + .../src/type-aliases/NftRestSchema.mdx | 164 ++++++ .../src/type-aliases/PaginateOptions.mdx | 59 +++ .../src/type-aliases/PortfolioRestSchema.mdx | 45 ++ .../src/type-aliases/PortfolioToken.mdx | 16 + .../src/type-aliases/PricesRestSchema.mdx | 38 ++ .../src/type-aliases/RequestOptions.mdx | 45 ++ .../SearchContractMetadataParams.mdx | 14 + .../SearchContractMetadataResult.mdx | 14 + .../SummarizeNftAttributesParams.mdx | 14 + .../SummarizeNftAttributesResult.mdx | 14 + .../data-apis/src/variables/VERSION.mdx | 14 + docs/pages/reference/modules.mdx | 1 + packages/data-apis/README.md | 131 +++-- packages/data-apis/package.json | 16 +- packages/data-apis/src/version.ts | 2 +- scripts/generate-typedoc-yaml.ts | 2 + tsconfig.typedoc.json | 1 + typedoc.json | 1 + 151 files changed, 8397 insertions(+), 77 deletions(-) create mode 100644 docs/pages/reference/common/src/classes/AlchemyApiError.mdx create mode 100644 docs/pages/reference/common/src/classes/AlchemyRestClient.mdx create mode 100644 docs/pages/reference/common/src/functions/composeSignals.mdx create mode 100644 docs/pages/reference/common/src/functions/resolveNetwork.mdx create mode 100644 docs/pages/reference/common/src/functions/sleep.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/AlchemyApiErrorDetails.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/AlchemyNetwork.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/AlchemyRestClientParams.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/KnownAlchemyNetwork.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/NetworkInput.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/QueryParams.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/QueryValue.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/ResolvedNetwork.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/RestRequestFn.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/RestRequestOptions.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/RestRequestParams.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/RestRequestSchema.mdx create mode 100644 docs/pages/reference/data-apis/src/README.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/computeRarity.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/createDataClient.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/dataActions.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getAssetTransfers.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getAssetTransfersPages.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getCollectionMetadata.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getCollectionsForOwner.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getCollectionsForOwnerPages.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getContractMetadata.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getContractMetadataBatch.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getContractsForOwner.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getContractsForOwnerPages.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getFloorPrice.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getHistoricalTokenPrices.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftContractsByAddress.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftContractsByAddressPages.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftMetadata.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftMetadataBatch.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftSales.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftSalesPages.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftsByAddress.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftsByAddressPages.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftsForCollection.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftsForCollectionPages.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftsForContract.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftsForContractPages.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftsForOwner.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getNftsForOwnerPages.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getOwnersForContract.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getOwnersForNft.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getSpamContracts.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getTokenAllowance.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getTokenBalances.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getTokenBalancesByAddress.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getTokenMetadata.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getTokenPricesByAddress.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getTokenPricesBySymbol.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/getTokensByAddress.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/isAirdropNft.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/isHolderOfContract.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/isSpamContract.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/searchContractMetadata.mdx create mode 100644 docs/pages/reference/data-apis/src/functions/summarizeNftAttributes.mdx create mode 100644 docs/pages/reference/data-apis/src/interfaces/PortfolioAddressEntry.mdx create mode 100644 docs/pages/reference/data-apis/src/interfaces/PriceAddressEntry.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClient.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClientOptions.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/AssetTransfer.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/ComputeRarityParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/ComputeRarityResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/DataActions.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/DataRpcSchema.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetAssetTransfersParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetAssetTransfersResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetCollectionMetadataParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetCollectionMetadataResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetCollectionsForOwnerParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetCollectionsForOwnerResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetContractsForOwnerParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetContractsForOwnerResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetFloorPriceParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetFloorPriceResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftContractsByAddressParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftContractsByAddressResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftSalesParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftSalesResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftsByAddressParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftsByAddressResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftsForCollectionParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftsForCollectionResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftsForContractParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftsForContractResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftsForOwnerParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetNftsForOwnerResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetOwnersForContractParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetOwnersForContractResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetOwnersForNftParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetOwnersForNftResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetSpamContractsParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetSpamContractsResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesByAddressParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesByAddressResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokenMetadataParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokenMetadataResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesByAddressParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesByAddressResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesBySymbolParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesBySymbolResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokensByAddressParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/GetTokensByAddressResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/IsAirdropNftParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/IsAirdropNftResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/IsHolderOfContractParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/IsHolderOfContractResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/IsSpamContractParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/IsSpamContractResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/NftRestSchema.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/PaginateOptions.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/PortfolioRestSchema.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/PortfolioToken.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/PricesRestSchema.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/RequestOptions.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/SearchContractMetadataParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/SearchContractMetadataResult.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/SummarizeNftAttributesParams.mdx create mode 100644 docs/pages/reference/data-apis/src/type-aliases/SummarizeNftAttributesResult.mdx create mode 100644 docs/pages/reference/data-apis/src/variables/VERSION.mdx diff --git a/docs/docs.yml b/docs/docs.yml index 9d2c4bf6a5..534e6f7ed4 100644 --- a/docs/docs.yml +++ b/docs/docs.yml @@ -506,6 +506,259 @@ navigation: path: wallets/pages/reference/smart-accounts/src/variables/semiModularAccount7702StaticImpl.mdx - page: semiModularAccountV2StaticImpl path: wallets/pages/reference/smart-accounts/src/variables/semiModularAccountV2StaticImpl.mdx + - section: Data APIs + path: wallets/pages/reference/data-apis/src/README.mdx + contents: + - section: Functions + contents: + - page: computeRarity + path: wallets/pages/reference/data-apis/src/functions/computeRarity.mdx + - page: createDataClient + path: wallets/pages/reference/data-apis/src/functions/createDataClient.mdx + - page: dataActions + path: wallets/pages/reference/data-apis/src/functions/dataActions.mdx + - page: getAssetTransfers + path: wallets/pages/reference/data-apis/src/functions/getAssetTransfers.mdx + - page: getAssetTransfersPages + path: wallets/pages/reference/data-apis/src/functions/getAssetTransfersPages.mdx + - page: getCollectionMetadata + path: wallets/pages/reference/data-apis/src/functions/getCollectionMetadata.mdx + - page: getCollectionsForOwner + path: wallets/pages/reference/data-apis/src/functions/getCollectionsForOwner.mdx + - page: getCollectionsForOwnerPages + path: wallets/pages/reference/data-apis/src/functions/getCollectionsForOwnerPages.mdx + - page: getContractMetadata + path: wallets/pages/reference/data-apis/src/functions/getContractMetadata.mdx + - page: getContractMetadataBatch + path: wallets/pages/reference/data-apis/src/functions/getContractMetadataBatch.mdx + - page: getContractsForOwner + path: wallets/pages/reference/data-apis/src/functions/getContractsForOwner.mdx + - page: getContractsForOwnerPages + path: wallets/pages/reference/data-apis/src/functions/getContractsForOwnerPages.mdx + - page: getFloorPrice + path: wallets/pages/reference/data-apis/src/functions/getFloorPrice.mdx + - page: getHistoricalTokenPrices + path: wallets/pages/reference/data-apis/src/functions/getHistoricalTokenPrices.mdx + - page: getNftContractsByAddress + path: wallets/pages/reference/data-apis/src/functions/getNftContractsByAddress.mdx + - page: getNftContractsByAddressPages + path: wallets/pages/reference/data-apis/src/functions/getNftContractsByAddressPages.mdx + - page: getNftMetadata + path: wallets/pages/reference/data-apis/src/functions/getNftMetadata.mdx + - page: getNftMetadataBatch + path: wallets/pages/reference/data-apis/src/functions/getNftMetadataBatch.mdx + - page: getNftSales + path: wallets/pages/reference/data-apis/src/functions/getNftSales.mdx + - page: getNftSalesPages + path: wallets/pages/reference/data-apis/src/functions/getNftSalesPages.mdx + - page: getNftsByAddress + path: wallets/pages/reference/data-apis/src/functions/getNftsByAddress.mdx + - page: getNftsByAddressPages + path: wallets/pages/reference/data-apis/src/functions/getNftsByAddressPages.mdx + - page: getNftsForCollection + path: wallets/pages/reference/data-apis/src/functions/getNftsForCollection.mdx + - page: getNftsForCollectionPages + path: wallets/pages/reference/data-apis/src/functions/getNftsForCollectionPages.mdx + - page: getNftsForContract + path: wallets/pages/reference/data-apis/src/functions/getNftsForContract.mdx + - page: getNftsForContractPages + path: wallets/pages/reference/data-apis/src/functions/getNftsForContractPages.mdx + - page: getNftsForOwner + path: wallets/pages/reference/data-apis/src/functions/getNftsForOwner.mdx + - page: getNftsForOwnerPages + path: wallets/pages/reference/data-apis/src/functions/getNftsForOwnerPages.mdx + - page: getOwnersForContract + path: wallets/pages/reference/data-apis/src/functions/getOwnersForContract.mdx + - page: getOwnersForNft + path: wallets/pages/reference/data-apis/src/functions/getOwnersForNft.mdx + - page: getSpamContracts + path: wallets/pages/reference/data-apis/src/functions/getSpamContracts.mdx + - page: getTokenAllowance + path: wallets/pages/reference/data-apis/src/functions/getTokenAllowance.mdx + - page: getTokenBalances + path: wallets/pages/reference/data-apis/src/functions/getTokenBalances.mdx + - page: getTokenBalancesByAddress + path: wallets/pages/reference/data-apis/src/functions/getTokenBalancesByAddress.mdx + - page: getTokenMetadata + path: wallets/pages/reference/data-apis/src/functions/getTokenMetadata.mdx + - page: getTokenPricesByAddress + path: wallets/pages/reference/data-apis/src/functions/getTokenPricesByAddress.mdx + - page: getTokenPricesBySymbol + path: wallets/pages/reference/data-apis/src/functions/getTokenPricesBySymbol.mdx + - page: getTokensByAddress + path: wallets/pages/reference/data-apis/src/functions/getTokensByAddress.mdx + - page: isAirdropNft + path: wallets/pages/reference/data-apis/src/functions/isAirdropNft.mdx + - page: isHolderOfContract + path: wallets/pages/reference/data-apis/src/functions/isHolderOfContract.mdx + - page: isSpamContract + path: wallets/pages/reference/data-apis/src/functions/isSpamContract.mdx + - page: searchContractMetadata + path: wallets/pages/reference/data-apis/src/functions/searchContractMetadata.mdx + - page: summarizeNftAttributes + path: wallets/pages/reference/data-apis/src/functions/summarizeNftAttributes.mdx + - section: Interfaces + contents: + - page: PortfolioAddressEntry + path: wallets/pages/reference/data-apis/src/interfaces/PortfolioAddressEntry.mdx + - page: PriceAddressEntry + path: wallets/pages/reference/data-apis/src/interfaces/PriceAddressEntry.mdx + - section: Type Aliases + contents: + - page: AlchemyDataClient + path: wallets/pages/reference/data-apis/src/type-aliases/AlchemyDataClient.mdx + - page: AlchemyDataClientOptions + path: wallets/pages/reference/data-apis/src/type-aliases/AlchemyDataClientOptions.mdx + - page: AssetTransfer + path: wallets/pages/reference/data-apis/src/type-aliases/AssetTransfer.mdx + - page: ComputeRarityParams + path: wallets/pages/reference/data-apis/src/type-aliases/ComputeRarityParams.mdx + - page: ComputeRarityResult + path: wallets/pages/reference/data-apis/src/type-aliases/ComputeRarityResult.mdx + - page: DataActions + path: wallets/pages/reference/data-apis/src/type-aliases/DataActions.mdx + - page: DataRpcSchema + path: wallets/pages/reference/data-apis/src/type-aliases/DataRpcSchema.mdx + - page: GetAssetTransfersParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetAssetTransfersParams.mdx + - page: GetAssetTransfersResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetAssetTransfersResult.mdx + - page: GetCollectionMetadataParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetCollectionMetadataParams.mdx + - page: GetCollectionMetadataResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetCollectionMetadataResult.mdx + - page: GetCollectionsForOwnerParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetCollectionsForOwnerParams.mdx + - page: GetCollectionsForOwnerResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetCollectionsForOwnerResult.mdx + - page: GetContractMetadataBatchParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchParams.mdx + - page: GetContractMetadataBatchResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchResult.mdx + - page: GetContractMetadataParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetContractMetadataParams.mdx + - page: GetContractMetadataResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetContractMetadataResult.mdx + - page: GetContractsForOwnerParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetContractsForOwnerParams.mdx + - page: GetContractsForOwnerResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetContractsForOwnerResult.mdx + - page: GetFloorPriceParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetFloorPriceParams.mdx + - page: GetFloorPriceResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetFloorPriceResult.mdx + - page: GetHistoricalTokenPricesParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesParams.mdx + - page: GetHistoricalTokenPricesResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesResult.mdx + - page: GetNftContractsByAddressParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftContractsByAddressParams.mdx + - page: GetNftContractsByAddressResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftContractsByAddressResult.mdx + - page: GetNftMetadataBatchParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchParams.mdx + - page: GetNftMetadataBatchResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchResult.mdx + - page: GetNftMetadataParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftMetadataParams.mdx + - page: GetNftMetadataResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftMetadataResult.mdx + - page: GetNftSalesParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftSalesParams.mdx + - page: GetNftSalesResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftSalesResult.mdx + - page: GetNftsByAddressParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftsByAddressParams.mdx + - page: GetNftsByAddressResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftsByAddressResult.mdx + - page: GetNftsForCollectionParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftsForCollectionParams.mdx + - page: GetNftsForCollectionResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftsForCollectionResult.mdx + - page: GetNftsForContractParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftsForContractParams.mdx + - page: GetNftsForContractResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftsForContractResult.mdx + - page: GetNftsForOwnerParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftsForOwnerParams.mdx + - page: GetNftsForOwnerResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetNftsForOwnerResult.mdx + - page: GetOwnersForContractParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetOwnersForContractParams.mdx + - page: GetOwnersForContractResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetOwnersForContractResult.mdx + - page: GetOwnersForNftParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetOwnersForNftParams.mdx + - page: GetOwnersForNftResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetOwnersForNftResult.mdx + - page: GetSpamContractsParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetSpamContractsParams.mdx + - page: GetSpamContractsResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetSpamContractsResult.mdx + - page: GetTokenAllowanceParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceParams.mdx + - page: GetTokenAllowanceResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceResult.mdx + - page: GetTokenBalancesByAddressParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokenBalancesByAddressParams.mdx + - page: GetTokenBalancesByAddressResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokenBalancesByAddressResult.mdx + - page: GetTokenBalancesParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokenBalancesParams.mdx + - page: GetTokenBalancesResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokenBalancesResult.mdx + - page: GetTokenMetadataParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokenMetadataParams.mdx + - page: GetTokenMetadataResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokenMetadataResult.mdx + - page: GetTokenPricesByAddressParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokenPricesByAddressParams.mdx + - page: GetTokenPricesByAddressResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokenPricesByAddressResult.mdx + - page: GetTokenPricesBySymbolParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokenPricesBySymbolParams.mdx + - page: GetTokenPricesBySymbolResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokenPricesBySymbolResult.mdx + - page: GetTokensByAddressParams + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokensByAddressParams.mdx + - page: GetTokensByAddressResult + path: wallets/pages/reference/data-apis/src/type-aliases/GetTokensByAddressResult.mdx + - page: IsAirdropNftParams + path: wallets/pages/reference/data-apis/src/type-aliases/IsAirdropNftParams.mdx + - page: IsAirdropNftResult + path: wallets/pages/reference/data-apis/src/type-aliases/IsAirdropNftResult.mdx + - page: IsHolderOfContractParams + path: wallets/pages/reference/data-apis/src/type-aliases/IsHolderOfContractParams.mdx + - page: IsHolderOfContractResult + path: wallets/pages/reference/data-apis/src/type-aliases/IsHolderOfContractResult.mdx + - page: IsSpamContractParams + path: wallets/pages/reference/data-apis/src/type-aliases/IsSpamContractParams.mdx + - page: IsSpamContractResult + path: wallets/pages/reference/data-apis/src/type-aliases/IsSpamContractResult.mdx + - page: NftRestSchema + path: wallets/pages/reference/data-apis/src/type-aliases/NftRestSchema.mdx + - page: PaginateOptions + path: wallets/pages/reference/data-apis/src/type-aliases/PaginateOptions.mdx + - page: PortfolioRestSchema + path: wallets/pages/reference/data-apis/src/type-aliases/PortfolioRestSchema.mdx + - page: PortfolioToken + path: wallets/pages/reference/data-apis/src/type-aliases/PortfolioToken.mdx + - page: PricesRestSchema + path: wallets/pages/reference/data-apis/src/type-aliases/PricesRestSchema.mdx + - page: RequestOptions + path: wallets/pages/reference/data-apis/src/type-aliases/RequestOptions.mdx + - page: SearchContractMetadataParams + path: wallets/pages/reference/data-apis/src/type-aliases/SearchContractMetadataParams.mdx + - page: SearchContractMetadataResult + path: wallets/pages/reference/data-apis/src/type-aliases/SearchContractMetadataResult.mdx + - page: SummarizeNftAttributesParams + path: wallets/pages/reference/data-apis/src/type-aliases/SummarizeNftAttributesParams.mdx + - page: SummarizeNftAttributesResult + path: wallets/pages/reference/data-apis/src/type-aliases/SummarizeNftAttributesResult.mdx + - section: Variables + contents: + - page: VERSION + path: wallets/pages/reference/data-apis/src/variables/VERSION.mdx - section: AA Infra path: wallets/pages/reference/aa-infra/src/README.mdx contents: @@ -526,6 +779,10 @@ navigation: contents: - page: AccountNotFoundError path: wallets/pages/reference/common/src/classes/AccountNotFoundError.mdx + - page: AlchemyApiError + path: wallets/pages/reference/common/src/classes/AlchemyApiError.mdx + - page: AlchemyRestClient + path: wallets/pages/reference/common/src/classes/AlchemyRestClient.mdx - page: BaseError path: wallets/pages/reference/common/src/classes/BaseError.mdx - page: ChainNotFoundError @@ -550,6 +807,8 @@ navigation: path: wallets/pages/reference/common/src/functions/bigIntMax.mdx - page: bigIntMultiply path: wallets/pages/reference/common/src/functions/bigIntMultiply.mdx + - page: composeSignals + path: wallets/pages/reference/common/src/functions/composeSignals.mdx - page: getAlchemyRpcUrl path: wallets/pages/reference/common/src/functions/getAlchemyRpcUrl.mdx - page: getSupportedChainIds @@ -564,6 +823,10 @@ navigation: path: wallets/pages/reference/common/src/functions/lowerAddress.mdx - page: raise path: wallets/pages/reference/common/src/functions/raise.mdx + - page: resolveNetwork + path: wallets/pages/reference/common/src/functions/resolveNetwork.mdx + - page: sleep + path: wallets/pages/reference/common/src/functions/sleep.mdx - page: validateAlchemyConnectionConfig path: wallets/pages/reference/common/src/functions/validateAlchemyConnectionConfig.mdx - section: Interfaces @@ -572,14 +835,38 @@ navigation: path: wallets/pages/reference/common/src/interfaces/AlchemyTransportConfig.mdx - section: Type Aliases contents: + - page: AlchemyApiErrorDetails + path: wallets/pages/reference/common/src/type-aliases/AlchemyApiErrorDetails.mdx - page: AlchemyConnectionConfig path: wallets/pages/reference/common/src/type-aliases/AlchemyConnectionConfig.mdx + - page: AlchemyNetwork + path: wallets/pages/reference/common/src/type-aliases/AlchemyNetwork.mdx + - page: AlchemyRestClientParams + path: wallets/pages/reference/common/src/type-aliases/AlchemyRestClientParams.mdx - page: AlchemyTransport path: wallets/pages/reference/common/src/type-aliases/AlchemyTransport.mdx - page: ExtractRpcMethod path: wallets/pages/reference/common/src/type-aliases/ExtractRpcMethod.mdx + - page: KnownAlchemyNetwork + path: wallets/pages/reference/common/src/type-aliases/KnownAlchemyNetwork.mdx + - page: NetworkInput + path: wallets/pages/reference/common/src/type-aliases/NetworkInput.mdx - page: Never path: wallets/pages/reference/common/src/type-aliases/Never.mdx + - page: QueryParams + path: wallets/pages/reference/common/src/type-aliases/QueryParams.mdx + - page: QueryValue + path: wallets/pages/reference/common/src/type-aliases/QueryValue.mdx + - page: ResolvedNetwork + path: wallets/pages/reference/common/src/type-aliases/ResolvedNetwork.mdx + - page: RestRequestFn + path: wallets/pages/reference/common/src/type-aliases/RestRequestFn.mdx + - page: RestRequestOptions + path: wallets/pages/reference/common/src/type-aliases/RestRequestOptions.mdx + - page: RestRequestParams + path: wallets/pages/reference/common/src/type-aliases/RestRequestParams.mdx + - page: RestRequestSchema + path: wallets/pages/reference/common/src/type-aliases/RestRequestSchema.mdx - section: Variables contents: - page: AlchemyConnectionConfigSchema diff --git a/docs/pages/reference/common/src/README.mdx b/docs/pages/reference/common/src/README.mdx index 7ab81f3036..a5d7f866ed 100644 --- a/docs/pages/reference/common/src/README.mdx +++ b/docs/pages/reference/common/src/README.mdx @@ -44,16 +44,18 @@ MIT ## Classes -| Class | Description | -| :--------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [AccountNotFoundError](/wallets/reference/common/classes/AccountNotFoundError) | This error is thrown when an account could not be found to execute a specific action. It extends the `BaseError` class. | -| [BaseError](/wallets/reference/common/classes/BaseError) | A custom error class that extends from `ViemBaseError`. This class allows for error messages to include links to relevant documentation based on provided `docsPath` and `docsSlug` parameters. This is based on on viem's BaseError type (obviously from the import and extend) we want the errors here to point to our docs if we supply a docsPath though | -| [ChainNotFoundError](/wallets/reference/common/classes/ChainNotFoundError) | Error class representing a "Chain Not Found" error, typically thrown when no chain is supplied to the client. | -| [ConnectionConfigError](/wallets/reference/common/classes/ConnectionConfigError) | Error class for connection configuration validation failures. | -| [FetchError](/wallets/reference/common/classes/FetchError) | Error class representing a "Fetch Error" error, typically thrown when a fetch request fails. | -| [InvalidRequestError](/wallets/reference/common/classes/InvalidRequestError) | This error is thrown when an invalid request is made. It extends the `BaseError` class. | -| [MethodUnsupportedError](/wallets/reference/common/classes/MethodUnsupportedError) | This error is thrown when an unknown method is called. It extends the `BaseError` class. | -| [ServerError](/wallets/reference/common/classes/ServerError) | Error class representing a "Server Error" error, typically thrown when a server request fails. | +| Class | Description | +| :--------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [AccountNotFoundError](/wallets/reference/common/classes/AccountNotFoundError) | This error is thrown when an account could not be found to execute a specific action. It extends the `BaseError` class. | +| [AlchemyApiError](/wallets/reference/common/classes/AlchemyApiError) | The normalized error family for Alchemy API failures. Both the REST channel (AlchemyRestClient → ServerError/FetchError, which extend this class) and SDK JSON-RPC actions surface failures as AlchemyApiError, so consumers can handle status/code/requestId/retryAfter uniformly: | +| [AlchemyRestClient](/wallets/reference/common/classes/AlchemyRestClient) | A client for making requests to Alchemy's non-JSON-RPC endpoints, with typed routes/bodies/queries (via a RestRequestSchema), bounded retries with exponential backoff (429/5xx/network only, honoring Retry-After), per-attempt timeouts, abort support, and a per-request idempotency id sent as X-Alchemy-Client-Request-Id and surfaced on thrown errors. | +| [BaseError](/wallets/reference/common/classes/BaseError) | A custom error class that extends from `ViemBaseError`. This class allows for error messages to include links to relevant documentation based on provided `docsPath` and `docsSlug` parameters. This is based on on viem's BaseError type (obviously from the import and extend) we want the errors here to point to our docs if we supply a docsPath though | +| [ChainNotFoundError](/wallets/reference/common/classes/ChainNotFoundError) | Error class representing a "Chain Not Found" error, typically thrown when no chain is supplied to the client. | +| [ConnectionConfigError](/wallets/reference/common/classes/ConnectionConfigError) | Error class for connection configuration validation failures. | +| [FetchError](/wallets/reference/common/classes/FetchError) | Error class representing a "Fetch Error" error, typically thrown when a fetch request fails. | +| [InvalidRequestError](/wallets/reference/common/classes/InvalidRequestError) | This error is thrown when an invalid request is made. It extends the `BaseError` class. | +| [MethodUnsupportedError](/wallets/reference/common/classes/MethodUnsupportedError) | This error is thrown when an unknown method is called. It extends the `BaseError` class. | +| [ServerError](/wallets/reference/common/classes/ServerError) | Error class representing a "Server Error" error, typically thrown when a server request fails. | ## Interfaces @@ -63,12 +65,24 @@ MIT ## Type Aliases -| Type Alias | Description | -| :---------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | -| [AlchemyConnectionConfig](/wallets/reference/common/type-aliases/AlchemyConnectionConfig) | TypeScript type derived from the schema for external consumption. This provides clean type inference without exposing Zod implementation details. | -| [AlchemyTransport](/wallets/reference/common/type-aliases/AlchemyTransport) | - | -| [ExtractRpcMethod](/wallets/reference/common/type-aliases/ExtractRpcMethod) | - | -| [Never](/wallets/reference/common/type-aliases/Never) | - | +| Type Alias | Description | +| :---------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [AlchemyApiErrorDetails](/wallets/reference/common/type-aliases/AlchemyApiErrorDetails) | Normalized failure metadata shared across REST and JSON-RPC channels. | +| [AlchemyConnectionConfig](/wallets/reference/common/type-aliases/AlchemyConnectionConfig) | TypeScript type derived from the schema for external consumption. This provides clean type inference without exposing Zod implementation details. | +| [AlchemyNetwork](/wallets/reference/common/type-aliases/AlchemyNetwork) | An Alchemy network identifier. Known slugs get autocomplete; arbitrary strings are accepted as an escape hatch so new networks work without an SDK release. | +| [AlchemyRestClientParams](/wallets/reference/common/type-aliases/AlchemyRestClientParams) | Parameters for creating an AlchemyRestClient instance. | +| [AlchemyTransport](/wallets/reference/common/type-aliases/AlchemyTransport) | - | +| [ExtractRpcMethod](/wallets/reference/common/type-aliases/ExtractRpcMethod) | - | +| [KnownAlchemyNetwork](/wallets/reference/common/type-aliases/KnownAlchemyNetwork) | Known Alchemy network slugs for autocomplete. | +| [NetworkInput](/wallets/reference/common/type-aliases/NetworkInput) | Any accepted network input: a viem Chain, an Alchemy network slug (e.g. "eth-mainnet"), or a CAIP-2 identifier (e.g. "eip155:1", "solana:mainnet"). | +| [Never](/wallets/reference/common/type-aliases/Never) | - | +| [QueryParams](/wallets/reference/common/type-aliases/QueryParams) | A query-params object; array values serialize as repeated keys. | +| [QueryValue](/wallets/reference/common/type-aliases/QueryValue) | Values the query serializer accepts (null/undefined entries are skipped). | +| [ResolvedNetwork](/wallets/reference/common/type-aliases/ResolvedNetwork) | A resolved network: the Alchemy slug used for URL construction and REST payloads, plus the numeric chain ID when one exists (EVM only). | +| [RestRequestFn](/wallets/reference/common/type-aliases/RestRequestFn) | - | +| [RestRequestOptions](/wallets/reference/common/type-aliases/RestRequestOptions) | Per-request runtime options; values override the client-level defaults. | +| [RestRequestParams](/wallets/reference/common/type-aliases/RestRequestParams) | - | +| [RestRequestSchema](/wallets/reference/common/type-aliases/RestRequestSchema) | - | ## Variables @@ -78,17 +92,20 @@ MIT ## Functions -| Function | Description | -| :----------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [alchemyTransport](/wallets/reference/common/functions/alchemyTransport) | Creates an Alchemy HTTP transport for connecting to Alchemy's services. | -| [assertNever](/wallets/reference/common/functions/assertNever) | Asserts that a value is never. | -| [bigIntMax](/wallets/reference/common/functions/bigIntMax) | Returns the max bigint in a list of bigints | -| [bigIntMultiply](/wallets/reference/common/functions/bigIntMultiply) | Given a bigint and a number (which can be a float), returns the bigint value. Note: this function has loss and will round down to the nearest integer. | -| [getAlchemyRpcUrl](/wallets/reference/common/functions/getAlchemyRpcUrl) | Gets the Alchemy RPC base URL for a given chain ID. | -| [getSupportedChainIds](/wallets/reference/common/functions/getSupportedChainIds) | Gets all supported chain IDs from the registry. | -| [isAlchemyConnectionConfig](/wallets/reference/common/functions/isAlchemyConnectionConfig) | Type guard to check if a value is a valid Alchemy connection config. | -| [isAlchemyTransport](/wallets/reference/common/functions/isAlchemyTransport) | A type guard for the transport to determine if it is an Alchemy transport. Used in cases where we would like to do switching depending on the transport. | -| [isChainSupported](/wallets/reference/common/functions/isChainSupported) | Checks if a chain ID is supported by the Alchemy RPC registry. | -| [lowerAddress](/wallets/reference/common/functions/lowerAddress) | Lowercase an address | -| [raise](/wallets/reference/common/functions/raise) | Raises an error. | -| [validateAlchemyConnectionConfig](/wallets/reference/common/functions/validateAlchemyConnectionConfig) | Validates an Alchemy connection configuration object. | +| Function | Description | +| :----------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [alchemyTransport](/wallets/reference/common/functions/alchemyTransport) | Creates an Alchemy HTTP transport for connecting to Alchemy's services. | +| [assertNever](/wallets/reference/common/functions/assertNever) | Asserts that a value is never. | +| [bigIntMax](/wallets/reference/common/functions/bigIntMax) | Returns the max bigint in a list of bigints | +| [bigIntMultiply](/wallets/reference/common/functions/bigIntMultiply) | Given a bigint and a number (which can be a float), returns the bigint value. Note: this function has loss and will round down to the nearest integer. | +| [composeSignals](/wallets/reference/common/functions/composeSignals) | Combines multiple abort signals into one that aborts when any input aborts. Uses AbortSignal.any where available, with an addEventListener fallback. | +| [getAlchemyRpcUrl](/wallets/reference/common/functions/getAlchemyRpcUrl) | Gets the Alchemy RPC base URL for a given chain ID. | +| [getSupportedChainIds](/wallets/reference/common/functions/getSupportedChainIds) | Gets all supported chain IDs from the registry. | +| [isAlchemyConnectionConfig](/wallets/reference/common/functions/isAlchemyConnectionConfig) | Type guard to check if a value is a valid Alchemy connection config. | +| [isAlchemyTransport](/wallets/reference/common/functions/isAlchemyTransport) | A type guard for the transport to determine if it is an Alchemy transport. Used in cases where we would like to do switching depending on the transport. | +| [isChainSupported](/wallets/reference/common/functions/isChainSupported) | Checks if a chain ID is supported by the Alchemy RPC registry. | +| [lowerAddress](/wallets/reference/common/functions/lowerAddress) | Lowercase an address | +| [raise](/wallets/reference/common/functions/raise) | Raises an error. | +| [resolveNetwork](/wallets/reference/common/functions/resolveNetwork) | Resolves any accepted network input — viem Chain, Alchemy network slug, or CAIP-2 identifier — to the Alchemy network slug (and chain ID when one exists). All three forms resolve against the same daikon-generated registry. | +| [sleep](/wallets/reference/common/functions/sleep) | Waits for a duration, rejecting immediately with the signal's reason if the signal aborts first. | +| [validateAlchemyConnectionConfig](/wallets/reference/common/functions/validateAlchemyConnectionConfig) | Validates an Alchemy connection configuration object. | diff --git a/docs/pages/reference/common/src/classes/AlchemyApiError.mdx b/docs/pages/reference/common/src/classes/AlchemyApiError.mdx new file mode 100644 index 0000000000..74272e42c8 --- /dev/null +++ b/docs/pages/reference/common/src/classes/AlchemyApiError.mdx @@ -0,0 +1,219 @@ +--- +title: AlchemyApiError +description: "The normalized error family for Alchemy API failures. Both the REST channel (AlchemyRestClient → ServerError/FetchError, which extend this class) and SDK JSON-RPC actions surface failures as AlchemyApiError, so consumers can handle status/code/requestId/retryAfter uniformly: ```ts try { ... } catch (e) { if (e instanceof AlchemyApiError && e.status === 429) { await sleep(e.retryAfter ?? 1_000); } } ```" +slug: wallets/reference/common/classes/AlchemyApiError +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +Defined in: [packages/common/src/errors/AlchemyApiError.ts:29](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/AlchemyApiError.ts#L29) + +The normalized error family for Alchemy API failures. Both the REST channel +(AlchemyRestClient → ServerError/FetchError, which extend this class) and +SDK JSON-RPC actions surface failures as AlchemyApiError, so consumers can +handle status/code/requestId/retryAfter uniformly: + +```ts +try { ... } catch (e) { + if (e instanceof AlchemyApiError && e.status === 429) { + await sleep(e.retryAfter ?? 1_000); + } +} +``` + +## Extends + +- [`BaseError`](BaseError) + +## Extended by + +- [`FetchError`](FetchError) +- [`ServerError`](ServerError) + +## Constructors + +### Constructor + +```ts +new AlchemyApiError(shortMessage, args?): AlchemyApiError; +``` + +Defined in: [packages/common/src/errors/AlchemyApiError.ts:47](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/AlchemyApiError.ts#L47) + +Creates a normalized API error. + +#### Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `shortMessage` + + `string` + + The headline error message +
+ `args?` + + [`AlchemyApiErrorDetails`](../type-aliases/AlchemyApiErrorDetails) & `object` + + Failure metadata plus BaseError options +
+ +#### Returns + +`AlchemyApiError` + +#### Overrides + +[`BaseError`](BaseError).[`constructor`](BaseError#constructor) + +## Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDefault valueDescription
+ `code?` + + `string` | `number` + + `undefined` + + Provider error code: JSON-RPC `error.code` or a REST error-body code. +
+ `name` + + `string` + + `"AlchemyApiError"` + + ‐ +
+ `requestId?` + + `string` + + `undefined` + + The client-generated X-Alchemy-Client-Request-Id sent with the request. +
+ `retryAfter?` + + `number` + + `undefined` + + Parsed Retry-After hint in milliseconds, when the server provided one. +
+ `status?` + + `number` + + `undefined` + + HTTP status code, when the failure was an HTTP response. +
+ `version` + + `string` + + `VERSION` + + ‐ +
diff --git a/docs/pages/reference/common/src/classes/AlchemyRestClient.mdx b/docs/pages/reference/common/src/classes/AlchemyRestClient.mdx new file mode 100644 index 0000000000..f6c7b4a980 --- /dev/null +++ b/docs/pages/reference/common/src/classes/AlchemyRestClient.mdx @@ -0,0 +1,115 @@ +--- +title: AlchemyRestClient +description: A client for making requests to Alchemy's non-JSON-RPC endpoints, with typed routes/bodies/queries (via a RestRequestSchema), bounded retries with exponential backoff (429/5xx/network only, honoring Retry-After), per-attempt timeouts, abort support, and a per-request idempotency id sent as X-Alchemy-Client-Request-Id and surfaced on thrown errors. +slug: wallets/reference/common/classes/AlchemyRestClient +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +Defined in: [packages/common/src/rest/restClient.ts:101](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/restClient.ts#L101) + +A client for making requests to Alchemy's non-JSON-RPC endpoints, with +typed routes/bodies/queries (via a RestRequestSchema), bounded retries with +exponential backoff (429/5xx/network only, honoring Retry-After), +per-attempt timeouts, abort support, and a per-request idempotency id sent +as X-Alchemy-Client-Request-Id and surfaced on thrown errors. + +## Type Parameters + + + + + + + + + + + + + +
Type Parameter
+ `Schema` *extends* [`RestRequestSchema`](../type-aliases/RestRequestSchema) +
+ +## Constructors + +### Constructor + +```ts +new AlchemyRestClient(params): AlchemyRestClient; +``` + +Defined in: [packages/common/src/rest/restClient.ts:113](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/restClient.ts#L113) + +Creates a new instance of AlchemyRestClient. + +#### Parameters + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `params` + + [`AlchemyRestClientParams`](../type-aliases/AlchemyRestClientParams) + + The parameters for configuring the client, including API key, JWT, custom URL, headers, and retry/timeout defaults. +
+ +#### Returns + +`AlchemyRestClient`\<`Schema`> + +## Properties + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ `request` + + [`RestRequestFn`](../type-aliases/RestRequestFn)\<`Schema`> + + Makes an HTTP request to an Alchemy non-JSON-RPC endpoint. Retries + 429/5xx/network failures with exponential backoff (honoring Retry-After); + other statuses throw immediately. A caller-initiated abort is never + retried. + + **Param** + + The parameters for the request +
diff --git a/docs/pages/reference/common/src/classes/BaseError.mdx b/docs/pages/reference/common/src/classes/BaseError.mdx index 41d41c8939..3b4dc5e5ce 100644 --- a/docs/pages/reference/common/src/classes/BaseError.mdx +++ b/docs/pages/reference/common/src/classes/BaseError.mdx @@ -19,11 +19,10 @@ we want the errors here to point to our docs if we supply a docsPath though ## Extended by +- [`AlchemyApiError`](AlchemyApiError) - [`ChainNotFoundError`](ChainNotFoundError) - [`AccountNotFoundError`](AccountNotFoundError) - [`ConnectionConfigError`](ConnectionConfigError) -- [`FetchError`](FetchError) -- [`ServerError`](ServerError) - [`InvalidRequestError`](InvalidRequestError) - [`MethodUnsupportedError`](MethodUnsupportedError) diff --git a/docs/pages/reference/common/src/classes/FetchError.mdx b/docs/pages/reference/common/src/classes/FetchError.mdx index aaa76be35b..6cf6e9b1da 100644 --- a/docs/pages/reference/common/src/classes/FetchError.mdx +++ b/docs/pages/reference/common/src/classes/FetchError.mdx @@ -7,13 +7,13 @@ layout: reference {/* This file is auto-generated by TypeDoc. Do not edit manually. */} -Defined in: [packages/common/src/errors/FetchError.ts:6](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/FetchError.ts#L6) +Defined in: [packages/common/src/errors/FetchError.ts:9](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/FetchError.ts#L9) Error class representing a "Fetch Error" error, typically thrown when a fetch request fails. ## Extends -- [`BaseError`](BaseError) +- [`AlchemyApiError`](AlchemyApiError) ## Constructors @@ -23,12 +23,13 @@ Error class representing a "Fetch Error" error, typically thrown when a fetch re new FetchError( route, method, - cause?): FetchError; + cause?, + apiDetails?): FetchError; ``` -Defined in: [packages/common/src/errors/FetchError.ts:16](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/FetchError.ts#L16) +Defined in: [packages/common/src/errors/FetchError.ts:20](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/FetchError.ts#L20) -Initializes a new instance of the error message with a default message indicating that no chain was supplied to the client. +Initializes a new fetch error for a request that failed before a response. #### Parameters @@ -84,6 +85,20 @@ Initializes a new instance of the error message with a default message indicatin + + + `apiDetails?` + + + + [`AlchemyApiErrorDetails`](../type-aliases/AlchemyApiErrorDetails) + + + + Normalized failure metadata (requestId). + + + @@ -93,7 +108,7 @@ Initializes a new instance of the error message with a default message indicatin #### Overrides -[`BaseError`](BaseError).[`constructor`](BaseError#constructor) +[`AlchemyApiError`](AlchemyApiError).[`constructor`](AlchemyApiError#constructor) ## Properties @@ -103,10 +118,29 @@ Initializes a new instance of the error message with a default message indicatin Property Type Default value + Description + + + `code?` + + + + `string` | `number` + + + + `undefined` + + + + Provider error code: JSON-RPC `error.code` or a REST error-body code. + + + `name` @@ -119,6 +153,64 @@ Initializes a new instance of the error message with a default message indicatin `"FetchError"` + + + ‐ + + + + + + `requestId?` + + + + `string` + + + + `undefined` + + + + The client-generated X-Alchemy-Client-Request-Id sent with the request. + + + + + + `retryAfter?` + + + + `number` + + + + `undefined` + + + + Parsed Retry-After hint in milliseconds, when the server provided one. + + + + + + `status?` + + + + `number` + + + + `undefined` + + + + HTTP status code, when the failure was an HTTP response. + @@ -133,6 +225,10 @@ Initializes a new instance of the error message with a default message indicatin `VERSION` + + + ‐ + diff --git a/docs/pages/reference/common/src/classes/ServerError.mdx b/docs/pages/reference/common/src/classes/ServerError.mdx index a326a3378d..ae246e9c6e 100644 --- a/docs/pages/reference/common/src/classes/ServerError.mdx +++ b/docs/pages/reference/common/src/classes/ServerError.mdx @@ -7,13 +7,13 @@ layout: reference {/* This file is auto-generated by TypeDoc. Do not edit manually. */} -Defined in: [packages/common/src/errors/ServerError.ts:6](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/ServerError.ts#L6) +Defined in: [packages/common/src/errors/ServerError.ts:9](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/ServerError.ts#L9) Error class representing a "Server Error" error, typically thrown when a server request fails. ## Extends -- [`BaseError`](BaseError) +- [`AlchemyApiError`](AlchemyApiError) ## Constructors @@ -23,12 +23,13 @@ Error class representing a "Server Error" error, typically thrown when a server new ServerError( message, status, - cause?): ServerError; + cause?, + apiDetails?): ServerError; ``` -Defined in: [packages/common/src/errors/ServerError.ts:16](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/ServerError.ts#L16) +Defined in: [packages/common/src/errors/ServerError.ts:20](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/ServerError.ts#L20) -Initializes a new instance of the error message with a default message indicating that no chain was supplied to the client. +Initializes a new server error for a failed HTTP response. #### Parameters @@ -84,6 +85,20 @@ Initializes a new instance of the error message with a default message indicatin + + + `apiDetails?` + + + + [`AlchemyApiErrorDetails`](../type-aliases/AlchemyApiErrorDetails) + + + + Normalized failure metadata (requestId, retryAfter, code). + + + @@ -93,7 +108,7 @@ Initializes a new instance of the error message with a default message indicatin #### Overrides -[`BaseError`](BaseError).[`constructor`](BaseError#constructor) +[`AlchemyApiError`](AlchemyApiError).[`constructor`](AlchemyApiError#constructor) ## Properties @@ -103,10 +118,29 @@ Initializes a new instance of the error message with a default message indicatin Property Type Default value + Description + + + `code?` + + + + `string` | `number` + + + + `undefined` + + + + Provider error code: JSON-RPC `error.code` or a REST error-body code. + + + `name` @@ -119,6 +153,64 @@ Initializes a new instance of the error message with a default message indicatin `"ServerError"` + + + ‐ + + + + + + `requestId?` + + + + `string` + + + + `undefined` + + + + The client-generated X-Alchemy-Client-Request-Id sent with the request. + + + + + + `retryAfter?` + + + + `number` + + + + `undefined` + + + + Parsed Retry-After hint in milliseconds, when the server provided one. + + + + + + `status?` + + + + `number` + + + + `undefined` + + + + HTTP status code, when the failure was an HTTP response. + @@ -133,6 +225,10 @@ Initializes a new instance of the error message with a default message indicatin `VERSION` + + + ‐ + diff --git a/docs/pages/reference/common/src/functions/composeSignals.mdx b/docs/pages/reference/common/src/functions/composeSignals.mdx new file mode 100644 index 0000000000..e148bcc037 --- /dev/null +++ b/docs/pages/reference/common/src/functions/composeSignals.mdx @@ -0,0 +1,52 @@ +--- +title: composeSignals +description: Overview of the composeSignals function +slug: wallets/reference/common/functions/composeSignals +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function composeSignals(...signals): undefined | AbortSignal; +``` + +Defined in: [packages/common/src/utils/signals.ts:8](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/utils/signals.ts#L8) + +Combines multiple abort signals into one that aborts when any input aborts. +Uses AbortSignal.any where available, with an addEventListener fallback. + +## Parameters + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ ...`signals` + + (`undefined` | `AbortSignal`)\[] + + Signals to combine (undefined entries are skipped) +
+ +## Returns + +`undefined` | `AbortSignal` + +The combined signal, or undefined if none were provided diff --git a/docs/pages/reference/common/src/functions/resolveNetwork.mdx b/docs/pages/reference/common/src/functions/resolveNetwork.mdx new file mode 100644 index 0000000000..c5d2694abf --- /dev/null +++ b/docs/pages/reference/common/src/functions/resolveNetwork.mdx @@ -0,0 +1,63 @@ +--- +title: resolveNetwork +description: Overview of the resolveNetwork function +slug: wallets/reference/common/functions/resolveNetwork +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function resolveNetwork(input): ResolvedNetwork; +``` + +Defined in: [packages/common/src/networks/networkRegistry.ts:93](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/networks/networkRegistry.ts#L93) + +Resolves any accepted network input — viem Chain, Alchemy network slug, or +CAIP-2 identifier — to the Alchemy network slug (and chain ID when one +exists). All three forms resolve against the same daikon-generated registry. + +## Example + +```ts +import { mainnet } from "viem/chains"; +resolveNetwork(mainnet); // { slug: "eth-mainnet", chainId: 1 } +resolveNetwork("eth-mainnet"); // { slug: "eth-mainnet", chainId: 1 } +resolveNetwork("eip155:1"); // { slug: "eth-mainnet", chainId: 1 } +resolveNetwork("solana:mainnet"); // { slug: "solana-mainnet" } +``` + +## Parameters + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `input` + + [`NetworkInput`](../type-aliases/NetworkInput) + + The network to resolve +
+ +## Returns + +[`ResolvedNetwork`](../type-aliases/ResolvedNetwork) + +The resolved slug and optional chain ID diff --git a/docs/pages/reference/common/src/functions/sleep.mdx b/docs/pages/reference/common/src/functions/sleep.mdx new file mode 100644 index 0000000000..155871898f --- /dev/null +++ b/docs/pages/reference/common/src/functions/sleep.mdx @@ -0,0 +1,66 @@ +--- +title: sleep +description: Overview of the sleep function +slug: wallets/reference/common/functions/sleep +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function sleep(ms, signal?): Promise; +``` + +Defined in: [packages/common/src/utils/signals.ts:39](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/utils/signals.ts#L39) + +Waits for a duration, rejecting immediately with the signal's reason if the +signal aborts first. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `ms` + + `number` + + Milliseconds to wait +
+ `signal?` + + `AbortSignal` + + Optional abort signal +
+ +## Returns + +`Promise`\<`void`> + +Resolves after the delay diff --git a/docs/pages/reference/common/src/type-aliases/AlchemyApiErrorDetails.mdx b/docs/pages/reference/common/src/type-aliases/AlchemyApiErrorDetails.mdx new file mode 100644 index 0000000000..8a25e5927c --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/AlchemyApiErrorDetails.mdx @@ -0,0 +1,87 @@ +--- +title: AlchemyApiErrorDetails +description: Normalized failure metadata shared across REST and JSON-RPC channels. +slug: wallets/reference/common/type-aliases/AlchemyApiErrorDetails +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type AlchemyApiErrorDetails = object; +``` + +Defined in: [packages/common/src/errors/AlchemyApiError.ts:4](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/AlchemyApiError.ts#L4) + +Normalized failure metadata shared across REST and JSON-RPC channels. + +## Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ `code?` + + `number` | `string` + + Provider error code: JSON-RPC `error.code` or a REST error-body code. +
+ `requestId?` + + `string` + + The client-generated X-Alchemy-Client-Request-Id sent with the request. +
+ `retryAfter?` + + `number` + + Parsed Retry-After hint in milliseconds, when the server provided one. +
+ `status?` + + `number` + + HTTP status code, when the failure was an HTTP response. +
diff --git a/docs/pages/reference/common/src/type-aliases/AlchemyNetwork.mdx b/docs/pages/reference/common/src/type-aliases/AlchemyNetwork.mdx new file mode 100644 index 0000000000..78145390ee --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/AlchemyNetwork.mdx @@ -0,0 +1,18 @@ +--- +title: AlchemyNetwork +description: An Alchemy network identifier. Known slugs get autocomplete; arbitrary strings are accepted as an escape hatch so new networks work without an SDK release. +slug: wallets/reference/common/type-aliases/AlchemyNetwork +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type AlchemyNetwork = KnownAlchemyNetwork | (string & object); +``` + +Defined in: [packages/common/src/networks/networkRegistry.ts:31](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/networks/networkRegistry.ts#L31) + +An Alchemy network identifier. Known slugs get autocomplete; arbitrary +strings are accepted as an escape hatch so new networks work without an +SDK release. diff --git a/docs/pages/reference/common/src/type-aliases/AlchemyRestClientParams.mdx b/docs/pages/reference/common/src/type-aliases/AlchemyRestClientParams.mdx new file mode 100644 index 0000000000..f9368f5e45 --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/AlchemyRestClientParams.mdx @@ -0,0 +1,129 @@ +--- +title: AlchemyRestClientParams +description: Parameters for creating an AlchemyRestClient instance. +slug: wallets/reference/common/type-aliases/AlchemyRestClientParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type AlchemyRestClientParams = object; +``` + +Defined in: [packages/common/src/rest/restClient.ts:16](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/restClient.ts#L16) + +Parameters for creating an AlchemyRestClient instance. + +## Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ `apiKey?` + + `string` + + API key for Alchemy authentication +
+ `headers?` + + `HeadersInit` + + Custom headers to be sent with requests +
+ `jwt?` + + `string` + + JWT token for Alchemy authentication +
+ `retryCount?` + + `number` + + Max retry attempts after the initial request (default 3; retries 429/5xx/network only) +
+ `retryDelay?` + + `number` + + Base backoff delay in ms, doubled per attempt (default 150) +
+ `timeout?` + + `number` + + Per-attempt timeout in ms (default 10000) +
+ `url?` + + `string` + + Custom URL (optional - defaults to Alchemy's chain-agnostic URL, but can be used to override it) +
diff --git a/docs/pages/reference/common/src/type-aliases/KnownAlchemyNetwork.mdx b/docs/pages/reference/common/src/type-aliases/KnownAlchemyNetwork.mdx new file mode 100644 index 0000000000..0cf69c5d59 --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/KnownAlchemyNetwork.mdx @@ -0,0 +1,32 @@ +--- +title: KnownAlchemyNetwork +description: "Known Alchemy network slugs for autocomplete. TODO(data-sdk): generate this union from daikon via the ws-tools CLI in the same pass that generates ALCHEMY_RPC_MAPPING, so it is never hand-maintained. This subset exists to prove the MVP only." +slug: wallets/reference/common/type-aliases/KnownAlchemyNetwork +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type KnownAlchemyNetwork = + | "eth-mainnet" + | "eth-sepolia" + | "base-mainnet" + | "base-sepolia" + | "polygon-mainnet" + | "polygon-amoy" + | "arb-mainnet" + | "arb-sepolia" + | "opt-mainnet" + | "opt-sepolia" + | "solana-mainnet" + | "solana-devnet"; +``` + +Defined in: [packages/common/src/networks/networkRegistry.ts:12](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/networks/networkRegistry.ts#L12) + +Known Alchemy network slugs for autocomplete. + +TODO(data-sdk): generate this union from daikon via the ws-tools CLI in the +same pass that generates ALCHEMY_RPC_MAPPING, so it is never hand-maintained. +This subset exists to prove the MVP only. diff --git a/docs/pages/reference/common/src/type-aliases/NetworkInput.mdx b/docs/pages/reference/common/src/type-aliases/NetworkInput.mdx new file mode 100644 index 0000000000..e2c2cd6f13 --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/NetworkInput.mdx @@ -0,0 +1,18 @@ +--- +title: NetworkInput +description: 'Any accepted network input: a viem Chain, an Alchemy network slug (e.g. "eth-mainnet"), or a CAIP-2 identifier (e.g. "eip155:1", "solana:mainnet").' +slug: wallets/reference/common/type-aliases/NetworkInput +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type NetworkInput = Chain | AlchemyNetwork; +``` + +Defined in: [packages/common/src/networks/networkRegistry.ts:38](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/networks/networkRegistry.ts#L38) + +Any accepted network input: a viem Chain, an Alchemy network slug +(e.g. "eth-mainnet"), or a CAIP-2 identifier (e.g. "eip155:1", +"solana:mainnet"). diff --git a/docs/pages/reference/common/src/type-aliases/QueryParams.mdx b/docs/pages/reference/common/src/type-aliases/QueryParams.mdx new file mode 100644 index 0000000000..1fbe615516 --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/QueryParams.mdx @@ -0,0 +1,16 @@ +--- +title: QueryParams +description: A query-params object; array values serialize as repeated keys. +slug: wallets/reference/common/type-aliases/QueryParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type QueryParams = Record; +``` + +Defined in: [packages/common/src/rest/types.ts:7](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L7) + +A query-params object; array values serialize as repeated keys. diff --git a/docs/pages/reference/common/src/type-aliases/QueryValue.mdx b/docs/pages/reference/common/src/type-aliases/QueryValue.mdx new file mode 100644 index 0000000000..f4a7b99373 --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/QueryValue.mdx @@ -0,0 +1,16 @@ +--- +title: QueryValue +description: Values the query serializer accepts (null/undefined entries are skipped). +slug: wallets/reference/common/type-aliases/QueryValue +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type QueryValue = string | number | boolean | null | undefined; +``` + +Defined in: [packages/common/src/rest/types.ts:4](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L4) + +Values the query serializer accepts (null/undefined entries are skipped). diff --git a/docs/pages/reference/common/src/type-aliases/ResolvedNetwork.mdx b/docs/pages/reference/common/src/type-aliases/ResolvedNetwork.mdx new file mode 100644 index 0000000000..bcb18e80fa --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/ResolvedNetwork.mdx @@ -0,0 +1,51 @@ +--- +title: ResolvedNetwork +description: "A resolved network: the Alchemy slug used for URL construction and REST payloads, plus the numeric chain ID when one exists (EVM only)." +slug: wallets/reference/common/type-aliases/ResolvedNetwork +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type ResolvedNetwork = object; +``` + +Defined in: [packages/common/src/networks/networkRegistry.ts:44](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/networks/networkRegistry.ts#L44) + +A resolved network: the Alchemy slug used for URL construction and REST +payloads, plus the numeric chain ID when one exists (EVM only). + +## Properties + + + + + + + + + + + + + + + + + + + + + + + +
PropertyType
+ `chainId?` + + `number` +
+ `slug` + + `string` +
diff --git a/docs/pages/reference/common/src/type-aliases/RestRequestFn.mdx b/docs/pages/reference/common/src/type-aliases/RestRequestFn.mdx new file mode 100644 index 0000000000..628b472429 --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/RestRequestFn.mdx @@ -0,0 +1,104 @@ +--- +title: RestRequestFn +description: Overview of RestRequestFn +slug: wallets/reference/common/type-aliases/RestRequestFn +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type RestRequestFn = <_parameters, _returnType>( + params, +) => Promise<_returnType>; +``` + +Defined in: [packages/common/src/rest/types.ts:69](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L69) + +## Type Parameters + + + + + + + + + + + + + + + + + +
Type ParameterDefault type
+ `Schema` *extends* [`RestRequestSchema`](RestRequestSchema) | `undefined` + + `undefined` +
+ +## Type Parameters + + + + + + + + + + + + + + + + + + + + + + + +
Type ParameterDefault type
+ `_parameters` *extends* [`RestRequestParams`](RestRequestParams)\<`Schema`> + + [`RestRequestParams`](RestRequestParams)\<`Schema`> +
+ `_returnType` + + `Schema` *extends* [`RestRequestSchema`](RestRequestSchema) ? `Extract`\<`Schema`\[`number`], \{ + `Route`: `_parameters`\[`"route"`]; + }>\[`"Response"`] : `unknown` +
+ +## Parameters + + + + + + + + + + + + + + + + + +
ParameterType
+ `params` + + `_parameters` +
+ +## Returns + +`Promise`\<`_returnType`> diff --git a/docs/pages/reference/common/src/type-aliases/RestRequestOptions.mdx b/docs/pages/reference/common/src/type-aliases/RestRequestOptions.mdx new file mode 100644 index 0000000000..9684d008a5 --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/RestRequestOptions.mdx @@ -0,0 +1,87 @@ +--- +title: RestRequestOptions +description: Per-request runtime options; values override the client-level defaults. +slug: wallets/reference/common/type-aliases/RestRequestOptions +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type RestRequestOptions = object; +``` + +Defined in: [packages/common/src/rest/types.ts:18](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L18) + +Per-request runtime options; values override the client-level defaults. + +## Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ `retryCount?` + + `number` + + Max retry attempts after the initial request (client default applies when omitted). +
+ `retryDelay?` + + `number` + + Base backoff delay in ms (client default applies when omitted). +
+ `signal?` + + `AbortSignal` + + Abort the request (and any pending retries). +
+ `timeout?` + + `number` + + Per-attempt timeout in ms (client default applies when omitted). +
diff --git a/docs/pages/reference/common/src/type-aliases/RestRequestParams.mdx b/docs/pages/reference/common/src/type-aliases/RestRequestParams.mdx new file mode 100644 index 0000000000..dd0ae54a18 --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/RestRequestParams.mdx @@ -0,0 +1,38 @@ +--- +title: RestRequestParams +description: Overview of RestRequestParams +slug: wallets/reference/common/type-aliases/RestRequestParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type RestRequestParams = Schema extends RestRequestSchema ? { [K in keyof Schema]: Prettify<{ method: Schema[K] extends Schema[number] ? Schema[K]["Method"] : never; route: Schema[K] extends Schema[number] ? Schema[K]["Route"] : never } & (Schema[K] extends Schema[number] ? Schema[K]["Body"] extends undefined ? { body?: undefined } : { body: (...)[(...)]["Body"] } : never) & (Schema[K] extends Schema[number] ? EntryQuery extends undefined ? { query?: undefined } : undefined extends EntryQuery<(...)[(...)]> ? { query?: EntryQuery<(...)> } : { query: EntryQuery<(...)> } : never) & RestRequestOptions> }[number] : object & RestRequestOptions; +``` + +Defined in: [packages/common/src/rest/types.ts:37](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L37) + +## Type Parameters + + + + + + + + + + + + + + + + + +
Type ParameterDefault type
+ `Schema` *extends* [`RestRequestSchema`](RestRequestSchema) | `undefined` + + `undefined` +
diff --git a/docs/pages/reference/common/src/type-aliases/RestRequestSchema.mdx b/docs/pages/reference/common/src/type-aliases/RestRequestSchema.mdx new file mode 100644 index 0000000000..a536fa02f6 --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/RestRequestSchema.mdx @@ -0,0 +1,14 @@ +--- +title: RestRequestSchema +description: Overview of RestRequestSchema +slug: wallets/reference/common/type-aliases/RestRequestSchema +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type RestRequestSchema = readonly object[]; +``` + +Defined in: [packages/common/src/rest/types.ts:9](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L9) diff --git a/docs/pages/reference/data-apis/src/README.mdx b/docs/pages/reference/data-apis/src/README.mdx new file mode 100644 index 0000000000..ce454ee350 --- /dev/null +++ b/docs/pages/reference/data-apis/src/README.mdx @@ -0,0 +1,204 @@ +--- +title: "@alchemy/data-apis" +description: Overview of data-apis +slug: wallets/reference/data-apis +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +A vertical-slice prototype of the Data APIs SDK, built to prove the architecture +before scaling to the full v1 surface (Portfolio, Prices, NFT, Token, Transfers). + +## What this proves + +One method per seam, not full coverage: + +| Method | Channel | What it demonstrates | +| ------------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| `portfolio.getTokensByAddress` | REST → global `api.g.alchemy.com/data/v1` | Multi-network request bodies via `AlchemyRestClient`; networks are payload, the client's chain is not involved | +| `nft.getNftsForOwner` | REST → `{network}.g.alchemy.com/nft/v3` | Network-scoped endpoint resolution with per-request `network` override falling back to the client default | +| `transfers.getAssetTransfers` | JSON-RPC → `AlchemyTransport` | Plain viem action; network override derives a transport instance from `client.transport.config` | + +Plus the two entry points: + +```ts +// Data-only developers (no viem knowledge required) +const data = createDataClient({ apiKey, network: "eth-mainnet" }); + +// Developers already on a viem client with an Alchemy transport +const client = createClient({ + chain: mainnet, + transport: alchemyTransport({ apiKey }), +}).extend(dataActions); +``` + +Network inputs accept all three formats everywhere, resolved by +`resolveNetwork()` in `@alchemy/common`: a viem `Chain`, an Alchemy slug +(`"eth-mainnet"`), or CAIP-2 (`"eip155:1"`, `"solana:mainnet"`). The slug ↔ +chain-ID mapping is derived from the existing daikon-generated +`ALCHEMY_RPC_MAPPING` — no second registry. + +## Generated internals + +Param/result types are generated from the docs repo's bundled OpenAPI/OpenRPC +specs by `@alchemy/api-codegen` (see that package's README for the +snapshot/generate pipeline). `src/generated/` is committed, machine-owned, and +never re-exported directly: the public types in `src/types.ts` are +hand-reviewed aliases, and `codegen.manifest.ts` maps spec operations to the +generated surface — referencing a renamed/removed spec operation fails +`pnpm generate` loudly. + +## Companion changes in @alchemy/common + +- `networks/networkRegistry.ts`: `resolveNetwork` + network types (slug map + derived from the registry URLs; to be emitted by ws-tools properly) +- `AlchemyRestClient` is now exported (was written for signer v5 but unexported) + +## Deliberately out of scope (tracked in the data SDK scope plan) + +- Rest client hardening: retries, timeouts, request-id, first-class query params +- Pagination iterators, error normalization, the SDK manifest, remaining methods +- ws-tools generator change to emit `{ slug, chainId, caip2 }` entries + + the `KnownAlchemyNetwork` union + +## Interfaces + +| Interface | Description | +| :------------------------------------------------------------------------------------- | :--------------------------------------------------- | +| [PortfolioAddressEntry](/wallets/reference/data-apis/interfaces/PortfolioAddressEntry) | An address paired with the networks to query it on. | +| [PriceAddressEntry](/wallets/reference/data-apis/interfaces/PriceAddressEntry) | A token address paired with the network it lives on. | + +## Type Aliases + +| Type Alias | Description | +| :----------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [AlchemyDataClient](/wallets/reference/data-apis/type-aliases/AlchemyDataClient) | - | +| [AlchemyDataClientOptions](/wallets/reference/data-apis/type-aliases/AlchemyDataClientOptions) | - | +| [AssetTransfer](/wallets/reference/data-apis/type-aliases/AssetTransfer) | - | +| [ComputeRarityParams](/wallets/reference/data-apis/type-aliases/ComputeRarityParams) | - | +| [ComputeRarityResult](/wallets/reference/data-apis/type-aliases/ComputeRarityResult) | - | +| [DataActions](/wallets/reference/data-apis/type-aliases/DataActions) | The namespaced Data API actions attached by the [dataActions](/wallets/reference/data-apis/functions/dataActions) decorator. | +| [DataRpcSchema](/wallets/reference/data-apis/type-aliases/DataRpcSchema) | viem RpcSchema entries for the Data JSON-RPC methods. Attach to a client to get typed `client.request({ method: "alchemy_getAssetTransfers", ... })`. | +| [GetAssetTransfersParams](/wallets/reference/data-apis/type-aliases/GetAssetTransfersParams) | Generated RPC params plus the SDK's network override. | +| [GetAssetTransfersResult](/wallets/reference/data-apis/type-aliases/GetAssetTransfersResult) | The spec result is `oneOf: ["Not Found (null)" string, object]`; the string branch is a docs-spec artifact the SDK deliberately does not surface, so it is collapsed away here (and in [DataRpcSchema](/wallets/reference/data-apis/type-aliases/DataRpcSchema)). | +| [GetCollectionMetadataParams](/wallets/reference/data-apis/type-aliases/GetCollectionMetadataParams) | - | +| [GetCollectionMetadataResult](/wallets/reference/data-apis/type-aliases/GetCollectionMetadataResult) | - | +| [GetCollectionsForOwnerParams](/wallets/reference/data-apis/type-aliases/GetCollectionsForOwnerParams) | - | +| [GetCollectionsForOwnerResult](/wallets/reference/data-apis/type-aliases/GetCollectionsForOwnerResult) | - | +| [GetContractMetadataBatchParams](/wallets/reference/data-apis/type-aliases/GetContractMetadataBatchParams) | - | +| [GetContractMetadataBatchResult](/wallets/reference/data-apis/type-aliases/GetContractMetadataBatchResult) | - | +| [GetContractMetadataParams](/wallets/reference/data-apis/type-aliases/GetContractMetadataParams) | - | +| [GetContractMetadataResult](/wallets/reference/data-apis/type-aliases/GetContractMetadataResult) | - | +| [GetContractsForOwnerParams](/wallets/reference/data-apis/type-aliases/GetContractsForOwnerParams) | - | +| [GetContractsForOwnerResult](/wallets/reference/data-apis/type-aliases/GetContractsForOwnerResult) | - | +| [GetFloorPriceParams](/wallets/reference/data-apis/type-aliases/GetFloorPriceParams) | - | +| [GetFloorPriceResult](/wallets/reference/data-apis/type-aliases/GetFloorPriceResult) | - | +| [GetHistoricalTokenPricesParams](/wallets/reference/data-apis/type-aliases/GetHistoricalTokenPricesParams) | - | +| [GetHistoricalTokenPricesResult](/wallets/reference/data-apis/type-aliases/GetHistoricalTokenPricesResult) | - | +| [GetNftContractsByAddressParams](/wallets/reference/data-apis/type-aliases/GetNftContractsByAddressParams) | - | +| [GetNftContractsByAddressResult](/wallets/reference/data-apis/type-aliases/GetNftContractsByAddressResult) | - | +| [GetNftMetadataBatchParams](/wallets/reference/data-apis/type-aliases/GetNftMetadataBatchParams) | - | +| [GetNftMetadataBatchResult](/wallets/reference/data-apis/type-aliases/GetNftMetadataBatchResult) | - | +| [GetNftMetadataParams](/wallets/reference/data-apis/type-aliases/GetNftMetadataParams) | - | +| [GetNftMetadataResult](/wallets/reference/data-apis/type-aliases/GetNftMetadataResult) | - | +| [GetNftSalesParams](/wallets/reference/data-apis/type-aliases/GetNftSalesParams) | - | +| [GetNftSalesResult](/wallets/reference/data-apis/type-aliases/GetNftSalesResult) | - | +| [GetNftsByAddressParams](/wallets/reference/data-apis/type-aliases/GetNftsByAddressParams) | - | +| [GetNftsByAddressResult](/wallets/reference/data-apis/type-aliases/GetNftsByAddressResult) | - | +| [GetNftsForCollectionParams](/wallets/reference/data-apis/type-aliases/GetNftsForCollectionParams) | - | +| [GetNftsForCollectionResult](/wallets/reference/data-apis/type-aliases/GetNftsForCollectionResult) | - | +| [GetNftsForContractParams](/wallets/reference/data-apis/type-aliases/GetNftsForContractParams) | - | +| [GetNftsForContractResult](/wallets/reference/data-apis/type-aliases/GetNftsForContractResult) | - | +| [GetNftsForOwnerParams](/wallets/reference/data-apis/type-aliases/GetNftsForOwnerParams) | - | +| [GetNftsForOwnerResult](/wallets/reference/data-apis/type-aliases/GetNftsForOwnerResult) | - | +| [GetOwnersForContractParams](/wallets/reference/data-apis/type-aliases/GetOwnersForContractParams) | - | +| [GetOwnersForContractResult](/wallets/reference/data-apis/type-aliases/GetOwnersForContractResult) | - | +| [GetOwnersForNftParams](/wallets/reference/data-apis/type-aliases/GetOwnersForNftParams) | - | +| [GetOwnersForNftResult](/wallets/reference/data-apis/type-aliases/GetOwnersForNftResult) | - | +| [GetSpamContractsParams](/wallets/reference/data-apis/type-aliases/GetSpamContractsParams) | - | +| [GetSpamContractsResult](/wallets/reference/data-apis/type-aliases/GetSpamContractsResult) | - | +| [GetTokenAllowanceParams](/wallets/reference/data-apis/type-aliases/GetTokenAllowanceParams) | - | +| [GetTokenAllowanceResult](/wallets/reference/data-apis/type-aliases/GetTokenAllowanceResult) | - | +| [GetTokenBalancesByAddressParams](/wallets/reference/data-apis/type-aliases/GetTokenBalancesByAddressParams) | - | +| [GetTokenBalancesByAddressResult](/wallets/reference/data-apis/type-aliases/GetTokenBalancesByAddressResult) | - | +| [GetTokenBalancesParams](/wallets/reference/data-apis/type-aliases/GetTokenBalancesParams) | - | +| [GetTokenBalancesResult](/wallets/reference/data-apis/type-aliases/GetTokenBalancesResult) | - | +| [GetTokenMetadataParams](/wallets/reference/data-apis/type-aliases/GetTokenMetadataParams) | - | +| [GetTokenMetadataResult](/wallets/reference/data-apis/type-aliases/GetTokenMetadataResult) | - | +| [GetTokenPricesByAddressParams](/wallets/reference/data-apis/type-aliases/GetTokenPricesByAddressParams) | - | +| [GetTokenPricesByAddressResult](/wallets/reference/data-apis/type-aliases/GetTokenPricesByAddressResult) | - | +| [GetTokenPricesBySymbolParams](/wallets/reference/data-apis/type-aliases/GetTokenPricesBySymbolParams) | Chain-agnostic: token symbols only, no network involved. | +| [GetTokenPricesBySymbolResult](/wallets/reference/data-apis/type-aliases/GetTokenPricesBySymbolResult) | - | +| [GetTokensByAddressParams](/wallets/reference/data-apis/type-aliases/GetTokensByAddressParams) | - | +| [GetTokensByAddressResult](/wallets/reference/data-apis/type-aliases/GetTokensByAddressResult) | - | +| [IsAirdropNftParams](/wallets/reference/data-apis/type-aliases/IsAirdropNftParams) | - | +| [IsAirdropNftResult](/wallets/reference/data-apis/type-aliases/IsAirdropNftResult) | - | +| [IsHolderOfContractParams](/wallets/reference/data-apis/type-aliases/IsHolderOfContractParams) | - | +| [IsHolderOfContractResult](/wallets/reference/data-apis/type-aliases/IsHolderOfContractResult) | - | +| [IsSpamContractParams](/wallets/reference/data-apis/type-aliases/IsSpamContractParams) | - | +| [IsSpamContractResult](/wallets/reference/data-apis/type-aliases/IsSpamContractResult) | - | +| [NftRestSchema](/wallets/reference/data-apis/type-aliases/NftRestSchema) | RestRequestSchema entries for the nft REST API. | +| [PaginateOptions](/wallets/reference/data-apis/type-aliases/PaginateOptions) | Options accepted by the `*Pages` async-iterator actions. | +| [PortfolioRestSchema](/wallets/reference/data-apis/type-aliases/PortfolioRestSchema) | RestRequestSchema entries for the portfolio REST API. | +| [PortfolioToken](/wallets/reference/data-apis/type-aliases/PortfolioToken) | - | +| [PricesRestSchema](/wallets/reference/data-apis/type-aliases/PricesRestSchema) | RestRequestSchema entries for the prices REST API. | +| [RequestOptions](/wallets/reference/data-apis/type-aliases/RequestOptions) | Per-request options accepted by data actions. | +| [SearchContractMetadataParams](/wallets/reference/data-apis/type-aliases/SearchContractMetadataParams) | - | +| [SearchContractMetadataResult](/wallets/reference/data-apis/type-aliases/SearchContractMetadataResult) | - | +| [SummarizeNftAttributesParams](/wallets/reference/data-apis/type-aliases/SummarizeNftAttributesParams) | - | +| [SummarizeNftAttributesResult](/wallets/reference/data-apis/type-aliases/SummarizeNftAttributesResult) | - | + +## Variables + +| Variable | Description | +| :-------------------------------------------------------- | :---------- | +| [VERSION](/wallets/reference/data-apis/variables/VERSION) | - | + +## Functions + +| Function | Description | +| :---------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [computeRarity](/wallets/reference/data-apis/functions/computeRarity) | Computes attribute rarity for a specific NFT. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [createDataClient](/wallets/reference/data-apis/functions/createDataClient) | Creates a Data API client. This is a convenience wrapper over `createClient` + `alchemyTransport` + the [dataActions](/wallets/reference/data-apis/functions/dataActions) decorator — developers already holding a viem client with an Alchemy transport can use `client.extend(dataActions)` instead and get the identical behavior. | +| [dataActions](/wallets/reference/data-apis/functions/dataActions) | A viem client decorator that attaches the Data API actions, grouped by namespace, to any client configured with an Alchemy transport. | +| [getAssetTransfers](/wallets/reference/data-apis/functions/getAssetTransfers) | Fetches historical asset transfers (external, internal, token) for the resolved network via the `alchemy_getAssetTransfers` JSON-RPC method. | +| [getAssetTransfersPages](/wallets/reference/data-apis/functions/getAssetTransfersPages) | Auto-paginating companion to [getAssetTransfers](/wallets/reference/data-apis/functions/getAssetTransfers): yields whole pages until the cursor is exhausted (or `maxPages` is hit), guarding against repeated cursors. Note: JSON-RPC requests run through the client's viem transport, which owns the fetch — the abort signal is honored between pages, not mid-request. | +| [getCollectionMetadata](/wallets/reference/data-apis/functions/getCollectionMetadata) | Fetches metadata for an NFT collection by slug. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getCollectionsForOwner](/wallets/reference/data-apis/functions/getCollectionsForOwner) | Fetches all NFT collections an address holds tokens from. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getCollectionsForOwnerPages](/wallets/reference/data-apis/functions/getCollectionsForOwnerPages) | Auto-paginating companion to [getCollectionsForOwner](/wallets/reference/data-apis/functions/getCollectionsForOwner): yields whole pages until the cursor is exhausted (or `maxPages` is hit), guarding against repeated cursors. Iterate items with an inner loop over each page. | +| [getContractMetadata](/wallets/reference/data-apis/functions/getContractMetadata) | Fetches metadata for an NFT contract. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getContractMetadataBatch](/wallets/reference/data-apis/functions/getContractMetadataBatch) | Fetches metadata for multiple NFT contracts in one call. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getContractsForOwner](/wallets/reference/data-apis/functions/getContractsForOwner) | Fetches all NFT contracts an address holds tokens from. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getContractsForOwnerPages](/wallets/reference/data-apis/functions/getContractsForOwnerPages) | Auto-paginating companion to [getContractsForOwner](/wallets/reference/data-apis/functions/getContractsForOwner): yields whole pages until the cursor is exhausted (or `maxPages` is hit), guarding against repeated cursors. Iterate items with an inner loop over each page. | +| [getFloorPrice](/wallets/reference/data-apis/functions/getFloorPrice) | Fetches marketplace floor prices for an NFT contract or collection. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getHistoricalTokenPrices](/wallets/reference/data-apis/functions/getHistoricalTokenPrices) | Fetches historical prices for a token, identified either by symbol (chain-agnostic) or by network + contract address. Runs against the global Prices API; when a network is given it travels in the request body, so the client's chain is not involved. | +| [getNftContractsByAddress](/wallets/reference/data-apis/functions/getNftContractsByAddress) | Fetches NFT contracts for one or more addresses across one or more networks in a single call. This is a multi-network request against the global Data API: networks travel in the request body, so the client's chain is not involved. | +| [getNftContractsByAddressPages](/wallets/reference/data-apis/functions/getNftContractsByAddressPages) | Auto-paginating companion to [getNftContractsByAddress](/wallets/reference/data-apis/functions/getNftContractsByAddress): yields whole pages until the cursor is exhausted (or `maxPages` is hit), guarding against repeated cursors. Iterate items with an inner loop over each page. | +| [getNftMetadata](/wallets/reference/data-apis/functions/getNftMetadata) | Fetches metadata for a single NFT. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getNftMetadataBatch](/wallets/reference/data-apis/functions/getNftMetadataBatch) | Fetches metadata for up to 100 NFTs in one call. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getNftSales](/wallets/reference/data-apis/functions/getNftSales) | Fetches NFT sales matching the given filters. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getNftSalesPages](/wallets/reference/data-apis/functions/getNftSalesPages) | Auto-paginating companion to [getNftSales](/wallets/reference/data-apis/functions/getNftSales): yields whole pages until the cursor is exhausted (or `maxPages` is hit), guarding against repeated cursors. Iterate items with an inner loop over each page. | +| [getNftsByAddress](/wallets/reference/data-apis/functions/getNftsByAddress) | Fetches NFTs for one or more addresses across one or more networks in a single call. This is a multi-network request against the global Data API: networks travel in the request body, so the client's chain is not involved. | +| [getNftsByAddressPages](/wallets/reference/data-apis/functions/getNftsByAddressPages) | Auto-paginating companion to [getNftsByAddress](/wallets/reference/data-apis/functions/getNftsByAddress): yields whole pages until the cursor is exhausted (or `maxPages` is hit), guarding against repeated cursors. Iterate items with an inner loop over each page. | +| [getNftsForCollection](/wallets/reference/data-apis/functions/getNftsForCollection) | Fetches all NFTs for a given collection (by contract address or collection slug). The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getNftsForCollectionPages](/wallets/reference/data-apis/functions/getNftsForCollectionPages) | Auto-paginating companion to [getNftsForCollection](/wallets/reference/data-apis/functions/getNftsForCollection): yields whole pages until the cursor is exhausted (or `maxPages` is hit), guarding against repeated cursors. Iterate items with an inner loop over each page. | +| [getNftsForContract](/wallets/reference/data-apis/functions/getNftsForContract) | Fetches all NFTs for a given contract. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getNftsForContractPages](/wallets/reference/data-apis/functions/getNftsForContractPages) | Auto-paginating companion to [getNftsForContract](/wallets/reference/data-apis/functions/getNftsForContract): yields whole pages until the cursor is exhausted (or `maxPages` is hit), guarding against repeated cursors. Iterate items with an inner loop over each page. | +| [getNftsForOwner](/wallets/reference/data-apis/functions/getNftsForOwner) | Fetches NFTs owned by an address on a single network. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getNftsForOwnerPages](/wallets/reference/data-apis/functions/getNftsForOwnerPages) | Auto-paginating companion to [getNftsForOwner](/wallets/reference/data-apis/functions/getNftsForOwner): yields whole pages until the cursor is exhausted (or `maxPages` is hit), guarding against repeated cursors. Iterate items with an inner loop over each page. | +| [getOwnersForContract](/wallets/reference/data-apis/functions/getOwnersForContract) | Fetches all owners for an NFT contract. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getOwnersForNft](/wallets/reference/data-apis/functions/getOwnersForNft) | Fetches the owners of a specific NFT. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getSpamContracts](/wallets/reference/data-apis/functions/getSpamContracts) | Fetches the full list of contracts classified as spam on a network. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [getTokenAllowance](/wallets/reference/data-apis/functions/getTokenAllowance) | Fetches the ERC-20 allowance a spender has from an owner via the `alchemy_getTokenAllowance` JSON-RPC method. Without a `network` override this uses the client's transport; with one, a transport instance is derived for the override network. | +| [getTokenBalances](/wallets/reference/data-apis/functions/getTokenBalances) | Fetches ERC-20 (and native) token balances for an address via the `alchemy_getTokenBalances` JSON-RPC method. Without a `network` override this uses the client's transport; with one, a transport instance is derived for the override network. | +| [getTokenBalancesByAddress](/wallets/reference/data-apis/functions/getTokenBalancesByAddress) | Fetches token balances (without metadata or prices) for one or more addresses across one or more networks in a single call. This is a multi-network request against the global Data API: networks travel in the request body, so the client's chain is not involved. | +| [getTokenMetadata](/wallets/reference/data-apis/functions/getTokenMetadata) | Fetches metadata (name, symbol, decimals, logo) for a token contract via the `alchemy_getTokenMetadata` JSON-RPC method. Without a `network` override this uses the client's transport; with one, a transport instance is derived for the override network. | +| [getTokenPricesByAddress](/wallets/reference/data-apis/functions/getTokenPricesByAddress) | Fetches current prices for tokens by contract address (max 25 pairs). This is a multi-network request against the global Prices API: each entry pairs an address with its network, so the client's chain is not involved. | +| [getTokenPricesBySymbol](/wallets/reference/data-apis/functions/getTokenPricesBySymbol) | Fetches current prices for tokens by symbol (max 25). This is a chain-agnostic request against the global Prices API — no network is involved at all. | +| [getTokensByAddress](/wallets/reference/data-apis/functions/getTokensByAddress) | Fetches tokens (with optional metadata and prices) for one or more addresses across one or more networks in a single call. This is a multi-network request against the global Data API: networks travel in the request body, so the client's chain is not involved. | +| [isAirdropNft](/wallets/reference/data-apis/functions/isAirdropNft) | Checks whether an NFT was airdropped to its owner. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [isHolderOfContract](/wallets/reference/data-apis/functions/isHolderOfContract) | Checks whether a wallet holds any NFT from a contract. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [isSpamContract](/wallets/reference/data-apis/functions/isSpamContract) | Checks whether a contract is classified as spam. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [searchContractMetadata](/wallets/reference/data-apis/functions/searchContractMetadata) | Searches NFT contract metadata by keyword. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | +| [summarizeNftAttributes](/wallets/reference/data-apis/functions/summarizeNftAttributes) | Summarizes attribute prevalence for an NFT contract. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | diff --git a/docs/pages/reference/data-apis/src/functions/computeRarity.mdx b/docs/pages/reference/data-apis/src/functions/computeRarity.mdx new file mode 100644 index 0000000000..451a61fd74 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/computeRarity.mdx @@ -0,0 +1,89 @@ +--- +title: computeRarity +description: Overview of the computeRarity function +slug: wallets/reference/data-apis/functions/computeRarity +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function computeRarity( + client, + params, + options?, +): Promise<{ + rarities?: object[]; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/computeRarity.ts:21](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/computeRarity.ts#L21) + +Computes attribute rarity for a specific NFT. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`ComputeRarityParams`](../type-aliases/ComputeRarityParams) + + Contract address, token id, and optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`rarities?`: `object`\[]; +}> + +Rarity scores per attribute diff --git a/docs/pages/reference/data-apis/src/functions/createDataClient.mdx b/docs/pages/reference/data-apis/src/functions/createDataClient.mdx new file mode 100644 index 0000000000..5b239f6298 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/createDataClient.mdx @@ -0,0 +1,67 @@ +--- +title: createDataClient +description: Overview of the createDataClient function +slug: wallets/reference/data-apis/functions/createDataClient +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function createDataClient(options): AlchemyDataClient; +``` + +Defined in: [packages/data-apis/src/client.ts:49](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/client.ts#L49) + +Creates a Data API client. This is a convenience wrapper over +`createClient` + `alchemyTransport` + the [dataActions](dataActions) decorator — +developers already holding a viem client with an Alchemy transport can use +`client.extend(dataActions)` instead and get the identical behavior. + +## Example + +```ts +import { createDataClient } from "@alchemy/data-apis"; + +const data = createDataClient({ + apiKey: process.env.ALCHEMY_API_KEY, + network: "eth-mainnet", // or `mainnet` from viem/chains, or "eip155:1" +}); + +const nfts = await data.nft.getNftsForOwner({ owner: "0x..." }); +``` + +## Parameters + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `options` + + [`AlchemyDataClientOptions`](../type-aliases/AlchemyDataClientOptions) + + Auth (apiKey/jwt/proxy url) and default network +
+ +## Returns + +[`AlchemyDataClient`](../type-aliases/AlchemyDataClient) + +A viem client extended with the Data API actions diff --git a/docs/pages/reference/data-apis/src/functions/dataActions.mdx b/docs/pages/reference/data-apis/src/functions/dataActions.mdx new file mode 100644 index 0000000000..2e331ef7f0 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/dataActions.mdx @@ -0,0 +1,68 @@ +--- +title: dataActions +description: Overview of the dataActions function +slug: wallets/reference/data-apis/functions/dataActions +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function dataActions(client): DataActions; +``` + +Defined in: [packages/data-apis/src/decorator.ts:236](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/decorator.ts#L236) + +A viem client decorator that attaches the Data API actions, grouped by +namespace, to any client configured with an Alchemy transport. + +## Example + +```ts +import { createClient } from "viem"; +import { mainnet } from "viem/chains"; +import { alchemyTransport } from "@alchemy/common"; +import { dataActions } from "@alchemy/data-apis"; + +const client = createClient({ + chain: mainnet, + transport: alchemyTransport({ apiKey: "..." }), +}).extend(dataActions); + +const nfts = await client.nft.getNftsForOwner({ owner: "0x..." }); +``` + +## Parameters + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + The client to decorate +
+ +## Returns + +[`DataActions`](../type-aliases/DataActions) + +The namespaced Data API actions diff --git a/docs/pages/reference/data-apis/src/functions/getAssetTransfers.mdx b/docs/pages/reference/data-apis/src/functions/getAssetTransfers.mdx new file mode 100644 index 0000000000..0827b8b112 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getAssetTransfers.mdx @@ -0,0 +1,80 @@ +--- +title: getAssetTransfers +description: Overview of the getAssetTransfers function +slug: wallets/reference/data-apis/functions/getAssetTransfers +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getAssetTransfers( + client, + params, +): Promise<{ + pageKey?: string; + transfers?: object[]; +}>; +``` + +Defined in: [packages/data-apis/src/actions/transfers/getAssetTransfers.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/transfers/getAssetTransfers.ts#L24) + +Fetches historical asset transfers (external, internal, token) for the +resolved network via the `alchemy_getAssetTransfers` JSON-RPC method. + +Without a `network` override this is a plain viem action over the client's +Alchemy transport. With an override, a transport instance is derived from +the client's transport config and pointed at the override network's RPC URL +— the same mechanism the transport's tracing support uses. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetAssetTransfersParams`](../type-aliases/GetAssetTransfersParams) + + Transfer filters and optional network override +
+ +## Returns + +`Promise`\<\{ +`pageKey?`: `string`; +`transfers?`: `object`\[]; +}> + +The matching transfers and pagination cursor diff --git a/docs/pages/reference/data-apis/src/functions/getAssetTransfersPages.mdx b/docs/pages/reference/data-apis/src/functions/getAssetTransfersPages.mdx new file mode 100644 index 0000000000..7c3616f3bb --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getAssetTransfersPages.mdx @@ -0,0 +1,97 @@ +--- +title: getAssetTransfersPages +description: Overview of the getAssetTransfersPages function +slug: wallets/reference/data-apis/functions/getAssetTransfersPages +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getAssetTransfersPages( + client, + params, + options?, +): AsyncGenerator< + { + pageKey?: string; + transfers?: object[]; + }, + void, + undefined +>; +``` + +Defined in: [packages/data-apis/src/actions/transfers/getAssetTransfersPages.ts:21](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/transfers/getAssetTransfersPages.ts#L21) + +Auto-paginating companion to [getAssetTransfers](getAssetTransfers): yields whole pages +until the cursor is exhausted (or `maxPages` is hit), guarding against +repeated cursors. Note: JSON-RPC requests run through the client's viem +transport, which owns the fetch — the abort signal is honored between +pages, not mid-request. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetAssetTransfersParams`](../type-aliases/GetAssetTransfersParams) + + Same params as getAssetTransfers (the cursor is managed for you) +
+ `options?` + + [`PaginateOptions`](../type-aliases/PaginateOptions) + + Abort signal (checked between pages) and page cap +
+ +## Returns + +`AsyncGenerator`\<\{ +`pageKey?`: `string`; +`transfers?`: `object`\[]; +}, `void`, `undefined`> + +Pages, in order diff --git a/docs/pages/reference/data-apis/src/functions/getCollectionMetadata.mdx b/docs/pages/reference/data-apis/src/functions/getCollectionMetadata.mdx new file mode 100644 index 0000000000..100c03a4cb --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getCollectionMetadata.mdx @@ -0,0 +1,109 @@ +--- +title: getCollectionMetadata +description: Overview of the getCollectionMetadata function +slug: wallets/reference/data-apis/functions/getCollectionMetadata +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getCollectionMetadata( + client, + params, + options?, +): Promise<{ + description?: string; + discordUrl?: string; + externalUrl?: string; + floorPrice?: { + floorPrice?: number; + marketplace?: string; + priceCurrency?: string; + }; + name?: string; + slug?: string; + twitterUsername?: string; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getCollectionMetadata.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getCollectionMetadata.ts#L24) + +Fetches metadata for an NFT collection by slug. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetCollectionMetadataParams`](../type-aliases/GetCollectionMetadataParams) + + Collection slug and optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`description?`: `string`; +`discordUrl?`: `string`; +`externalUrl?`: `string`; +`floorPrice?`: \{ +`floorPrice?`: `number`; +`marketplace?`: `string`; +`priceCurrency?`: `string`; +}; +`name?`: `string`; +`slug?`: `string`; +`twitterUsername?`: `string`; +}> + +The collection's metadata diff --git a/docs/pages/reference/data-apis/src/functions/getCollectionsForOwner.mdx b/docs/pages/reference/data-apis/src/functions/getCollectionsForOwner.mdx new file mode 100644 index 0000000000..9207e8d305 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getCollectionsForOwner.mdx @@ -0,0 +1,93 @@ +--- +title: getCollectionsForOwner +description: Overview of the getCollectionsForOwner function +slug: wallets/reference/data-apis/functions/getCollectionsForOwner +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getCollectionsForOwner( + client, + params, + options?, +): Promise<{ + collections?: object[]; + pageKey?: string; + totalCount?: string; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getCollectionsForOwner.ts:26](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getCollectionsForOwner.ts#L26) + +Fetches all NFT collections an address holds tokens from. The network is +resolved per request: an explicit `network` param wins, otherwise the +client's configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetCollectionsForOwnerParams`](../type-aliases/GetCollectionsForOwnerParams) + + Owner address, optional network override, and filters +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`collections?`: `object`\[]; +`pageKey?`: `string`; +`totalCount?`: `string`; +}> + +The owned collections and pagination cursor diff --git a/docs/pages/reference/data-apis/src/functions/getCollectionsForOwnerPages.mdx b/docs/pages/reference/data-apis/src/functions/getCollectionsForOwnerPages.mdx new file mode 100644 index 0000000000..59565ddc4c --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getCollectionsForOwnerPages.mdx @@ -0,0 +1,97 @@ +--- +title: getCollectionsForOwnerPages +description: Overview of the getCollectionsForOwnerPages function +slug: wallets/reference/data-apis/functions/getCollectionsForOwnerPages +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getCollectionsForOwnerPages( + client, + params, + options?, +): AsyncGenerator< + { + collections?: object[]; + pageKey?: string; + totalCount?: string; + }, + void, + undefined +>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getCollectionsForOwnerPages.ts:19](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getCollectionsForOwnerPages.ts#L19) + +Auto-paginating companion to [getCollectionsForOwner](getCollectionsForOwner): yields whole pages until the +cursor is exhausted (or `maxPages` is hit), guarding against repeated +cursors. Iterate items with an inner loop over each page. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetCollectionsForOwnerParams`](../type-aliases/GetCollectionsForOwnerParams) + + Same params as getCollectionsForOwner (the cursor is managed for you) +
+ `options?` + + [`PaginateOptions`](../type-aliases/PaginateOptions) + + Abort signal and page cap +
+ +## Returns + +`AsyncGenerator`\<\{ +`collections?`: `object`\[]; +`pageKey?`: `string`; +`totalCount?`: `string`; +}, `void`, `undefined`> + +Pages, in order diff --git a/docs/pages/reference/data-apis/src/functions/getContractMetadata.mdx b/docs/pages/reference/data-apis/src/functions/getContractMetadata.mdx new file mode 100644 index 0000000000..bb8ecc5d8f --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getContractMetadata.mdx @@ -0,0 +1,129 @@ +--- +title: getContractMetadata +description: Overview of the getContractMetadata function +slug: wallets/reference/data-apis/functions/getContractMetadata +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getContractMetadata( + client, + params, + options?, +): Promise<{ + address?: string; + contractDeployer?: string; + deployedBlockNumber?: number; + name?: string; + openseaMetadata?: { + bannerImageUrl?: string; + collectionName?: string; + description?: string; + discordUrl?: string; + externalUrl?: string; + floorPrice?: number; + imageUrl?: string; + lastIngestedAt?: string; + safelistRequestStatus?: string; + twitterUsername?: string; + }; + symbol?: string; + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + totalSupply?: string; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getContractMetadata.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getContractMetadata.ts#L24) + +Fetches metadata for an NFT contract. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetContractMetadataParams`](../type-aliases/GetContractMetadataParams) + + Contract address and optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`address?`: `string`; +`contractDeployer?`: `string`; +`deployedBlockNumber?`: `number`; +`name?`: `string`; +`openseaMetadata?`: \{ +`bannerImageUrl?`: `string`; +`collectionName?`: `string`; +`description?`: `string`; +`discordUrl?`: `string`; +`externalUrl?`: `string`; +`floorPrice?`: `number`; +`imageUrl?`: `string`; +`lastIngestedAt?`: `string`; +`safelistRequestStatus?`: `string`; +`twitterUsername?`: `string`; +}; +`symbol?`: `string`; +`tokenType?`: `"ERC721"` | `"ERC1155"` | `"NO_SUPPORTED_NFT_STANDARD"` | `"NOT_A_CONTRACT"`; +`totalSupply?`: `string`; +}> + +The contract's metadata diff --git a/docs/pages/reference/data-apis/src/functions/getContractMetadataBatch.mdx b/docs/pages/reference/data-apis/src/functions/getContractMetadataBatch.mdx new file mode 100644 index 0000000000..b0fe8c2708 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getContractMetadataBatch.mdx @@ -0,0 +1,81 @@ +--- +title: getContractMetadataBatch +description: Overview of the getContractMetadataBatch function +slug: wallets/reference/data-apis/functions/getContractMetadataBatch +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getContractMetadataBatch(client, params, options?): Promise; +``` + +Defined in: [packages/data-apis/src/actions/nft/getContractMetadataBatch.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getContractMetadataBatch.ts#L24) + +Fetches metadata for multiple NFT contracts in one call. The network is +resolved per request: an explicit `network` param wins, otherwise the +client's configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetContractMetadataBatchParams`](../type-aliases/GetContractMetadataBatchParams) + + The contract addresses and optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<`object`\[]> + +Metadata for each requested contract diff --git a/docs/pages/reference/data-apis/src/functions/getContractsForOwner.mdx b/docs/pages/reference/data-apis/src/functions/getContractsForOwner.mdx new file mode 100644 index 0000000000..acea551e81 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getContractsForOwner.mdx @@ -0,0 +1,93 @@ +--- +title: getContractsForOwner +description: Overview of the getContractsForOwner function +slug: wallets/reference/data-apis/functions/getContractsForOwner +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getContractsForOwner( + client, + params, + options?, +): Promise<{ + contracts?: object[]; + pageKey?: string; + totalCount?: string; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getContractsForOwner.ts:26](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getContractsForOwner.ts#L26) + +Fetches all NFT contracts an address holds tokens from. The network is +resolved per request: an explicit `network` param wins, otherwise the +client's configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetContractsForOwnerParams`](../type-aliases/GetContractsForOwnerParams) + + Owner address, optional network override, and filters +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`contracts?`: `object`\[]; +`pageKey?`: `string`; +`totalCount?`: `string`; +}> + +The owned contracts and pagination cursor diff --git a/docs/pages/reference/data-apis/src/functions/getContractsForOwnerPages.mdx b/docs/pages/reference/data-apis/src/functions/getContractsForOwnerPages.mdx new file mode 100644 index 0000000000..3df452f6db --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getContractsForOwnerPages.mdx @@ -0,0 +1,97 @@ +--- +title: getContractsForOwnerPages +description: Overview of the getContractsForOwnerPages function +slug: wallets/reference/data-apis/functions/getContractsForOwnerPages +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getContractsForOwnerPages( + client, + params, + options?, +): AsyncGenerator< + { + contracts?: object[]; + pageKey?: string; + totalCount?: string; + }, + void, + undefined +>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getContractsForOwnerPages.ts:19](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getContractsForOwnerPages.ts#L19) + +Auto-paginating companion to [getContractsForOwner](getContractsForOwner): yields whole pages until the +cursor is exhausted (or `maxPages` is hit), guarding against repeated +cursors. Iterate items with an inner loop over each page. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetContractsForOwnerParams`](../type-aliases/GetContractsForOwnerParams) + + Same params as getContractsForOwner (the cursor is managed for you) +
+ `options?` + + [`PaginateOptions`](../type-aliases/PaginateOptions) + + Abort signal and page cap +
+ +## Returns + +`AsyncGenerator`\<\{ +`contracts?`: `object`\[]; +`pageKey?`: `string`; +`totalCount?`: `string`; +}, `void`, `undefined`> + +Pages, in order diff --git a/docs/pages/reference/data-apis/src/functions/getFloorPrice.mdx b/docs/pages/reference/data-apis/src/functions/getFloorPrice.mdx new file mode 100644 index 0000000000..6fd5ca4218 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getFloorPrice.mdx @@ -0,0 +1,101 @@ +--- +title: getFloorPrice +description: Overview of the getFloorPrice function +slug: wallets/reference/data-apis/functions/getFloorPrice +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getFloorPrice( + client, + params, + options?, +): Promise<{ + nftMarketplaceName?: { + collectionUrl?: string; + error?: string; + floorPrice?: number; + priceCurrency?: "ETH"; + retrievedAt?: string; + }; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getFloorPrice.ts:21](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getFloorPrice.ts#L21) + +Fetches marketplace floor prices for an NFT contract or collection. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetFloorPriceParams`](../type-aliases/GetFloorPriceParams) + + Contract address or collection slug, and optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`nftMarketplaceName?`: \{ +`collectionUrl?`: `string`; +`error?`: `string`; +`floorPrice?`: `number`; +`priceCurrency?`: `"ETH"`; +`retrievedAt?`: `string`; +}; +}> + +Floor prices per marketplace diff --git a/docs/pages/reference/data-apis/src/functions/getHistoricalTokenPrices.mdx b/docs/pages/reference/data-apis/src/functions/getHistoricalTokenPrices.mdx new file mode 100644 index 0000000000..752a2faed2 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getHistoricalTokenPrices.mdx @@ -0,0 +1,109 @@ +--- +title: getHistoricalTokenPrices +description: Overview of the getHistoricalTokenPrices function +slug: wallets/reference/data-apis/functions/getHistoricalTokenPrices +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getHistoricalTokenPrices( + client, + params, + options?, +): Promise< + | { + currency: string; + data: object[]; + symbol: string; + } + | { + address: string; + currency: string; + data: object[]; + network: string; + } +>; +``` + +Defined in: [packages/data-apis/src/actions/prices/getHistoricalTokenPrices.ts:26](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/prices/getHistoricalTokenPrices.ts#L26) + +Fetches historical prices for a token, identified either by symbol +(chain-agnostic) or by network + contract address. Runs against the global +Prices API; when a network is given it travels in the request body, so the +client's chain is not involved. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetHistoricalTokenPricesParams`](../type-aliases/GetHistoricalTokenPricesParams) + + Token identifier, time range, and interval +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\< +| \{ +`currency`: `string`; +`data`: `object`\[]; +`symbol`: `string`; +} +| \{ +`address`: `string`; +`currency`: `string`; +`data`: `object`\[]; +`network`: `string`; +}> + +The price series diff --git a/docs/pages/reference/data-apis/src/functions/getNftContractsByAddress.mdx b/docs/pages/reference/data-apis/src/functions/getNftContractsByAddress.mdx new file mode 100644 index 0000000000..a93e254087 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftContractsByAddress.mdx @@ -0,0 +1,98 @@ +--- +title: getNftContractsByAddress +description: Overview of the getNftContractsByAddress function +slug: wallets/reference/data-apis/functions/getNftContractsByAddress +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftContractsByAddress( + client, + params, + options?, +): Promise<{ + data: { + contracts?: object[]; + pageKey?: string; + totalCount?: number; + }; +}>; +``` + +Defined in: [packages/data-apis/src/actions/portfolio/getNftContractsByAddress.ts:26](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/portfolio/getNftContractsByAddress.ts#L26) + +Fetches NFT contracts for one or more addresses across one or more +networks in a single call. +This is a multi-network request against the global Data API: networks +travel in the request body, so the client's chain is not involved. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftContractsByAddressParams`](../type-aliases/GetNftContractsByAddressParams) + + Addresses paired with networks, plus options +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`data`: \{ +`contracts?`: `object`\[]; +`pageKey?`: `string`; +`totalCount?`: `number`; +}; +}> + +The owned NFT contracts and pagination cursor diff --git a/docs/pages/reference/data-apis/src/functions/getNftContractsByAddressPages.mdx b/docs/pages/reference/data-apis/src/functions/getNftContractsByAddressPages.mdx new file mode 100644 index 0000000000..97e927cbeb --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftContractsByAddressPages.mdx @@ -0,0 +1,101 @@ +--- +title: getNftContractsByAddressPages +description: Overview of the getNftContractsByAddressPages function +slug: wallets/reference/data-apis/functions/getNftContractsByAddressPages +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftContractsByAddressPages( + client, + params, + options?, +): AsyncGenerator< + { + data: { + contracts?: object[]; + pageKey?: string; + totalCount?: number; + }; + }, + void, + undefined +>; +``` + +Defined in: [packages/data-apis/src/actions/portfolio/getNftContractsByAddressPages.ts:19](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/portfolio/getNftContractsByAddressPages.ts#L19) + +Auto-paginating companion to [getNftContractsByAddress](getNftContractsByAddress): yields whole pages until the +cursor is exhausted (or `maxPages` is hit), guarding against repeated +cursors. Iterate items with an inner loop over each page. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftContractsByAddressParams`](../type-aliases/GetNftContractsByAddressParams) + + Same params as getNftContractsByAddress (the cursor is managed for you) +
+ `options?` + + [`PaginateOptions`](../type-aliases/PaginateOptions) + + Abort signal and page cap +
+ +## Returns + +`AsyncGenerator`\<\{ +`data`: \{ +`contracts?`: `object`\[]; +`pageKey?`: `string`; +`totalCount?`: `number`; +}; +}, `void`, `undefined`> + +Pages, in order diff --git a/docs/pages/reference/data-apis/src/functions/getNftMetadata.mdx b/docs/pages/reference/data-apis/src/functions/getNftMetadata.mdx new file mode 100644 index 0000000000..6c089334d4 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftMetadata.mdx @@ -0,0 +1,231 @@ +--- +title: getNftMetadata +description: Overview of the getNftMetadata function +slug: wallets/reference/data-apis/functions/getNftMetadata +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftMetadata( + client, + params, + options?, +): Promise<{ + acquiredAt?: { + blockNumber?: string; + blockTimestamp?: string; + }; + animation?: { + cachedUrl?: string; + contentType?: string; + orginalUrl?: string; + size?: number; + }; + collection?: { + bannerImageUrl?: string; + externalUrl?: string; + name?: string; + slug?: string; + }; + contract?: { + address?: string; + contractDeployer?: string; + deployedBlockNumber?: number; + isSpam?: string; + name?: string; + openseaMetadata?: { + bannerImageUrl?: string; + collectionName?: string; + description?: string; + discordUrl?: string; + externalUrl?: string; + floorPrice?: number; + imageUrl?: string; + lastIngestedAt?: string; + safelistRequestStatus?: string; + twitterUsername?: string; + }; + spamClassifications?: string[]; + symbol?: string; + tokenType?: + | "ERC721" + | "ERC1155" + | "NO_SUPPORTED_NFT_STANDARD" + | "NOT_A_CONTRACT"; + totalSupply?: string; + }; + description?: string; + image?: { + cachedUrl?: string; + contentType?: string; + originalUrl?: string; + pngUrl?: string; + size?: number; + thumbnailUrl?: string; + }; + mint?: { + blockNumber?: number; + mintAddress?: string; + timestamp?: string; + transactionHash?: string; + }; + name?: string; + owners?: string[]; + raw?: { + error?: string; + metadata?: { + attributes?: object[]; + description?: string; + image?: string; + name?: string; + }; + tokenUri?: string; + }; + timeLastUpdated?: string; + tokenId: string; + tokenType?: string; + tokenUri?: string; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getNftMetadata.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getNftMetadata.ts#L24) + +Fetches metadata for a single NFT. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftMetadataParams`](../type-aliases/GetNftMetadataParams) + + Contract address, token id, optional network override, and cache options +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`acquiredAt?`: \{ +`blockNumber?`: `string`; +`blockTimestamp?`: `string`; +}; +`animation?`: \{ +`cachedUrl?`: `string`; +`contentType?`: `string`; +`orginalUrl?`: `string`; +`size?`: `number`; +}; +`collection?`: \{ +`bannerImageUrl?`: `string`; +`externalUrl?`: `string`; +`name?`: `string`; +`slug?`: `string`; +}; +`contract?`: \{ +`address?`: `string`; +`contractDeployer?`: `string`; +`deployedBlockNumber?`: `number`; +`isSpam?`: `string`; +`name?`: `string`; +`openseaMetadata?`: \{ +`bannerImageUrl?`: `string`; +`collectionName?`: `string`; +`description?`: `string`; +`discordUrl?`: `string`; +`externalUrl?`: `string`; +`floorPrice?`: `number`; +`imageUrl?`: `string`; +`lastIngestedAt?`: `string`; +`safelistRequestStatus?`: `string`; +`twitterUsername?`: `string`; +}; +`spamClassifications?`: `string`\[]; +`symbol?`: `string`; +`tokenType?`: `"ERC721"` | `"ERC1155"` | `"NO_SUPPORTED_NFT_STANDARD"` | `"NOT_A_CONTRACT"`; +`totalSupply?`: `string`; +}; +`description?`: `string`; +`image?`: \{ +`cachedUrl?`: `string`; +`contentType?`: `string`; +`originalUrl?`: `string`; +`pngUrl?`: `string`; +`size?`: `number`; +`thumbnailUrl?`: `string`; +}; +`mint?`: \{ +`blockNumber?`: `number`; +`mintAddress?`: `string`; +`timestamp?`: `string`; +`transactionHash?`: `string`; +}; +`name?`: `string`; +`owners?`: `string`\[]; +`raw?`: \{ +`error?`: `string`; +`metadata?`: \{ +`attributes?`: `object`\[]; +`description?`: `string`; +`image?`: `string`; +`name?`: `string`; +}; +`tokenUri?`: `string`; +}; +`timeLastUpdated?`: `string`; +`tokenId`: `string`; +`tokenType?`: `string`; +`tokenUri?`: `string`; +}> + +The NFT's metadata diff --git a/docs/pages/reference/data-apis/src/functions/getNftMetadataBatch.mdx b/docs/pages/reference/data-apis/src/functions/getNftMetadataBatch.mdx new file mode 100644 index 0000000000..68aad57942 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftMetadataBatch.mdx @@ -0,0 +1,81 @@ +--- +title: getNftMetadataBatch +description: Overview of the getNftMetadataBatch function +slug: wallets/reference/data-apis/functions/getNftMetadataBatch +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftMetadataBatch(client, params, options?): Promise; +``` + +Defined in: [packages/data-apis/src/actions/nft/getNftMetadataBatch.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getNftMetadataBatch.ts#L24) + +Fetches metadata for up to 100 NFTs in one call. The network is resolved +per request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftMetadataBatchParams`](../type-aliases/GetNftMetadataBatchParams) + + The tokens to look up, cache options, and optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<`object`\[]> + +Metadata for each requested NFT diff --git a/docs/pages/reference/data-apis/src/functions/getNftSales.mdx b/docs/pages/reference/data-apis/src/functions/getNftSales.mdx new file mode 100644 index 0000000000..81783585af --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftSales.mdx @@ -0,0 +1,101 @@ +--- +title: getNftSales +description: Overview of the getNftSales function +slug: wallets/reference/data-apis/functions/getNftSales +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftSales( + client, + params, + options?, +): Promise<{ + nftSales?: object[]; + pageKey?: string; + validAt?: { + blockHash?: string; + blockNumber?: number; + blockTimestamp?: string; + }; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getNftSales.ts:21](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getNftSales.ts#L21) + +Fetches NFT sales matching the given filters. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftSalesParams`](../type-aliases/GetNftSalesParams) + + Sale filters, optional network override, and paging options +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`nftSales?`: `object`\[]; +`pageKey?`: `string`; +`validAt?`: \{ +`blockHash?`: `string`; +`blockNumber?`: `number`; +`blockTimestamp?`: `string`; +}; +}> + +The matching sales and pagination cursor diff --git a/docs/pages/reference/data-apis/src/functions/getNftSalesPages.mdx b/docs/pages/reference/data-apis/src/functions/getNftSalesPages.mdx new file mode 100644 index 0000000000..0c1bb58956 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftSalesPages.mdx @@ -0,0 +1,105 @@ +--- +title: getNftSalesPages +description: Overview of the getNftSalesPages function +slug: wallets/reference/data-apis/functions/getNftSalesPages +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftSalesPages( + client, + params, + options?, +): AsyncGenerator< + { + nftSales?: object[]; + pageKey?: string; + validAt?: { + blockHash?: string; + blockNumber?: number; + blockTimestamp?: string; + }; + }, + void, + undefined +>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getNftSalesPages.ts:16](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getNftSalesPages.ts#L16) + +Auto-paginating companion to [getNftSales](getNftSales): yields whole pages until the +cursor is exhausted (or `maxPages` is hit), guarding against repeated +cursors. Iterate items with an inner loop over each page. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftSalesParams`](../type-aliases/GetNftSalesParams) + + Same params as getNftSales (the cursor is managed for you) +
+ `options?` + + [`PaginateOptions`](../type-aliases/PaginateOptions) + + Abort signal and page cap +
+ +## Returns + +`AsyncGenerator`\<\{ +`nftSales?`: `object`\[]; +`pageKey?`: `string`; +`validAt?`: \{ +`blockHash?`: `string`; +`blockNumber?`: `number`; +`blockTimestamp?`: `string`; +}; +}, `void`, `undefined`> + +Pages, in order diff --git a/docs/pages/reference/data-apis/src/functions/getNftsByAddress.mdx b/docs/pages/reference/data-apis/src/functions/getNftsByAddress.mdx new file mode 100644 index 0000000000..269a78d285 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftsByAddress.mdx @@ -0,0 +1,98 @@ +--- +title: getNftsByAddress +description: Overview of the getNftsByAddress function +slug: wallets/reference/data-apis/functions/getNftsByAddress +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftsByAddress( + client, + params, + options?, +): Promise<{ + data: { + ownedNfts?: object[]; + pageKey?: string; + totalCount?: number; + }; +}>; +``` + +Defined in: [packages/data-apis/src/actions/portfolio/getNftsByAddress.ts:26](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/portfolio/getNftsByAddress.ts#L26) + +Fetches NFTs for one or more addresses across one or more networks in a +single call. +This is a multi-network request against the global Data API: networks +travel in the request body, so the client's chain is not involved. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftsByAddressParams`](../type-aliases/GetNftsByAddressParams) + + Addresses paired with networks, plus options +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`data`: \{ +`ownedNfts?`: `object`\[]; +`pageKey?`: `string`; +`totalCount?`: `number`; +}; +}> + +The owned NFTs and pagination cursor diff --git a/docs/pages/reference/data-apis/src/functions/getNftsByAddressPages.mdx b/docs/pages/reference/data-apis/src/functions/getNftsByAddressPages.mdx new file mode 100644 index 0000000000..63df7dd278 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftsByAddressPages.mdx @@ -0,0 +1,101 @@ +--- +title: getNftsByAddressPages +description: Overview of the getNftsByAddressPages function +slug: wallets/reference/data-apis/functions/getNftsByAddressPages +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftsByAddressPages( + client, + params, + options?, +): AsyncGenerator< + { + data: { + ownedNfts?: object[]; + pageKey?: string; + totalCount?: number; + }; + }, + void, + undefined +>; +``` + +Defined in: [packages/data-apis/src/actions/portfolio/getNftsByAddressPages.ts:19](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/portfolio/getNftsByAddressPages.ts#L19) + +Auto-paginating companion to [getNftsByAddress](getNftsByAddress): yields whole pages until the +cursor is exhausted (or `maxPages` is hit), guarding against repeated +cursors. Iterate items with an inner loop over each page. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftsByAddressParams`](../type-aliases/GetNftsByAddressParams) + + Same params as getNftsByAddress (the cursor is managed for you) +
+ `options?` + + [`PaginateOptions`](../type-aliases/PaginateOptions) + + Abort signal and page cap +
+ +## Returns + +`AsyncGenerator`\<\{ +`data`: \{ +`ownedNfts?`: `object`\[]; +`pageKey?`: `string`; +`totalCount?`: `number`; +}; +}, `void`, `undefined`> + +Pages, in order diff --git a/docs/pages/reference/data-apis/src/functions/getNftsForCollection.mdx b/docs/pages/reference/data-apis/src/functions/getNftsForCollection.mdx new file mode 100644 index 0000000000..c859241467 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftsForCollection.mdx @@ -0,0 +1,91 @@ +--- +title: getNftsForCollection +description: Overview of the getNftsForCollection function +slug: wallets/reference/data-apis/functions/getNftsForCollection +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftsForCollection( + client, + params, + options?, +): Promise<{ + nextToken?: string; + nfts?: object[]; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getNftsForCollection.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getNftsForCollection.ts#L24) + +Fetches all NFTs for a given collection (by contract address or collection slug). The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftsForCollectionParams`](../type-aliases/GetNftsForCollectionParams) + + Collection identifier, optional network override, and paging filters +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`nextToken?`: `string`; +`nfts?`: `object`\[]; +}> + +The collection's NFTs and next-page token diff --git a/docs/pages/reference/data-apis/src/functions/getNftsForCollectionPages.mdx b/docs/pages/reference/data-apis/src/functions/getNftsForCollectionPages.mdx new file mode 100644 index 0000000000..7e14c9b6c1 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftsForCollectionPages.mdx @@ -0,0 +1,95 @@ +--- +title: getNftsForCollectionPages +description: Overview of the getNftsForCollectionPages function +slug: wallets/reference/data-apis/functions/getNftsForCollectionPages +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftsForCollectionPages( + client, + params, + options?, +): AsyncGenerator< + { + nextToken?: string; + nfts?: object[]; + }, + void, + undefined +>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getNftsForCollectionPages.ts:19](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getNftsForCollectionPages.ts#L19) + +Auto-paginating companion to [getNftsForCollection](getNftsForCollection): yields whole pages until the +cursor is exhausted (or `maxPages` is hit), guarding against repeated +cursors. Iterate items with an inner loop over each page. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftsForCollectionParams`](../type-aliases/GetNftsForCollectionParams) + + Same params as getNftsForCollection (the cursor is managed for you) +
+ `options?` + + [`PaginateOptions`](../type-aliases/PaginateOptions) + + Abort signal and page cap +
+ +## Returns + +`AsyncGenerator`\<\{ +`nextToken?`: `string`; +`nfts?`: `object`\[]; +}, `void`, `undefined`> + +Pages, in order diff --git a/docs/pages/reference/data-apis/src/functions/getNftsForContract.mdx b/docs/pages/reference/data-apis/src/functions/getNftsForContract.mdx new file mode 100644 index 0000000000..1be96b92ac --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftsForContract.mdx @@ -0,0 +1,91 @@ +--- +title: getNftsForContract +description: Overview of the getNftsForContract function +slug: wallets/reference/data-apis/functions/getNftsForContract +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftsForContract( + client, + params, + options?, +): Promise<{ + nfts?: object[]; + pageKey?: string; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getNftsForContract.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getNftsForContract.ts#L24) + +Fetches all NFTs for a given contract. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftsForContractParams`](../type-aliases/GetNftsForContractParams) + + Contract address, optional network override, and paging filters +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`nfts?`: `object`\[]; +`pageKey?`: `string`; +}> + +The contract's NFTs and pagination cursor diff --git a/docs/pages/reference/data-apis/src/functions/getNftsForContractPages.mdx b/docs/pages/reference/data-apis/src/functions/getNftsForContractPages.mdx new file mode 100644 index 0000000000..b701acfdb6 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftsForContractPages.mdx @@ -0,0 +1,95 @@ +--- +title: getNftsForContractPages +description: Overview of the getNftsForContractPages function +slug: wallets/reference/data-apis/functions/getNftsForContractPages +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftsForContractPages( + client, + params, + options?, +): AsyncGenerator< + { + nfts?: object[]; + pageKey?: string; + }, + void, + undefined +>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getNftsForContractPages.ts:19](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getNftsForContractPages.ts#L19) + +Auto-paginating companion to [getNftsForContract](getNftsForContract): yields whole pages until the +cursor is exhausted (or `maxPages` is hit), guarding against repeated +cursors. Iterate items with an inner loop over each page. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftsForContractParams`](../type-aliases/GetNftsForContractParams) + + Same params as getNftsForContract (the cursor is managed for you) +
+ `options?` + + [`PaginateOptions`](../type-aliases/PaginateOptions) + + Abort signal and page cap +
+ +## Returns + +`AsyncGenerator`\<\{ +`nfts?`: `object`\[]; +`pageKey?`: `string`; +}, `void`, `undefined`> + +Pages, in order diff --git a/docs/pages/reference/data-apis/src/functions/getNftsForOwner.mdx b/docs/pages/reference/data-apis/src/functions/getNftsForOwner.mdx new file mode 100644 index 0000000000..c822eb30c9 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftsForOwner.mdx @@ -0,0 +1,103 @@ +--- +title: getNftsForOwner +description: Overview of the getNftsForOwner function +slug: wallets/reference/data-apis/functions/getNftsForOwner +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftsForOwner( + client, + params, + options?, +): Promise<{ + ownedNfts?: object[]; + pageKey?: string; + totalCount?: number; + validAt?: { + blockHash?: string; + blockNumber?: number; + blockTimestamp?: string; + }; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getNftsForOwner.ts:26](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getNftsForOwner.ts#L26) + +Fetches NFTs owned by an address on a single network. The network is +resolved per request: an explicit `network` param wins, otherwise the +client's configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftsForOwnerParams`](../type-aliases/GetNftsForOwnerParams) + + Owner address, optional network override, and filters +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`ownedNfts?`: `object`\[]; +`pageKey?`: `string`; +`totalCount?`: `number`; +`validAt?`: \{ +`blockHash?`: `string`; +`blockNumber?`: `number`; +`blockTimestamp?`: `string`; +}; +}> + +The owned NFTs and pagination cursor diff --git a/docs/pages/reference/data-apis/src/functions/getNftsForOwnerPages.mdx b/docs/pages/reference/data-apis/src/functions/getNftsForOwnerPages.mdx new file mode 100644 index 0000000000..6bd3606577 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getNftsForOwnerPages.mdx @@ -0,0 +1,107 @@ +--- +title: getNftsForOwnerPages +description: Overview of the getNftsForOwnerPages function +slug: wallets/reference/data-apis/functions/getNftsForOwnerPages +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getNftsForOwnerPages( + client, + params, + options?, +): AsyncGenerator< + { + ownedNfts?: object[]; + pageKey?: string; + totalCount?: number; + validAt?: { + blockHash?: string; + blockNumber?: number; + blockTimestamp?: string; + }; + }, + void, + undefined +>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getNftsForOwnerPages.ts:19](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getNftsForOwnerPages.ts#L19) + +Auto-paginating companion to [getNftsForOwner](getNftsForOwner): yields whole pages until the +cursor is exhausted (or `maxPages` is hit), guarding against repeated +cursors. Iterate items with an inner loop over each page. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetNftsForOwnerParams`](../type-aliases/GetNftsForOwnerParams) + + Same params as getNftsForOwner (the cursor is managed for you) +
+ `options?` + + [`PaginateOptions`](../type-aliases/PaginateOptions) + + Abort signal and page cap +
+ +## Returns + +`AsyncGenerator`\<\{ +`ownedNfts?`: `object`\[]; +`pageKey?`: `string`; +`totalCount?`: `number`; +`validAt?`: \{ +`blockHash?`: `string`; +`blockNumber?`: `number`; +`blockTimestamp?`: `string`; +}; +}, `void`, `undefined`> + +Pages, in order diff --git a/docs/pages/reference/data-apis/src/functions/getOwnersForContract.mdx b/docs/pages/reference/data-apis/src/functions/getOwnersForContract.mdx new file mode 100644 index 0000000000..24f2f74946 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getOwnersForContract.mdx @@ -0,0 +1,89 @@ +--- +title: getOwnersForContract +description: Overview of the getOwnersForContract function +slug: wallets/reference/data-apis/functions/getOwnersForContract +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getOwnersForContract( + client, + params, + options?, +): Promise<{ + owners?: object[]; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getOwnersForContract.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getOwnersForContract.ts#L24) + +Fetches all owners for an NFT contract. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetOwnersForContractParams`](../type-aliases/GetOwnersForContractParams) + + Contract address, optional network override, and balance options +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`owners?`: `object`\[]; +}> + +The contract's owners diff --git a/docs/pages/reference/data-apis/src/functions/getOwnersForNft.mdx b/docs/pages/reference/data-apis/src/functions/getOwnersForNft.mdx new file mode 100644 index 0000000000..f0db6e6455 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getOwnersForNft.mdx @@ -0,0 +1,91 @@ +--- +title: getOwnersForNft +description: Overview of the getOwnersForNft function +slug: wallets/reference/data-apis/functions/getOwnersForNft +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getOwnersForNft( + client, + params, + options?, +): Promise<{ + owners?: string[]; + pageKey?: string; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getOwnersForNft.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getOwnersForNft.ts#L24) + +Fetches the owners of a specific NFT. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetOwnersForNftParams`](../type-aliases/GetOwnersForNftParams) + + Contract address, token id, and optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`owners?`: `string`\[]; +`pageKey?`: `string`; +}> + +The NFT's owners diff --git a/docs/pages/reference/data-apis/src/functions/getSpamContracts.mdx b/docs/pages/reference/data-apis/src/functions/getSpamContracts.mdx new file mode 100644 index 0000000000..aab9b57e9f --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getSpamContracts.mdx @@ -0,0 +1,89 @@ +--- +title: getSpamContracts +description: Overview of the getSpamContracts function +slug: wallets/reference/data-apis/functions/getSpamContracts +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getSpamContracts( + client, + params?, + options?, +): Promise<{ + contractAddresses?: string[]; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/getSpamContracts.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/getSpamContracts.ts#L24) + +Fetches the full list of contracts classified as spam on a network. The +network is resolved per request: an explicit `network` param wins, +otherwise the client's configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params?` + + [`GetSpamContractsParams`](../type-aliases/GetSpamContractsParams) + + Optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`contractAddresses?`: `string`\[]; +}> + +The spam contract addresses diff --git a/docs/pages/reference/data-apis/src/functions/getTokenAllowance.mdx b/docs/pages/reference/data-apis/src/functions/getTokenAllowance.mdx new file mode 100644 index 0000000000..10ce833065 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getTokenAllowance.mdx @@ -0,0 +1,68 @@ +--- +title: getTokenAllowance +description: Overview of the getTokenAllowance function +slug: wallets/reference/data-apis/functions/getTokenAllowance +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getTokenAllowance(client, params): Promise; +``` + +Defined in: [packages/data-apis/src/actions/token/getTokenAllowance.ts:21](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/token/getTokenAllowance.ts#L21) + +Fetches the ERC-20 allowance a spender has from an owner via the +`alchemy_getTokenAllowance` JSON-RPC method. Without a `network` override +this uses the client's transport; with one, a transport instance is derived +for the override network. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetTokenAllowanceParams`](../type-aliases/GetTokenAllowanceParams) + + Contract, owner, spender, and optional network override +
+ +## Returns + +`Promise`\<`string`> + +The allowance as a decimal string diff --git a/docs/pages/reference/data-apis/src/functions/getTokenBalances.mdx b/docs/pages/reference/data-apis/src/functions/getTokenBalances.mdx new file mode 100644 index 0000000000..56c890a6d7 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getTokenBalances.mdx @@ -0,0 +1,71 @@ +--- +title: getTokenBalances +description: Overview of the getTokenBalances function +slug: wallets/reference/data-apis/functions/getTokenBalances +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getTokenBalances( + client, + params, +): Promise; +``` + +Defined in: [packages/data-apis/src/actions/token/getTokenBalances.ts:22](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/token/getTokenBalances.ts#L22) + +Fetches ERC-20 (and native) token balances for an address via the +`alchemy_getTokenBalances` JSON-RPC method. Without a `network` override +this uses the client's transport; with one, a transport instance is derived +for the override network. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetTokenBalancesParams`](../type-aliases/GetTokenBalancesParams) + + Address, token spec, paging options, and optional network override +
+ +## Returns + +`Promise`\<`AlchemyGetTokenBalancesResult`> + +The token balances diff --git a/docs/pages/reference/data-apis/src/functions/getTokenBalancesByAddress.mdx b/docs/pages/reference/data-apis/src/functions/getTokenBalancesByAddress.mdx new file mode 100644 index 0000000000..a8b5168dbe --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getTokenBalancesByAddress.mdx @@ -0,0 +1,96 @@ +--- +title: getTokenBalancesByAddress +description: Overview of the getTokenBalancesByAddress function +slug: wallets/reference/data-apis/functions/getTokenBalancesByAddress +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getTokenBalancesByAddress( + client, + params, + options?, +): Promise<{ + data: { + pageKey?: string; + tokens?: object[]; + }; +}>; +``` + +Defined in: [packages/data-apis/src/actions/portfolio/getTokenBalancesByAddress.ts:26](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/portfolio/getTokenBalancesByAddress.ts#L26) + +Fetches token balances (without metadata or prices) for one or more +addresses across one or more networks in a single call. +This is a multi-network request against the global Data API: networks +travel in the request body, so the client's chain is not involved. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetTokenBalancesByAddressParams`](../type-aliases/GetTokenBalancesByAddressParams) + + Addresses paired with networks, plus options +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`data`: \{ +`pageKey?`: `string`; +`tokens?`: `object`\[]; +}; +}> + +Token balances per wallet/network pair diff --git a/docs/pages/reference/data-apis/src/functions/getTokenMetadata.mdx b/docs/pages/reference/data-apis/src/functions/getTokenMetadata.mdx new file mode 100644 index 0000000000..f414681d29 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getTokenMetadata.mdx @@ -0,0 +1,71 @@ +--- +title: getTokenMetadata +description: Overview of the getTokenMetadata function +slug: wallets/reference/data-apis/functions/getTokenMetadata +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getTokenMetadata( + client, + params, +): Promise; +``` + +Defined in: [packages/data-apis/src/actions/token/getTokenMetadata.ts:21](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/token/getTokenMetadata.ts#L21) + +Fetches metadata (name, symbol, decimals, logo) for a token contract via +the `alchemy_getTokenMetadata` JSON-RPC method. Without a `network` +override this uses the client's transport; with one, a transport instance +is derived for the override network. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetTokenMetadataParams`](../type-aliases/GetTokenMetadataParams) + + The contract address and optional network override +
+ +## Returns + +`Promise`\<`AlchemyGetTokenMetadataResult`> + +The token metadata diff --git a/docs/pages/reference/data-apis/src/functions/getTokenPricesByAddress.mdx b/docs/pages/reference/data-apis/src/functions/getTokenPricesByAddress.mdx new file mode 100644 index 0000000000..c8d9277e2b --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getTokenPricesByAddress.mdx @@ -0,0 +1,89 @@ +--- +title: getTokenPricesByAddress +description: Overview of the getTokenPricesByAddress function +slug: wallets/reference/data-apis/functions/getTokenPricesByAddress +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getTokenPricesByAddress( + client, + params, + options?, +): Promise<{ + data: object[]; +}>; +``` + +Defined in: [packages/data-apis/src/actions/prices/getTokenPricesByAddress.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/prices/getTokenPricesByAddress.ts#L24) + +Fetches current prices for tokens by contract address (max 25 pairs). This +is a multi-network request against the global Prices API: each entry pairs +an address with its network, so the client's chain is not involved. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetTokenPricesByAddressParams`](../type-aliases/GetTokenPricesByAddressParams) + + Address/network pairs to price +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`data`: `object`\[]; +}> + +Prices per address/network pair diff --git a/docs/pages/reference/data-apis/src/functions/getTokenPricesBySymbol.mdx b/docs/pages/reference/data-apis/src/functions/getTokenPricesBySymbol.mdx new file mode 100644 index 0000000000..5c2d5b3799 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getTokenPricesBySymbol.mdx @@ -0,0 +1,105 @@ +--- +title: getTokenPricesBySymbol +description: Overview of the getTokenPricesBySymbol function +slug: wallets/reference/data-apis/functions/getTokenPricesBySymbol +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getTokenPricesBySymbol( + client, + params, + options?, +): Promise<{ + data: object[]; +}>; +``` + +Defined in: [packages/data-apis/src/actions/prices/getTokenPricesBySymbol.ts:23](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/prices/getTokenPricesBySymbol.ts#L23) + +Fetches current prices for tokens by symbol (max 25). This is a +chain-agnostic request against the global Prices API — no network is +involved at all. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + \{ `symbols`: `string`\[]; } + + The token symbols to price +
+ `params.symbols` + + `string`\[] + + **Description** + + Array of token symbols (limit 25). Example: symbols=\[ETH,BTC] +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`data`: `object`\[]; +}> + +Prices per symbol diff --git a/docs/pages/reference/data-apis/src/functions/getTokensByAddress.mdx b/docs/pages/reference/data-apis/src/functions/getTokensByAddress.mdx new file mode 100644 index 0000000000..226e0d9116 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/getTokensByAddress.mdx @@ -0,0 +1,108 @@ +--- +title: getTokensByAddress +description: Overview of the getTokensByAddress function +slug: wallets/reference/data-apis/functions/getTokensByAddress +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function getTokensByAddress( + client, + params, + options?, +): Promise<{ + data: { + pageKey?: string; + tokens?: object[]; + }; +}>; +``` + +Defined in: [packages/data-apis/src/actions/portfolio/getTokensByAddress.ts:34](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/portfolio/getTokensByAddress.ts#L34) + +Fetches tokens (with optional metadata and prices) for one or more addresses +across one or more networks in a single call. This is a multi-network +request against the global Data API: networks travel in the request body, +so the client's chain is not involved. + +## Example + +```ts +import { mainnet, base } from "viem/chains"; + +const tokens = await getTokensByAddress(client, { + addresses: [ + { address: "0x...", networks: [mainnet, base, "solana-mainnet"] }, + ], +}); +``` + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`GetTokensByAddressParams`](../type-aliases/GetTokensByAddressParams) + + Addresses paired with networks, plus options +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`data`: \{ +`pageKey?`: `string`; +`tokens?`: `object`\[]; +}; +}> + +Token balances, metadata, and prices diff --git a/docs/pages/reference/data-apis/src/functions/isAirdropNft.mdx b/docs/pages/reference/data-apis/src/functions/isAirdropNft.mdx new file mode 100644 index 0000000000..b643d71a8b --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/isAirdropNft.mdx @@ -0,0 +1,89 @@ +--- +title: isAirdropNft +description: Overview of the isAirdropNft function +slug: wallets/reference/data-apis/functions/isAirdropNft +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function isAirdropNft( + client, + params, + options?, +): Promise<{ + isAirdrop?: boolean; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/isAirdropNft.ts:21](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/isAirdropNft.ts#L21) + +Checks whether an NFT was airdropped to its owner. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`IsAirdropNftParams`](../type-aliases/IsAirdropNftParams) + + Contract address, token id, and optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`isAirdrop?`: `boolean`; +}> + +The airdrop classification diff --git a/docs/pages/reference/data-apis/src/functions/isHolderOfContract.mdx b/docs/pages/reference/data-apis/src/functions/isHolderOfContract.mdx new file mode 100644 index 0000000000..81a54f6e6a --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/isHolderOfContract.mdx @@ -0,0 +1,89 @@ +--- +title: isHolderOfContract +description: Overview of the isHolderOfContract function +slug: wallets/reference/data-apis/functions/isHolderOfContract +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function isHolderOfContract( + client, + params, + options?, +): Promise<{ + isHolderOfContract?: boolean; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/isHolderOfContract.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/isHolderOfContract.ts#L24) + +Checks whether a wallet holds any NFT from a contract. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`IsHolderOfContractParams`](../type-aliases/IsHolderOfContractParams) + + Wallet address, contract address, and optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`isHolderOfContract?`: `boolean`; +}> + +The holder check result diff --git a/docs/pages/reference/data-apis/src/functions/isSpamContract.mdx b/docs/pages/reference/data-apis/src/functions/isSpamContract.mdx new file mode 100644 index 0000000000..b380593102 --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/isSpamContract.mdx @@ -0,0 +1,89 @@ +--- +title: isSpamContract +description: Overview of the isSpamContract function +slug: wallets/reference/data-apis/functions/isSpamContract +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function isSpamContract( + client, + params, + options?, +): Promise<{ + isSpamContract?: boolean; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/isSpamContract.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/isSpamContract.ts#L24) + +Checks whether a contract is classified as spam. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`IsSpamContractParams`](../type-aliases/IsSpamContractParams) + + Contract address and optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`isSpamContract?`: `boolean`; +}> + +The spam classification diff --git a/docs/pages/reference/data-apis/src/functions/searchContractMetadata.mdx b/docs/pages/reference/data-apis/src/functions/searchContractMetadata.mdx new file mode 100644 index 0000000000..366b015b8b --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/searchContractMetadata.mdx @@ -0,0 +1,81 @@ +--- +title: searchContractMetadata +description: Overview of the searchContractMetadata function +slug: wallets/reference/data-apis/functions/searchContractMetadata +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function searchContractMetadata(client, params, options?): Promise; +``` + +Defined in: [packages/data-apis/src/actions/nft/searchContractMetadata.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/searchContractMetadata.ts#L24) + +Searches NFT contract metadata by keyword. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`SearchContractMetadataParams`](../type-aliases/SearchContractMetadataParams) + + Search query and optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<`object`\[]> + +The matching contracts diff --git a/docs/pages/reference/data-apis/src/functions/summarizeNftAttributes.mdx b/docs/pages/reference/data-apis/src/functions/summarizeNftAttributes.mdx new file mode 100644 index 0000000000..dd146caa3f --- /dev/null +++ b/docs/pages/reference/data-apis/src/functions/summarizeNftAttributes.mdx @@ -0,0 +1,93 @@ +--- +title: summarizeNftAttributes +description: Overview of the summarizeNftAttributes function +slug: wallets/reference/data-apis/functions/summarizeNftAttributes +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function summarizeNftAttributes( + client, + params, + options?, +): Promise<{ + contractAddress: string; + summary?: Record; + totalSupply?: string; +}>; +``` + +Defined in: [packages/data-apis/src/actions/nft/summarizeNftAttributes.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/nft/summarizeNftAttributes.ts#L24) + +Summarizes attribute prevalence for an NFT contract. The network is resolved per +request: an explicit `network` param wins, otherwise the client's +configured network/chain applies. + +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `client` + + `DataClient` + + A client configured with an Alchemy transport +
+ `params` + + [`SummarizeNftAttributesParams`](../type-aliases/SummarizeNftAttributesParams) + + Contract address and optional network override +
+ `options?` + + [`RequestOptions`](../type-aliases/RequestOptions) + + Per-request options (abort signal) +
+ +## Returns + +`Promise`\<\{ +`contractAddress`: `string`; +`summary?`: `Record`\<`string`, `never`>; +`totalSupply?`: `string`; +}> + +The attribute summary diff --git a/docs/pages/reference/data-apis/src/interfaces/PortfolioAddressEntry.mdx b/docs/pages/reference/data-apis/src/interfaces/PortfolioAddressEntry.mdx new file mode 100644 index 0000000000..6fb5e366d5 --- /dev/null +++ b/docs/pages/reference/data-apis/src/interfaces/PortfolioAddressEntry.mdx @@ -0,0 +1,55 @@ +--- +title: PortfolioAddressEntry +description: An address paired with the networks to query it on. +slug: wallets/reference/data-apis/interfaces/PortfolioAddressEntry +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +Defined in: [packages/data-apis/src/types.ts:103](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L103) + +An address paired with the networks to query it on. + +## Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ `address` + + `string` + + ‐ +
+ `networks` + + `NetworkInput`\[] + + Networks to query; accepts viem Chains, Alchemy slugs, or CAIP-2 ids. +
diff --git a/docs/pages/reference/data-apis/src/interfaces/PriceAddressEntry.mdx b/docs/pages/reference/data-apis/src/interfaces/PriceAddressEntry.mdx new file mode 100644 index 0000000000..12ac07a82d --- /dev/null +++ b/docs/pages/reference/data-apis/src/interfaces/PriceAddressEntry.mdx @@ -0,0 +1,55 @@ +--- +title: PriceAddressEntry +description: A token address paired with the network it lives on. +slug: wallets/reference/data-apis/interfaces/PriceAddressEntry +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +Defined in: [packages/data-apis/src/types.ts:152](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L152) + +A token address paired with the network it lives on. + +## Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ `address` + + `string` + + ‐ +
+ `network` + + `NetworkInput` + + Accepts a viem Chain, an Alchemy slug, or a CAIP-2 id. +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClient.mdx b/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClient.mdx new file mode 100644 index 0000000000..33421c4134 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClient.mdx @@ -0,0 +1,15 @@ +--- +title: AlchemyDataClient +description: Overview of AlchemyDataClient +slug: wallets/reference/data-apis/type-aliases/AlchemyDataClient +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type AlchemyDataClient = Client & + DataActions; +``` + +Defined in: [packages/data-apis/src/client.ts:25](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/client.ts#L25) diff --git a/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClientOptions.mdx b/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClientOptions.mdx new file mode 100644 index 0000000000..3ae36fac92 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClientOptions.mdx @@ -0,0 +1,50 @@ +--- +title: AlchemyDataClientOptions +description: Overview of AlchemyDataClientOptions +slug: wallets/reference/data-apis/type-aliases/AlchemyDataClientOptions +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type AlchemyDataClientOptions = Pick< + AlchemyTransportConfig, + "apiKey" | "jwt" | "url" | "fetchOptions" +> & + object; +``` + +Defined in: [packages/data-apis/src/client.ts:12](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/client.ts#L12) + +## Type Declaration + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ `network?` + + `NetworkInput` + + Default network for network-scoped calls (NFT, Token, Transfers). + Accepts a viem Chain, an Alchemy slug ("eth-mainnet"), or CAIP-2 + ("eip155:1"). Multi-network calls (Portfolio) take explicit networks + per request. +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/AssetTransfer.mdx b/docs/pages/reference/data-apis/src/type-aliases/AssetTransfer.mdx new file mode 100644 index 0000000000..b24b798e1d --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/AssetTransfer.mdx @@ -0,0 +1,14 @@ +--- +title: AssetTransfer +description: Overview of AssetTransfer +slug: wallets/reference/data-apis/type-aliases/AssetTransfer +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type AssetTransfer = NonNullable[number]; +``` + +Defined in: [packages/data-apis/src/types.ts:297](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L297) diff --git a/docs/pages/reference/data-apis/src/type-aliases/ComputeRarityParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/ComputeRarityParams.mdx new file mode 100644 index 0000000000..efbb3a05ac --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/ComputeRarityParams.mdx @@ -0,0 +1,14 @@ +--- +title: ComputeRarityParams +description: Overview of ComputeRarityParams +slug: wallets/reference/data-apis/type-aliases/ComputeRarityParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type ComputeRarityParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:244](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L244) diff --git a/docs/pages/reference/data-apis/src/type-aliases/ComputeRarityResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/ComputeRarityResult.mdx new file mode 100644 index 0000000000..1707675e81 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/ComputeRarityResult.mdx @@ -0,0 +1,14 @@ +--- +title: ComputeRarityResult +description: Overview of ComputeRarityResult +slug: wallets/reference/data-apis/type-aliases/ComputeRarityResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type ComputeRarityResult = ComputeRarityResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:245](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L245) diff --git a/docs/pages/reference/data-apis/src/type-aliases/DataActions.mdx b/docs/pages/reference/data-apis/src/type-aliases/DataActions.mdx new file mode 100644 index 0000000000..2575659a81 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/DataActions.mdx @@ -0,0 +1,490 @@ +--- +title: DataActions +description: The namespaced Data API actions attached by the dataActions decorator. +slug: wallets/reference/data-apis/type-aliases/DataActions +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type DataActions = object; +``` + +Defined in: [packages/data-apis/src/decorator.ts:64](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/decorator.ts#L64) + +The namespaced Data API actions attached by the [dataActions](../functions/dataActions) decorator. + +## Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyType
+ `nft` + + `object` +
+ `nft.computeRarity` + + `Action`\<[`ComputeRarityParams`](ComputeRarityParams), [`ComputeRarityResult`](ComputeRarityResult)> +
+ `nft.getCollectionMetadata` + + `Action`\<[`GetCollectionMetadataParams`](GetCollectionMetadataParams), [`GetCollectionMetadataResult`](GetCollectionMetadataResult)> +
+ `nft.getCollectionsForOwner` + + `Action`\<[`GetCollectionsForOwnerParams`](GetCollectionsForOwnerParams), [`GetCollectionsForOwnerResult`](GetCollectionsForOwnerResult)> +
+ `nft.getCollectionsForOwnerPages` + + `PagesAction`\<[`GetCollectionsForOwnerParams`](GetCollectionsForOwnerParams), [`GetCollectionsForOwnerResult`](GetCollectionsForOwnerResult)> +
+ `nft.getContractMetadata` + + `Action`\<[`GetContractMetadataParams`](GetContractMetadataParams), [`GetContractMetadataResult`](GetContractMetadataResult)> +
+ `nft.getContractMetadataBatch` + + `Action`\<[`GetContractMetadataBatchParams`](GetContractMetadataBatchParams), `any`\[]> +
+ `nft.getContractsForOwner` + + `Action`\<[`GetContractsForOwnerParams`](GetContractsForOwnerParams), [`GetContractsForOwnerResult`](GetContractsForOwnerResult)> +
+ `nft.getContractsForOwnerPages` + + `PagesAction`\<[`GetContractsForOwnerParams`](GetContractsForOwnerParams), [`GetContractsForOwnerResult`](GetContractsForOwnerResult)> +
+ `nft.getFloorPrice` + + `Action`\<[`GetFloorPriceParams`](GetFloorPriceParams), [`GetFloorPriceResult`](GetFloorPriceResult)> +
+ `nft.getNftMetadata` + + `Action`\<[`GetNftMetadataParams`](GetNftMetadataParams), [`GetNftMetadataResult`](GetNftMetadataResult)> +
+ `nft.getNftMetadataBatch` + + `Action`\<[`GetNftMetadataBatchParams`](GetNftMetadataBatchParams), `any`\[]> +
+ `nft.getNftSales` + + `Action`\<[`GetNftSalesParams`](GetNftSalesParams), [`GetNftSalesResult`](GetNftSalesResult)> +
+ `nft.getNftSalesPages` + + `PagesAction`\<[`GetNftSalesParams`](GetNftSalesParams), [`GetNftSalesResult`](GetNftSalesResult)> +
+ `nft.getNftsForCollection` + + `Action`\<[`GetNftsForCollectionParams`](GetNftsForCollectionParams), [`GetNftsForCollectionResult`](GetNftsForCollectionResult)> +
+ `nft.getNftsForCollectionPages` + + `PagesAction`\<[`GetNftsForCollectionParams`](GetNftsForCollectionParams), [`GetNftsForCollectionResult`](GetNftsForCollectionResult)> +
+ `nft.getNftsForContract` + + `Action`\<[`GetNftsForContractParams`](GetNftsForContractParams), [`GetNftsForContractResult`](GetNftsForContractResult)> +
+ `nft.getNftsForContractPages` + + `PagesAction`\<[`GetNftsForContractParams`](GetNftsForContractParams), [`GetNftsForContractResult`](GetNftsForContractResult)> +
+ `nft.getNftsForOwner` + + `Action`\<[`GetNftsForOwnerParams`](GetNftsForOwnerParams), [`GetNftsForOwnerResult`](GetNftsForOwnerResult)> +
+ `nft.getNftsForOwnerPages` + + `PagesAction`\<[`GetNftsForOwnerParams`](GetNftsForOwnerParams), [`GetNftsForOwnerResult`](GetNftsForOwnerResult)> +
+ `nft.getOwnersForContract` + + `Action`\<[`GetOwnersForContractParams`](GetOwnersForContractParams), [`GetOwnersForContractResult`](GetOwnersForContractResult)> +
+ `nft.getOwnersForNft` + + `Action`\<[`GetOwnersForNftParams`](GetOwnersForNftParams), [`GetOwnersForNftResult`](GetOwnersForNftResult)> +
+ `nft.getSpamContracts` + + `Action`\<[`GetSpamContractsParams`](GetSpamContractsParams), [`GetSpamContractsResult`](GetSpamContractsResult)> +
+ `nft.isAirdropNft` + + `Action`\<[`IsAirdropNftParams`](IsAirdropNftParams), [`IsAirdropNftResult`](IsAirdropNftResult)> +
+ `nft.isHolderOfContract` + + `Action`\<[`IsHolderOfContractParams`](IsHolderOfContractParams), [`IsHolderOfContractResult`](IsHolderOfContractResult)> +
+ `nft.isSpamContract` + + `Action`\<[`IsSpamContractParams`](IsSpamContractParams), [`IsSpamContractResult`](IsSpamContractResult)> +
+ `nft.searchContractMetadata` + + `Action`\<[`SearchContractMetadataParams`](SearchContractMetadataParams), `any`\[]> +
+ `nft.summarizeNftAttributes` + + `Action`\<[`SummarizeNftAttributesParams`](SummarizeNftAttributesParams), [`SummarizeNftAttributesResult`](SummarizeNftAttributesResult)> +
+ `portfolio` + + `object` +
+ `portfolio.getNftContractsByAddress` + + `Action`\<[`GetNftContractsByAddressParams`](GetNftContractsByAddressParams), [`GetNftContractsByAddressResult`](GetNftContractsByAddressResult)> +
+ `portfolio.getNftContractsByAddressPages` + + `PagesAction`\<[`GetNftContractsByAddressParams`](GetNftContractsByAddressParams), [`GetNftContractsByAddressResult`](GetNftContractsByAddressResult)> +
+ `portfolio.getNftsByAddress` + + `Action`\<[`GetNftsByAddressParams`](GetNftsByAddressParams), [`GetNftsByAddressResult`](GetNftsByAddressResult)> +
+ `portfolio.getNftsByAddressPages` + + `PagesAction`\<[`GetNftsByAddressParams`](GetNftsByAddressParams), [`GetNftsByAddressResult`](GetNftsByAddressResult)> +
+ `portfolio.getTokenBalancesByAddress` + + `Action`\<[`GetTokenBalancesByAddressParams`](GetTokenBalancesByAddressParams), [`GetTokenBalancesByAddressResult`](GetTokenBalancesByAddressResult)> +
+ `portfolio.getTokensByAddress` + + `Action`\<[`GetTokensByAddressParams`](GetTokensByAddressParams), [`GetTokensByAddressResult`](GetTokensByAddressResult)> +
+ `prices` + + `object` +
+ `prices.getHistoricalTokenPrices` + + `Action`\<[`GetHistoricalTokenPricesParams`](GetHistoricalTokenPricesParams), [`GetHistoricalTokenPricesResult`](GetHistoricalTokenPricesResult)> +
+ `prices.getTokenPricesByAddress` + + `Action`\<[`GetTokenPricesByAddressParams`](GetTokenPricesByAddressParams), [`GetTokenPricesByAddressResult`](GetTokenPricesByAddressResult)> +
+ `prices.getTokenPricesBySymbol` + + `Action`\<[`GetTokenPricesBySymbolParams`](GetTokenPricesBySymbolParams), [`GetTokenPricesBySymbolResult`](GetTokenPricesBySymbolResult)> +
+ `token` + + `object` +
+ `token.getTokenAllowance` + + `RpcAction`\<[`GetTokenAllowanceParams`](GetTokenAllowanceParams), [`GetTokenAllowanceResult`](GetTokenAllowanceResult)> +
+ `token.getTokenBalances` + + `RpcAction`\<[`GetTokenBalancesParams`](GetTokenBalancesParams), [`GetTokenBalancesResult`](GetTokenBalancesResult)> +
+ `token.getTokenMetadata` + + `RpcAction`\<[`GetTokenMetadataParams`](GetTokenMetadataParams), [`GetTokenMetadataResult`](GetTokenMetadataResult)> +
+ `transfers` + + `object` +
+ `transfers.getAssetTransfers` + + `RpcAction`\<[`GetAssetTransfersParams`](GetAssetTransfersParams), [`GetAssetTransfersResult`](GetAssetTransfersResult)> +
+ `transfers.getAssetTransfersPages` + + `PagesAction`\<[`GetAssetTransfersParams`](GetAssetTransfersParams), [`GetAssetTransfersResult`](GetAssetTransfersResult)> +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/DataRpcSchema.mdx b/docs/pages/reference/data-apis/src/type-aliases/DataRpcSchema.mdx new file mode 100644 index 0000000000..0b9e08ac59 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/DataRpcSchema.mdx @@ -0,0 +1,28 @@ +--- +title: DataRpcSchema +description: 'viem RpcSchema entries for the Data JSON-RPC methods. Attach to a client to get typed `client.request({ method: "alchemy_getAssetTransfers", ... })`. The transfers ReturnType uses the SDK''s GetAssetTransfersResult, which collapses the spec''s "Not Found (null)" string branch (a docs-spec artifact).' +slug: wallets/reference/data-apis/type-aliases/DataRpcSchema +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type DataRpcSchema = [ + { + Method: TransfersRpcSchema[0]["Method"]; + Parameters: TransfersRpcSchema[0]["Parameters"]; + ReturnType: GetAssetTransfersResult; + }, + ...TokenRpcSchema, +]; +``` + +Defined in: [packages/data-apis/src/schema/rpc.ts:21](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/schema/rpc.ts#L21) + +viem RpcSchema entries for the Data JSON-RPC methods. Attach to a client to +get typed `client.request({ method: "alchemy_getAssetTransfers", ... })`. + +The transfers ReturnType uses the SDK's [GetAssetTransfersResult](GetAssetTransfersResult), +which collapses the spec's "Not Found (null)" string branch (a docs-spec +artifact). diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetAssetTransfersParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetAssetTransfersParams.mdx new file mode 100644 index 0000000000..4adef767a7 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetAssetTransfersParams.mdx @@ -0,0 +1,45 @@ +--- +title: GetAssetTransfersParams +description: Generated RPC params plus the SDK's network override. +slug: wallets/reference/data-apis/type-aliases/GetAssetTransfersParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetAssetTransfersParams = AlchemyGetAssetTransfersParams & object; +``` + +Defined in: [packages/data-apis/src/types.ts:282](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L282) + +Generated RPC params plus the SDK's network override. + +## Type Declaration + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ `network?` + + `NetworkInput` + + Overrides the client-level network for this request. +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetAssetTransfersResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetAssetTransfersResult.mdx new file mode 100644 index 0000000000..67f80d4898 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetAssetTransfersResult.mdx @@ -0,0 +1,18 @@ +--- +title: GetAssetTransfersResult +description: 'The spec result is `oneOf: ["Not Found (null)" string, object]`; the string branch is a docs-spec artifact the SDK deliberately does not surface, so it is collapsed away here (and in DataRpcSchema).' +slug: wallets/reference/data-apis/type-aliases/GetAssetTransfersResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetAssetTransfersResult = Exclude; +``` + +Defined in: [packages/data-apis/src/types.ts:292](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L292) + +The spec result is `oneOf: ["Not Found (null)" string, object]`; the string +branch is a docs-spec artifact the SDK deliberately does not surface, so it +is collapsed away here (and in [DataRpcSchema](DataRpcSchema)). diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetCollectionMetadataParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetCollectionMetadataParams.mdx new file mode 100644 index 0000000000..d8dbf166e6 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetCollectionMetadataParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetCollectionMetadataParams +description: Overview of GetCollectionMetadataParams +slug: wallets/reference/data-apis/type-aliases/GetCollectionMetadataParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetCollectionMetadataParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:200](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L200) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetCollectionMetadataResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetCollectionMetadataResult.mdx new file mode 100644 index 0000000000..824a54d012 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetCollectionMetadataResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetCollectionMetadataResult +description: Overview of GetCollectionMetadataResult +slug: wallets/reference/data-apis/type-aliases/GetCollectionMetadataResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetCollectionMetadataResult = GetCollectionMetadataResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:202](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L202) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetCollectionsForOwnerParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetCollectionsForOwnerParams.mdx new file mode 100644 index 0000000000..3c83c0796b --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetCollectionsForOwnerParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetCollectionsForOwnerParams +description: Overview of GetCollectionsForOwnerParams +slug: wallets/reference/data-apis/type-aliases/GetCollectionsForOwnerParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetCollectionsForOwnerParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:208](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L208) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetCollectionsForOwnerResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetCollectionsForOwnerResult.mdx new file mode 100644 index 0000000000..365f3380f4 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetCollectionsForOwnerResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetCollectionsForOwnerResult +description: Overview of GetCollectionsForOwnerResult +slug: wallets/reference/data-apis/type-aliases/GetCollectionsForOwnerResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetCollectionsForOwnerResult = GetCollectionsForOwnerResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:210](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L210) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchParams.mdx new file mode 100644 index 0000000000..102e4c55d7 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchParams.mdx @@ -0,0 +1,43 @@ +--- +title: GetContractMetadataBatchParams +description: Overview of GetContractMetadataBatchParams +slug: wallets/reference/data-apis/type-aliases/GetContractMetadataBatchParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetContractMetadataBatchParams = GetContractMetadataBatchBody & object; +``` + +Defined in: [packages/data-apis/src/types.ts:194](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L194) + +## Type Declaration + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ `network?` + + `NetworkInput` + + Overrides the client-level network for this request. +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchResult.mdx new file mode 100644 index 0000000000..cee96390d3 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetContractMetadataBatchResult +description: Overview of GetContractMetadataBatchResult +slug: wallets/reference/data-apis/type-aliases/GetContractMetadataBatchResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetContractMetadataBatchResult = any[]; +``` + +Defined in: [packages/data-apis/src/types.ts:198](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L198) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataParams.mdx new file mode 100644 index 0000000000..9b35b87eda --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetContractMetadataParams +description: Overview of GetContractMetadataParams +slug: wallets/reference/data-apis/type-aliases/GetContractMetadataParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetContractMetadataParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:191](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L191) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataResult.mdx new file mode 100644 index 0000000000..67d65acb42 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetContractMetadataResult +description: Overview of GetContractMetadataResult +slug: wallets/reference/data-apis/type-aliases/GetContractMetadataResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetContractMetadataResult = GetContractMetadataResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:192](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L192) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetContractsForOwnerParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetContractsForOwnerParams.mdx new file mode 100644 index 0000000000..24497ba16a --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetContractsForOwnerParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetContractsForOwnerParams +description: Overview of GetContractsForOwnerParams +slug: wallets/reference/data-apis/type-aliases/GetContractsForOwnerParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetContractsForOwnerParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:204](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L204) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetContractsForOwnerResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetContractsForOwnerResult.mdx new file mode 100644 index 0000000000..b3e50636c5 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetContractsForOwnerResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetContractsForOwnerResult +description: Overview of GetContractsForOwnerResult +slug: wallets/reference/data-apis/type-aliases/GetContractsForOwnerResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetContractsForOwnerResult = GetContractsForOwnerResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:206](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L206) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetFloorPriceParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetFloorPriceParams.mdx new file mode 100644 index 0000000000..4aa9340839 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetFloorPriceParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetFloorPriceParams +description: Overview of GetFloorPriceParams +slug: wallets/reference/data-apis/type-aliases/GetFloorPriceParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetFloorPriceParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:222](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L222) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetFloorPriceResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetFloorPriceResult.mdx new file mode 100644 index 0000000000..ed0baef02f --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetFloorPriceResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetFloorPriceResult +description: Overview of GetFloorPriceResult +slug: wallets/reference/data-apis/type-aliases/GetFloorPriceResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetFloorPriceResult = GetFloorPriceResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:223](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L223) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesParams.mdx new file mode 100644 index 0000000000..f0b4b5cf45 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesParams.mdx @@ -0,0 +1,15 @@ +--- +title: GetHistoricalTokenPricesParams +description: Overview of GetHistoricalTokenPricesParams +slug: wallets/reference/data-apis/type-aliases/GetHistoricalTokenPricesParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetHistoricalTokenPricesParams = + WithNetworkInput; +``` + +Defined in: [packages/data-apis/src/types.ts:166](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L166) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesResult.mdx new file mode 100644 index 0000000000..d868effe1c --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetHistoricalTokenPricesResult +description: Overview of GetHistoricalTokenPricesResult +slug: wallets/reference/data-apis/type-aliases/GetHistoricalTokenPricesResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetHistoricalTokenPricesResult = GetHistoricalTokenPricesResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:168](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L168) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftContractsByAddressParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftContractsByAddressParams.mdx new file mode 100644 index 0000000000..c04c503e87 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftContractsByAddressParams.mdx @@ -0,0 +1,15 @@ +--- +title: GetNftContractsByAddressParams +description: Overview of GetNftContractsByAddressParams +slug: wallets/reference/data-apis/type-aliases/GetNftContractsByAddressParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftContractsByAddressParams = + PortfolioParams; +``` + +Defined in: [packages/data-apis/src/types.ts:141](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L141) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftContractsByAddressResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftContractsByAddressResult.mdx new file mode 100644 index 0000000000..e988b1a438 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftContractsByAddressResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftContractsByAddressResult +description: Overview of GetNftContractsByAddressResult +slug: wallets/reference/data-apis/type-aliases/GetNftContractsByAddressResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftContractsByAddressResult = GetNftContractsByAddressResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:143](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L143) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchParams.mdx new file mode 100644 index 0000000000..ee0e595eb9 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchParams.mdx @@ -0,0 +1,43 @@ +--- +title: GetNftMetadataBatchParams +description: Overview of GetNftMetadataBatchParams +slug: wallets/reference/data-apis/type-aliases/GetNftMetadataBatchParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftMetadataBatchParams = GetNftMetadataBatchBody & object; +``` + +Defined in: [packages/data-apis/src/types.ts:185](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L185) + +## Type Declaration + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ `network?` + + `NetworkInput` + + Overrides the client-level network for this request. +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchResult.mdx new file mode 100644 index 0000000000..509180a453 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftMetadataBatchResult +description: Overview of GetNftMetadataBatchResult +slug: wallets/reference/data-apis/type-aliases/GetNftMetadataBatchResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftMetadataBatchResult = any[]; +``` + +Defined in: [packages/data-apis/src/types.ts:189](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L189) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataParams.mdx new file mode 100644 index 0000000000..e30aabe9fe --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftMetadataParams +description: Overview of GetNftMetadataParams +slug: wallets/reference/data-apis/type-aliases/GetNftMetadataParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftMetadataParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:182](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L182) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataResult.mdx new file mode 100644 index 0000000000..5d50656e60 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftMetadataResult +description: Overview of GetNftMetadataResult +slug: wallets/reference/data-apis/type-aliases/GetNftMetadataResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftMetadataResult = GetNftMetadataResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:183](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L183) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftSalesParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftSalesParams.mdx new file mode 100644 index 0000000000..2569e900e8 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftSalesParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftSalesParams +description: Overview of GetNftSalesParams +slug: wallets/reference/data-apis/type-aliases/GetNftSalesParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftSalesParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:219](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L219) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftSalesResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftSalesResult.mdx new file mode 100644 index 0000000000..2fdf7bf7a3 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftSalesResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftSalesResult +description: Overview of GetNftSalesResult +slug: wallets/reference/data-apis/type-aliases/GetNftSalesResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftSalesResult = GetNftSalesResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:220](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L220) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftsByAddressParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftsByAddressParams.mdx new file mode 100644 index 0000000000..47a137f87b --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftsByAddressParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftsByAddressParams +description: Overview of GetNftsByAddressParams +slug: wallets/reference/data-apis/type-aliases/GetNftsByAddressParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftsByAddressParams = PortfolioParams; +``` + +Defined in: [packages/data-apis/src/types.ts:138](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L138) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftsByAddressResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftsByAddressResult.mdx new file mode 100644 index 0000000000..7979beba13 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftsByAddressResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftsByAddressResult +description: Overview of GetNftsByAddressResult +slug: wallets/reference/data-apis/type-aliases/GetNftsByAddressResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftsByAddressResult = GetNftsByAddressResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:139](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L139) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftsForCollectionParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftsForCollectionParams.mdx new file mode 100644 index 0000000000..ffd8d215b9 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftsForCollectionParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftsForCollectionParams +description: Overview of GetNftsForCollectionParams +slug: wallets/reference/data-apis/type-aliases/GetNftsForCollectionParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftsForCollectionParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:178](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L178) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftsForCollectionResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftsForCollectionResult.mdx new file mode 100644 index 0000000000..24f651a906 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftsForCollectionResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftsForCollectionResult +description: Overview of GetNftsForCollectionResult +slug: wallets/reference/data-apis/type-aliases/GetNftsForCollectionResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftsForCollectionResult = GetNftsForCollectionResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:180](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L180) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftsForContractParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftsForContractParams.mdx new file mode 100644 index 0000000000..820b670a63 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftsForContractParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftsForContractParams +description: Overview of GetNftsForContractParams +slug: wallets/reference/data-apis/type-aliases/GetNftsForContractParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftsForContractParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:175](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L175) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftsForContractResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftsForContractResult.mdx new file mode 100644 index 0000000000..7109b40278 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftsForContractResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftsForContractResult +description: Overview of GetNftsForContractResult +slug: wallets/reference/data-apis/type-aliases/GetNftsForContractResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftsForContractResult = GetNftsForContractResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:176](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L176) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftsForOwnerParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftsForOwnerParams.mdx new file mode 100644 index 0000000000..858929b264 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftsForOwnerParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftsForOwnerParams +description: Overview of GetNftsForOwnerParams +slug: wallets/reference/data-apis/type-aliases/GetNftsForOwnerParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftsForOwnerParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:172](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L172) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftsForOwnerResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftsForOwnerResult.mdx new file mode 100644 index 0000000000..c64bc22f68 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftsForOwnerResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetNftsForOwnerResult +description: Overview of GetNftsForOwnerResult +slug: wallets/reference/data-apis/type-aliases/GetNftsForOwnerResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetNftsForOwnerResult = GetNftsForOwnerResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:173](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L173) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetOwnersForContractParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetOwnersForContractParams.mdx new file mode 100644 index 0000000000..9f5eb3107f --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetOwnersForContractParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetOwnersForContractParams +description: Overview of GetOwnersForContractParams +slug: wallets/reference/data-apis/type-aliases/GetOwnersForContractParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetOwnersForContractParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:215](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L215) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetOwnersForContractResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetOwnersForContractResult.mdx new file mode 100644 index 0000000000..906bc149c4 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetOwnersForContractResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetOwnersForContractResult +description: Overview of GetOwnersForContractResult +slug: wallets/reference/data-apis/type-aliases/GetOwnersForContractResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetOwnersForContractResult = GetOwnersForContractResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:217](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L217) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetOwnersForNftParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetOwnersForNftParams.mdx new file mode 100644 index 0000000000..55127ea63d --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetOwnersForNftParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetOwnersForNftParams +description: Overview of GetOwnersForNftParams +slug: wallets/reference/data-apis/type-aliases/GetOwnersForNftParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetOwnersForNftParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:212](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L212) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetOwnersForNftResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetOwnersForNftResult.mdx new file mode 100644 index 0000000000..94b6aecc51 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetOwnersForNftResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetOwnersForNftResult +description: Overview of GetOwnersForNftResult +slug: wallets/reference/data-apis/type-aliases/GetOwnersForNftResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetOwnersForNftResult = GetOwnersForNftResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:213](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L213) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetSpamContractsParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetSpamContractsParams.mdx new file mode 100644 index 0000000000..8bbc8aef0b --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetSpamContractsParams.mdx @@ -0,0 +1,43 @@ +--- +title: GetSpamContractsParams +description: Overview of GetSpamContractsParams +slug: wallets/reference/data-apis/type-aliases/GetSpamContractsParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetSpamContractsParams = object; +``` + +Defined in: [packages/data-apis/src/types.ts:229](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L229) + +## Properties + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ `network?` + + `NetworkInput` + + Overrides the client-level network for this request. +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetSpamContractsResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetSpamContractsResult.mdx new file mode 100644 index 0000000000..f80bd26e71 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetSpamContractsResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetSpamContractsResult +description: Overview of GetSpamContractsResult +slug: wallets/reference/data-apis/type-aliases/GetSpamContractsResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetSpamContractsResult = GetSpamContractsResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:233](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L233) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceParams.mdx new file mode 100644 index 0000000000..f664a3d457 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceParams.mdx @@ -0,0 +1,43 @@ +--- +title: GetTokenAllowanceParams +description: Overview of GetTokenAllowanceParams +slug: wallets/reference/data-apis/type-aliases/GetTokenAllowanceParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokenAllowanceParams = AlchemyGetTokenAllowanceParams & object; +``` + +Defined in: [packages/data-apis/src/types.ts:273](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L273) + +## Type Declaration + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ `network?` + + `NetworkInput` + + Overrides the client-level network for this request. +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceResult.mdx new file mode 100644 index 0000000000..3d86752095 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetTokenAllowanceResult +description: Overview of GetTokenAllowanceResult +slug: wallets/reference/data-apis/type-aliases/GetTokenAllowanceResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokenAllowanceResult = AlchemyGetTokenAllowanceResult; +``` + +Defined in: [packages/data-apis/src/types.ts:277](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L277) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesByAddressParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesByAddressParams.mdx new file mode 100644 index 0000000000..1c5a006791 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesByAddressParams.mdx @@ -0,0 +1,15 @@ +--- +title: GetTokenBalancesByAddressParams +description: Overview of GetTokenBalancesByAddressParams +slug: wallets/reference/data-apis/type-aliases/GetTokenBalancesByAddressParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokenBalancesByAddressParams = + PortfolioParams; +``` + +Defined in: [packages/data-apis/src/types.ts:134](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L134) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesByAddressResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesByAddressResult.mdx new file mode 100644 index 0000000000..b1ec86bb47 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesByAddressResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetTokenBalancesByAddressResult +description: Overview of GetTokenBalancesByAddressResult +slug: wallets/reference/data-apis/type-aliases/GetTokenBalancesByAddressResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokenBalancesByAddressResult = GetTokenBalancesByAddressResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:136](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L136) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesParams.mdx new file mode 100644 index 0000000000..97e04e5efc --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesParams.mdx @@ -0,0 +1,85 @@ +--- +title: GetTokenBalancesParams +description: Overview of GetTokenBalancesParams +slug: wallets/reference/data-apis/type-aliases/GetTokenBalancesParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokenBalancesParams = object; +``` + +Defined in: [packages/data-apis/src/types.ts:253](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L253) + +## Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ `address` + + `AlchemyGetTokenBalancesAddressParam` + + The address to fetch balances for. +
+ `network?` + + `NetworkInput` + + Overrides the client-level network for this request. +
+ `options?` + + `AlchemyGetTokenBalancesOptionsParam` + + Paging options (pageKey/maxCount; only valid with the "erc20" spec). +
+ `tokenSpec?` + + `AlchemyGetTokenBalancesTokenSpecParam` + + "erc20" | "NATIVE\_TOKEN" | "DEFAULT\_TOKENS" or an explicit contract list. +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesResult.mdx new file mode 100644 index 0000000000..ff233c79ca --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetTokenBalancesResult +description: Overview of GetTokenBalancesResult +slug: wallets/reference/data-apis/type-aliases/GetTokenBalancesResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokenBalancesResult = AlchemyGetTokenBalancesResult; +``` + +Defined in: [packages/data-apis/src/types.ts:263](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L263) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenMetadataParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenMetadataParams.mdx new file mode 100644 index 0000000000..6a8f2ef68f --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenMetadataParams.mdx @@ -0,0 +1,57 @@ +--- +title: GetTokenMetadataParams +description: Overview of GetTokenMetadataParams +slug: wallets/reference/data-apis/type-aliases/GetTokenMetadataParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokenMetadataParams = object; +``` + +Defined in: [packages/data-apis/src/types.ts:265](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L265) + +## Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ `contractAddress` + + `AlchemyGetTokenMetadataParams` + + The token contract address. +
+ `network?` + + `NetworkInput` + + Overrides the client-level network for this request. +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenMetadataResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenMetadataResult.mdx new file mode 100644 index 0000000000..6f5eb8e600 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenMetadataResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetTokenMetadataResult +description: Overview of GetTokenMetadataResult +slug: wallets/reference/data-apis/type-aliases/GetTokenMetadataResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokenMetadataResult = AlchemyGetTokenMetadataResult; +``` + +Defined in: [packages/data-apis/src/types.ts:271](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L271) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesByAddressParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesByAddressParams.mdx new file mode 100644 index 0000000000..8da2057662 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesByAddressParams.mdx @@ -0,0 +1,42 @@ +--- +title: GetTokenPricesByAddressParams +description: Overview of GetTokenPricesByAddressParams +slug: wallets/reference/data-apis/type-aliases/GetTokenPricesByAddressParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokenPricesByAddressParams = Omit< + GetTokenPricesByAddressBody, + "addresses" +> & + object; +``` + +Defined in: [packages/data-apis/src/types.ts:158](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L158) + +## Type Declaration + + + + + + + + + + + + + + + + + +
NameType
+ `addresses` + + [`PriceAddressEntry`](../interfaces/PriceAddressEntry)\[] +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesByAddressResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesByAddressResult.mdx new file mode 100644 index 0000000000..c1a5b8f7a0 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesByAddressResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetTokenPricesByAddressResult +description: Overview of GetTokenPricesByAddressResult +slug: wallets/reference/data-apis/type-aliases/GetTokenPricesByAddressResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokenPricesByAddressResult = GetTokenPricesByAddressResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:164](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L164) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesBySymbolParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesBySymbolParams.mdx new file mode 100644 index 0000000000..a4340e24fb --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesBySymbolParams.mdx @@ -0,0 +1,16 @@ +--- +title: GetTokenPricesBySymbolParams +description: "Chain-agnostic: token symbols only, no network involved." +slug: wallets/reference/data-apis/type-aliases/GetTokenPricesBySymbolParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokenPricesBySymbolParams = GetTokenPricesBySymbolQuery; +``` + +Defined in: [packages/data-apis/src/types.ts:148](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L148) + +Chain-agnostic: token symbols only, no network involved. diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesBySymbolResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesBySymbolResult.mdx new file mode 100644 index 0000000000..314f543ba0 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenPricesBySymbolResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetTokenPricesBySymbolResult +description: Overview of GetTokenPricesBySymbolResult +slug: wallets/reference/data-apis/type-aliases/GetTokenPricesBySymbolResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokenPricesBySymbolResult = GetTokenPricesBySymbolResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:149](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L149) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokensByAddressParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokensByAddressParams.mdx new file mode 100644 index 0000000000..d33a42a825 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokensByAddressParams.mdx @@ -0,0 +1,14 @@ +--- +title: GetTokensByAddressParams +description: Overview of GetTokensByAddressParams +slug: wallets/reference/data-apis/type-aliases/GetTokensByAddressParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokensByAddressParams = PortfolioParams; +``` + +Defined in: [packages/data-apis/src/types.ts:128](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L128) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokensByAddressResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokensByAddressResult.mdx new file mode 100644 index 0000000000..af2758041b --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokensByAddressResult.mdx @@ -0,0 +1,14 @@ +--- +title: GetTokensByAddressResult +description: Overview of GetTokensByAddressResult +slug: wallets/reference/data-apis/type-aliases/GetTokensByAddressResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type GetTokensByAddressResult = GetTokensByAddressResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:129](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L129) diff --git a/docs/pages/reference/data-apis/src/type-aliases/IsAirdropNftParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/IsAirdropNftParams.mdx new file mode 100644 index 0000000000..4d4d0773ff --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/IsAirdropNftParams.mdx @@ -0,0 +1,14 @@ +--- +title: IsAirdropNftParams +description: Overview of IsAirdropNftParams +slug: wallets/reference/data-apis/type-aliases/IsAirdropNftParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type IsAirdropNftParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:238](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L238) diff --git a/docs/pages/reference/data-apis/src/type-aliases/IsAirdropNftResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/IsAirdropNftResult.mdx new file mode 100644 index 0000000000..39de5788f4 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/IsAirdropNftResult.mdx @@ -0,0 +1,14 @@ +--- +title: IsAirdropNftResult +description: Overview of IsAirdropNftResult +slug: wallets/reference/data-apis/type-aliases/IsAirdropNftResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type IsAirdropNftResult = IsAirdropNftResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:239](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L239) diff --git a/docs/pages/reference/data-apis/src/type-aliases/IsHolderOfContractParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/IsHolderOfContractParams.mdx new file mode 100644 index 0000000000..fb4a00261d --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/IsHolderOfContractParams.mdx @@ -0,0 +1,14 @@ +--- +title: IsHolderOfContractParams +description: Overview of IsHolderOfContractParams +slug: wallets/reference/data-apis/type-aliases/IsHolderOfContractParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type IsHolderOfContractParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:241](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L241) diff --git a/docs/pages/reference/data-apis/src/type-aliases/IsHolderOfContractResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/IsHolderOfContractResult.mdx new file mode 100644 index 0000000000..a6b797270f --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/IsHolderOfContractResult.mdx @@ -0,0 +1,14 @@ +--- +title: IsHolderOfContractResult +description: Overview of IsHolderOfContractResult +slug: wallets/reference/data-apis/type-aliases/IsHolderOfContractResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type IsHolderOfContractResult = IsHolderOfContractResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:242](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L242) diff --git a/docs/pages/reference/data-apis/src/type-aliases/IsSpamContractParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/IsSpamContractParams.mdx new file mode 100644 index 0000000000..1d24a34285 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/IsSpamContractParams.mdx @@ -0,0 +1,14 @@ +--- +title: IsSpamContractParams +description: Overview of IsSpamContractParams +slug: wallets/reference/data-apis/type-aliases/IsSpamContractParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type IsSpamContractParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:235](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L235) diff --git a/docs/pages/reference/data-apis/src/type-aliases/IsSpamContractResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/IsSpamContractResult.mdx new file mode 100644 index 0000000000..1bb482d015 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/IsSpamContractResult.mdx @@ -0,0 +1,14 @@ +--- +title: IsSpamContractResult +description: Overview of IsSpamContractResult +slug: wallets/reference/data-apis/type-aliases/IsSpamContractResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type IsSpamContractResult = IsSpamContractResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:236](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L236) diff --git a/docs/pages/reference/data-apis/src/type-aliases/NftRestSchema.mdx b/docs/pages/reference/data-apis/src/type-aliases/NftRestSchema.mdx new file mode 100644 index 0000000000..08f80b75d3 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/NftRestSchema.mdx @@ -0,0 +1,164 @@ +--- +title: NftRestSchema +description: RestRequestSchema entries for the nft REST API. +slug: wallets/reference/data-apis/type-aliases/NftRestSchema +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type NftRestSchema = readonly [ + { + Body?: undefined; + Method: "GET"; + Query: GetNftsForOwnerQuery; + Response: GetNftsForOwnerResponse; + Route: "getNFTsForOwner"; + }, + { + Body?: undefined; + Method: "GET"; + Query: GetNftsForContractQuery; + Response: GetNftsForContractResponse; + Route: "getNFTsForContract"; + }, + { + Body?: undefined; + Method: "GET"; + Query?: GetNftsForCollectionQuery; + Response: GetNftsForCollectionResponse; + Route: "getNFTsForCollection"; + }, + { + Body?: undefined; + Method: "GET"; + Query: GetNftMetadataQuery; + Response: GetNftMetadataResponse; + Route: "getNFTMetadata"; + }, + { + Body: GetNftMetadataBatchBody; + Method: "POST"; + Query?: undefined; + Response: any[]; + Route: "getNFTMetadataBatch"; + }, + { + Body?: undefined; + Method: "GET"; + Query: GetContractMetadataQuery; + Response: GetContractMetadataResponse; + Route: "getContractMetadata"; + }, + { + Body: GetContractMetadataBatchBody; + Method: "POST"; + Query?: undefined; + Response: any[]; + Route: "getContractMetadataBatch"; + }, + { + Body?: undefined; + Method: "GET"; + Query: GetCollectionMetadataQuery; + Response: GetCollectionMetadataResponse; + Route: "getCollectionMetadata"; + }, + { + Body?: undefined; + Method: "GET"; + Query: GetContractsForOwnerQuery; + Response: GetContractsForOwnerResponse; + Route: "getContractsForOwner"; + }, + { + Body?: undefined; + Method: "GET"; + Query: GetCollectionsForOwnerQuery; + Response: GetCollectionsForOwnerResponse; + Route: "getCollectionsForOwner"; + }, + { + Body?: undefined; + Method: "GET"; + Query: GetOwnersForNftQuery; + Response: GetOwnersForNftResponse; + Route: "getOwnersForNFT"; + }, + { + Body?: undefined; + Method: "GET"; + Query: GetOwnersForContractQuery; + Response: GetOwnersForContractResponse; + Route: "getOwnersForContract"; + }, + { + Body?: undefined; + Method: "GET"; + Query?: GetNftSalesQuery; + Response: GetNftSalesResponse; + Route: "getNFTSales"; + }, + { + Body?: undefined; + Method: "GET"; + Query: GetFloorPriceQuery; + Response: GetFloorPriceResponse; + Route: "getFloorPrice"; + }, + { + Body?: undefined; + Method: "GET"; + Query: SearchContractMetadataQuery; + Response: any[]; + Route: "searchContractMetadata"; + }, + { + Body?: undefined; + Method: "GET"; + Query?: undefined; + Response: GetSpamContractsResponse; + Route: "getSpamContracts"; + }, + { + Body?: undefined; + Method: "GET"; + Query: IsSpamContractQuery; + Response: IsSpamContractResponse; + Route: "isSpamContract"; + }, + { + Body?: undefined; + Method: "GET"; + Query: IsAirdropNftQuery; + Response: IsAirdropNftResponse; + Route: "isAirdropNFT"; + }, + { + Body?: undefined; + Method: "GET"; + Query: IsHolderOfContractQuery; + Response: IsHolderOfContractResponse; + Route: "isHolderOfContract"; + }, + { + Body?: undefined; + Method: "GET"; + Query: ComputeRarityQuery; + Response: ComputeRarityResponse; + Route: "computeRarity"; + }, + { + Body?: undefined; + Method: "GET"; + Query: SummarizeNftAttributesQuery; + Response: SummarizeNftAttributesResponse; + Route: "summarizeNFTAttributes"; + }, +]; +``` + +Defined in: [packages/data-apis/src/generated/rest/nft.schema.ts:194](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/generated/rest/nft.schema.ts#L194) + +RestRequestSchema entries for the nft REST API. diff --git a/docs/pages/reference/data-apis/src/type-aliases/PaginateOptions.mdx b/docs/pages/reference/data-apis/src/type-aliases/PaginateOptions.mdx new file mode 100644 index 0000000000..2f2615d7a7 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/PaginateOptions.mdx @@ -0,0 +1,59 @@ +--- +title: PaginateOptions +description: Options accepted by the `*Pages` async-iterator actions. +slug: wallets/reference/data-apis/type-aliases/PaginateOptions +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type PaginateOptions = object; +``` + +Defined in: [packages/data-apis/src/internal/paginate.ts:4](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/internal/paginate.ts#L4) + +Options accepted by the `*Pages` async-iterator actions. + +## Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ `maxPages?` + + `number` + + Stop after this many pages (the page that hits the cap is still yielded). +
+ `signal?` + + `AbortSignal` + + Aborts iteration (checked between pages and passed to page requests). +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/PortfolioRestSchema.mdx b/docs/pages/reference/data-apis/src/type-aliases/PortfolioRestSchema.mdx new file mode 100644 index 0000000000..b1aff7e126 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/PortfolioRestSchema.mdx @@ -0,0 +1,45 @@ +--- +title: PortfolioRestSchema +description: RestRequestSchema entries for the portfolio REST API. +slug: wallets/reference/data-apis/type-aliases/PortfolioRestSchema +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type PortfolioRestSchema = readonly [ + { + Body: GetTokensByAddressBody; + Method: "POST"; + Query?: undefined; + Response: GetTokensByAddressResponse; + Route: "assets/tokens/by-address"; + }, + { + Body: GetTokenBalancesByAddressBody; + Method: "POST"; + Query?: undefined; + Response: GetTokenBalancesByAddressResponse; + Route: "assets/tokens/balances/by-address"; + }, + { + Body: GetNftsByAddressBody; + Method: "POST"; + Query?: undefined; + Response: GetNftsByAddressResponse; + Route: "assets/nfts/by-address"; + }, + { + Body: GetNftContractsByAddressBody; + Method: "POST"; + Query?: undefined; + Response: GetNftContractsByAddressResponse; + Route: "assets/nfts/contracts/by-address"; + }, +]; +``` + +Defined in: [packages/data-apis/src/generated/rest/portfolio.schema.ts:46](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/generated/rest/portfolio.schema.ts#L46) + +RestRequestSchema entries for the portfolio REST API. diff --git a/docs/pages/reference/data-apis/src/type-aliases/PortfolioToken.mdx b/docs/pages/reference/data-apis/src/type-aliases/PortfolioToken.mdx new file mode 100644 index 0000000000..68be5b085a --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/PortfolioToken.mdx @@ -0,0 +1,16 @@ +--- +title: PortfolioToken +description: Overview of PortfolioToken +slug: wallets/reference/data-apis/type-aliases/PortfolioToken +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type PortfolioToken = NonNullable< + GetTokensByAddressResponse["data"]["tokens"] +>[number]; +``` + +Defined in: [packages/data-apis/src/types.ts:130](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L130) diff --git a/docs/pages/reference/data-apis/src/type-aliases/PricesRestSchema.mdx b/docs/pages/reference/data-apis/src/type-aliases/PricesRestSchema.mdx new file mode 100644 index 0000000000..8229bff260 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/PricesRestSchema.mdx @@ -0,0 +1,38 @@ +--- +title: PricesRestSchema +description: RestRequestSchema entries for the prices REST API. +slug: wallets/reference/data-apis/type-aliases/PricesRestSchema +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type PricesRestSchema = readonly [ + { + Body?: undefined; + Method: "GET"; + Query: GetTokenPricesBySymbolQuery; + Response: GetTokenPricesBySymbolResponse; + Route: "tokens/by-symbol"; + }, + { + Body: GetTokenPricesByAddressBody; + Method: "POST"; + Query?: undefined; + Response: GetTokenPricesByAddressResponse; + Route: "tokens/by-address"; + }, + { + Body: GetHistoricalTokenPricesBody; + Method: "POST"; + Query?: undefined; + Response: GetHistoricalTokenPricesResponse; + Route: "tokens/historical"; + }, +]; +``` + +Defined in: [packages/data-apis/src/generated/rest/prices.schema.ts:37](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/generated/rest/prices.schema.ts#L37) + +RestRequestSchema entries for the prices REST API. diff --git a/docs/pages/reference/data-apis/src/type-aliases/RequestOptions.mdx b/docs/pages/reference/data-apis/src/type-aliases/RequestOptions.mdx new file mode 100644 index 0000000000..23f240cbbf --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/RequestOptions.mdx @@ -0,0 +1,45 @@ +--- +title: RequestOptions +description: Per-request options accepted by data actions. +slug: wallets/reference/data-apis/type-aliases/RequestOptions +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type RequestOptions = object; +``` + +Defined in: [packages/data-apis/src/internal/clientHelpers.ts:20](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/internal/clientHelpers.ts#L20) + +Per-request options accepted by data actions. + +## Properties + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ `signal?` + + `AbortSignal` + + Aborts the request (and any pending retries). +
diff --git a/docs/pages/reference/data-apis/src/type-aliases/SearchContractMetadataParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/SearchContractMetadataParams.mdx new file mode 100644 index 0000000000..88da73d50d --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/SearchContractMetadataParams.mdx @@ -0,0 +1,14 @@ +--- +title: SearchContractMetadataParams +description: Overview of SearchContractMetadataParams +slug: wallets/reference/data-apis/type-aliases/SearchContractMetadataParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type SearchContractMetadataParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:225](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L225) diff --git a/docs/pages/reference/data-apis/src/type-aliases/SearchContractMetadataResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/SearchContractMetadataResult.mdx new file mode 100644 index 0000000000..76baae7ca7 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/SearchContractMetadataResult.mdx @@ -0,0 +1,14 @@ +--- +title: SearchContractMetadataResult +description: Overview of SearchContractMetadataResult +slug: wallets/reference/data-apis/type-aliases/SearchContractMetadataResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type SearchContractMetadataResult = any[]; +``` + +Defined in: [packages/data-apis/src/types.ts:227](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L227) diff --git a/docs/pages/reference/data-apis/src/type-aliases/SummarizeNftAttributesParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/SummarizeNftAttributesParams.mdx new file mode 100644 index 0000000000..eedb28d511 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/SummarizeNftAttributesParams.mdx @@ -0,0 +1,14 @@ +--- +title: SummarizeNftAttributesParams +description: Overview of SummarizeNftAttributesParams +slug: wallets/reference/data-apis/type-aliases/SummarizeNftAttributesParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type SummarizeNftAttributesParams = NetworkScoped; +``` + +Defined in: [packages/data-apis/src/types.ts:247](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L247) diff --git a/docs/pages/reference/data-apis/src/type-aliases/SummarizeNftAttributesResult.mdx b/docs/pages/reference/data-apis/src/type-aliases/SummarizeNftAttributesResult.mdx new file mode 100644 index 0000000000..2af500d854 --- /dev/null +++ b/docs/pages/reference/data-apis/src/type-aliases/SummarizeNftAttributesResult.mdx @@ -0,0 +1,14 @@ +--- +title: SummarizeNftAttributesResult +description: Overview of SummarizeNftAttributesResult +slug: wallets/reference/data-apis/type-aliases/SummarizeNftAttributesResult +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type SummarizeNftAttributesResult = SummarizeNftAttributesResponse; +``` + +Defined in: [packages/data-apis/src/types.ts:249](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L249) diff --git a/docs/pages/reference/data-apis/src/variables/VERSION.mdx b/docs/pages/reference/data-apis/src/variables/VERSION.mdx new file mode 100644 index 0000000000..8bc404b538 --- /dev/null +++ b/docs/pages/reference/data-apis/src/variables/VERSION.mdx @@ -0,0 +1,14 @@ +--- +title: VERSION +description: Overview of VERSION +slug: wallets/reference/data-apis/variables/VERSION +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +const VERSION: "5.0.3-alpha.0" = "5.0.3-alpha.0"; +``` + +Defined in: [packages/data-apis/src/version.ts:3](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/version.ts#L3) diff --git a/docs/pages/reference/modules.mdx b/docs/pages/reference/modules.mdx index c301a9751f..9f4a81fb67 100644 --- a/docs/pages/reference/modules.mdx +++ b/docs/pages/reference/modules.mdx @@ -13,6 +13,7 @@ layout: reference | :---------------------------------------------------------------------------------- | :---------- | | [aa-infra/src](aa-infra/src/README) | - | | [common/src](common/src/README) | - | +| [data-apis/src](data-apis/src/README) | - | | [smart-accounts/src](smart-accounts/src/README) | - | | [wallet-apis/src/exports](wallet-apis/src/exports/README) | - | | [wallet-apis/src/exports/experimental](wallet-apis/src/exports/experimental/README) | - | diff --git a/packages/data-apis/README.md b/packages/data-apis/README.md index cd7b4c5a69..33cfd75ec5 100644 --- a/packages/data-apis/README.md +++ b/packages/data-apis/README.md @@ -1,36 +1,104 @@ -# @alchemy/data-apis (MVP) +# @alchemy/data-apis -A vertical-slice prototype of the Data APIs SDK, built to prove the architecture -before scaling to the full v1 surface (Portfolio, Prices, NFT, Token, Transfers). +Alchemy's Data APIs — Portfolio, Prices, NFT, Token, and Transfers — as +typed, viem-style actions. **Currently published under the `alpha` dist-tag.** -## What this proves - -One method per seam, not full coverage: +```bash +npm install @alchemy/data-apis@alpha viem +``` -| Method | Channel | What it demonstrates | -| ------------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| `portfolio.getTokensByAddress` | REST → global `api.g.alchemy.com/data/v1` | Multi-network request bodies via `AlchemyRestClient`; networks are payload, the client's chain is not involved | -| `nft.getNftsForOwner` | REST → `{network}.g.alchemy.com/nft/v3` | Network-scoped endpoint resolution with per-request `network` override falling back to the client default | -| `transfers.getAssetTransfers` | JSON-RPC → `AlchemyTransport` | Plain viem action; network override derives a transport instance from `client.transport.config` | +## Quickstart -Plus the two entry points: +Two equivalent entry points: ```ts // Data-only developers (no viem knowledge required) -const data = createDataClient({ apiKey, network: "eth-mainnet" }); +import { createDataClient } from "@alchemy/data-apis"; + +const data = createDataClient({ + apiKey: process.env.ALCHEMY_API_KEY, + network: "eth-mainnet", // or `mainnet` from viem/chains, or "eip155:1" +}); + +// Developers already holding a viem client with an Alchemy transport +import { createClient } from "viem"; +import { mainnet } from "viem/chains"; +import { alchemyTransport } from "@alchemy/common"; +import { dataActions } from "@alchemy/data-apis"; -// Developers already on a viem client with an Alchemy transport const client = createClient({ chain: mainnet, - transport: alchemyTransport({ apiKey }), + transport: alchemyTransport({ apiKey: process.env.ALCHEMY_API_KEY }), }).extend(dataActions); ``` -Network inputs accept all three formats everywhere, resolved by -`resolveNetwork()` in `@alchemy/common`: a viem `Chain`, an Alchemy slug -(`"eth-mainnet"`), or CAIP-2 (`"eip155:1"`, `"solana:mainnet"`). The slug ↔ -chain-ID mapping is derived from the existing daikon-generated -`ALCHEMY_RPC_MAPPING` — no second registry. +Every action is also individually importable +(`import { getNftsForOwner } from "@alchemy/data-apis"`) for tree-shaking and +composability. + +## Namespaces + +```ts +// Portfolio — multi-network queries; networks travel per request +const tokens = await data.portfolio.getTokensByAddress({ + addresses: [ + { address: "0x...", networks: [mainnet, "base-mainnet", "solana-mainnet"] }, + ], +}); +// also: getTokenBalancesByAddress, getNftsByAddress, getNftContractsByAddress + +// Prices — chain-agnostic or address+network +const prices = await data.prices.getTokenPricesBySymbol({ + symbols: ["ETH", "USDC"], +}); +// also: getTokenPricesByAddress, getHistoricalTokenPrices + +// NFT — full v3 read surface (21 methods): ownership, metadata (+ batch), +// contracts/collections, owners, sales, floor price, search, spam/airdrop/rarity +const nfts = await data.nft.getNftsForOwner({ owner: "0x..." }); + +// Token — balances, metadata, allowance (JSON-RPC) +const balances = await data.token.getTokenBalances({ address: "0x..." }); + +// Transfers — historical transfer queries (JSON-RPC) +const transfers = await data.transfers.getAssetTransfers({ + category: ["erc20"], +}); +``` + +### Networks: three formats, everywhere + +Anywhere a network is accepted you can pass a viem `Chain`, an Alchemy slug +(`"eth-mainnet"`), or a CAIP-2 id (`"eip155:1"`, `"solana:mainnet"`) — +resolved by `resolveNetwork()` in `@alchemy/common`. Single-network methods +use the client's default network with a per-request `network` override; +multi-network methods (Portfolio, Prices-by-address) take networks in the +request itself. Registry-unknown slugs are passed through as an escape hatch. + +### Pagination + +Paginated methods have `*Pages` companions returning async generators that +manage cursors for you (and refuse to loop forever on repeated cursors): + +```ts +for await (const page of data.nft.getNftsForOwnerPages( + { owner: "0x..." }, + { maxPages: 10, signal: controller.signal }, +)) { + for (const nft of page.ownedNfts ?? []) { + // ... + } +} +``` + +### Errors + +Both channels (REST and JSON-RPC) normalize failures into `AlchemyApiError` +from `@alchemy/common`, carrying `status`, `code`, `requestId` (the +client-generated `X-Alchemy-Client-Request-Id`), and `retryAfter` when known. +REST requests retry 429/5xx/network failures with exponential backoff +(honoring `Retry-After`) and time out per attempt; pass an `AbortSignal` via +the per-request options to cancel. ## Generated internals @@ -42,15 +110,20 @@ hand-reviewed aliases, and `codegen.manifest.ts` maps spec operations to the generated surface — referencing a renamed/removed spec operation fails `pnpm generate` loudly. -## Companion changes in @alchemy/common +## Releasing -- `networks/networkRegistry.ts`: `resolveNetwork` + network types (slug map - derived from the registry URLs; to be emitted by ws-tools properly) -- `AlchemyRestClient` is now exported (was written for signer v5 but unexported) +While in alpha this package is deliberately **not** in `lerna.json`'s +fixed-version publish set, so the regular release workflow can't ship it as +`latest`. To publish an alpha: + +```bash +pnpm --filter @alchemy/data-apis build +cd packages/data-apis && pnpm publish --tag alpha +``` -## Deliberately out of scope (tracked in the data SDK scope plan) +Graduation checklist (when the team blesses a stable release): -- Rest client hardening: retries, timeouts, request-id, first-class query params -- Pagination iterators, error normalization, the SDK manifest, remaining methods -- ws-tools generator change to emit `{ slug, chainId, caip2 }` entries + - the `KnownAlchemyNetwork` union +1. Bump `version` to the shared monorepo version (drop the `-alpha.N` suffix). +2. Add `"packages/data-apis"` to `lerna.json`'s `packages` array. +3. Remove `publishConfig.tag`. +4. Announce the surface as semver-stable. diff --git a/packages/data-apis/package.json b/packages/data-apis/package.json index 4cba0ec8cc..c6c3882b14 100644 --- a/packages/data-apis/package.json +++ b/packages/data-apis/package.json @@ -1,7 +1,16 @@ { "name": "@alchemy/data-apis", - "version": "0.0.0", - "description": "Alchemy Data APIs SDK (Portfolio, Prices, NFT, Token, Transfers) — MVP", + "version": "5.0.3-alpha.0", + "description": "Alchemy Data APIs SDK: Portfolio, Prices, NFT, Token, and Transfers, as viem-style actions", + "keywords": [ + "alchemy", + "viem", + "nft", + "token", + "prices", + "portfolio", + "blockchain-data" + ], "author": "Alchemy", "license": "MIT", "private": false, @@ -53,7 +62,8 @@ }, "publishConfig": { "access": "public", - "registry": "https://registry.npmjs.org/" + "registry": "https://registry.npmjs.org/", + "tag": "alpha" }, "repository": { "type": "git", diff --git a/packages/data-apis/src/version.ts b/packages/data-apis/src/version.ts index 128c5e1f2d..5b39a18237 100644 --- a/packages/data-apis/src/version.ts +++ b/packages/data-apis/src/version.ts @@ -1,3 +1,3 @@ // This file is autogenerated by inject-version.ts. Any changes will be // overwritten on commit! -export const VERSION = "0.0.0"; +export const VERSION = "5.0.3-alpha.0"; diff --git a/scripts/generate-typedoc-yaml.ts b/scripts/generate-typedoc-yaml.ts index c977ac8745..f385c387dc 100644 --- a/scripts/generate-typedoc-yaml.ts +++ b/scripts/generate-typedoc-yaml.ts @@ -56,6 +56,7 @@ type TypeSections = Record; const PACKAGE_DISPLAY_NAMES: Record = { "aa-infra": "AA Infra", common: "Alchemy Common", + "data-apis": "Data APIs", "smart-accounts": "Smart Accounts", "wallet-apis": "Wallet APIs", } as const; @@ -65,6 +66,7 @@ const PACKAGE_DISPLAY_NAMES: Record = { const PACKAGES_INCLUDED_IN_NAV: string[] = [ "wallet-apis", "smart-accounts", + "data-apis", "aa-infra", "common", ]; diff --git a/tsconfig.typedoc.json b/tsconfig.typedoc.json index 4ebff17a81..26993b111f 100644 --- a/tsconfig.typedoc.json +++ b/tsconfig.typedoc.json @@ -11,6 +11,7 @@ "include": [ "packages/aa-infra/src/**/*", "packages/common/src/**/*", + "packages/data-apis/src/**/*", "packages/smart-accounts/src/**/*", "packages/wallet-apis/src/**/*" ], diff --git a/typedoc.json b/typedoc.json index 42b28064a2..b46a2319b6 100644 --- a/typedoc.json +++ b/typedoc.json @@ -8,6 +8,7 @@ "entryPoints": [ "packages/aa-infra/src/index.ts", "packages/common/src/index.ts", + "packages/data-apis/src/index.ts", "packages/smart-accounts/src/index.ts", "packages/wallet-apis/src/exports/index.ts", "packages/wallet-apis/src/exports/experimental.ts", From 90c0eee99d7d350070e6f5b17d73c072474a68da Mon Sep 17 00:00:00 2001 From: blake duncan Date: Wed, 10 Jun 2026 11:14:59 -0400 Subject: [PATCH 12/20] ci: codegen drift gate + spec-bump workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - on-pull-request: 'pnpm generate && git diff --exit-code' over generated output and spec snapshots, mirroring the existing docs:sdk drift check — fails if generation is stale or snapshots were hand-edited (offline, no new secrets) - bump-api-specs (workflow_dispatch): checks out the public docs repo at main, re-snapshots, regenerates, and opens a PR via create-pull-request; manifest validation hard-fails the run when an SDK-referenced operation was renamed/removed upstream Co-Authored-By: Claude Fable 5 --- .github/workflows/bump-api-specs.yml | 63 +++++++++++++++++++++++++++ .github/workflows/on-pull-request.yml | 6 +++ 2 files changed, 69 insertions(+) create mode 100644 .github/workflows/bump-api-specs.yml diff --git a/.github/workflows/bump-api-specs.yml b/.github/workflows/bump-api-specs.yml new file mode 100644 index 0000000000..76fa4af8db --- /dev/null +++ b/.github/workflows/bump-api-specs.yml @@ -0,0 +1,63 @@ +name: Bump API spec snapshots + +# Re-snapshots the bundled OpenAPI/OpenRPC specs from alchemyplatform/docs +# (public) at its current main, regenerates SDK internals, and opens a PR if +# anything changed. The manifest validation inside `pnpm generate` hard-fails +# the run if an operation the SDK depends on was renamed or removed — that +# failure is the drift alarm and needs a human decision, not a merge. +# +# Manual for now; switch `workflow_dispatch` to a schedule (or a +# repository_dispatch from the docs repo's publish workflow) once the cadence +# is settled. + +on: + workflow_dispatch: + +jobs: + bump-specs: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - name: Checkout docs repo + uses: actions/checkout@v4 + with: + repository: alchemyplatform/docs + path: .docs-checkout + + - name: Setup + uses: ./.github/actions/setup + + - name: Install docs dependencies + run: pnpm install --ignore-scripts + working-directory: .docs-checkout + + - name: Snapshot specs from docs main + run: pnpm tsx packages/api-codegen/src/cli.ts snapshot --docs "$GITHUB_WORKSPACE/.docs-checkout" + + - name: Regenerate SDK internals + run: | + pnpm run build:libs + pnpm run generate + + - name: Remove docs checkout before PR + run: rm -rf .docs-checkout + + - name: Open PR if anything changed + uses: peter-evans/create-pull-request@v6 + with: + commit-message: "chore(api-codegen): bump spec snapshots from docs main" + title: "chore(api-codegen): bump API spec snapshots" + body: | + Automated re-snapshot of the bundled OpenAPI/OpenRPC specs from + `alchemyplatform/docs` main, plus regenerated SDK internals. + + Review the `packages/api-codegen/specs/` diff for what moved + upstream and the `src/generated/` diff for the type impact. + Public types in `packages/data-apis/src/types.ts` are aliases — + check whether any public-surface change warrants a semver note. + branch: chore/bump-api-specs + delete-branch: true diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index 227d15dcb7..ad879fbf10 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -186,6 +186,12 @@ jobs: - name: Typecheck Test run: pnpm run test:typecheck + - name: Check generated API code is up to date + run: | + pnpm run generate + git diff --exit-code packages/*/src/generated packages/api-codegen/specs || \ + (echo "::error::Generated API code is out of date or spec snapshots were hand-edited. Run 'pnpm generate' and commit the changes." && exit 1) + - name: Check SDK reference docs are up to date run: | pnpm run docs:sdk From 66c0b88d4b8246afa9b97137ab56ea95373bb04d Mon Sep 17 00:00:00 2001 From: blake duncan Date: Wed, 10 Jun 2026 11:20:46 -0400 Subject: [PATCH 13/20] test(data-apis): type tests, export boundary, expanded live smoke suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - index.test-d.ts: 7 vitest typecheck tests (client/decorator surface equivalence, AlchemyTransport constraint enforcement, spec-accurate result shapes, three-format network params, Pages generator types); data-apis vitest config now enables typecheck under TYPECHECK=true like the shared config - exports.test.ts: locks the package's runtime export surface; generated internals barrel stays internal - smoke test grown 14 → 30 live tests: every namespace exercised against the real API (portfolio x3 new, prices x3, nft x5 new, token x3) plus two-page pagination walks for nft and transfers companions — all passing Co-Authored-By: Claude Fable 5 --- packages/data-apis/scripts/smoke-test.ts | 176 +++++++++++++++++++++++ packages/data-apis/src/exports.test.ts | 60 ++++++++ packages/data-apis/src/index.test-d.ts | 75 ++++++++++ packages/data-apis/vitest.config.ts | 7 + 4 files changed, 318 insertions(+) create mode 100644 packages/data-apis/src/exports.test.ts create mode 100644 packages/data-apis/src/index.test-d.ts diff --git a/packages/data-apis/scripts/smoke-test.ts b/packages/data-apis/scripts/smoke-test.ts index eb8fc81e75..9314dc6911 100644 --- a/packages/data-apis/scripts/smoke-test.ts +++ b/packages/data-apis/scripts/smoke-test.ts @@ -249,6 +249,182 @@ await test("nft via raw viem client", async () => { ); }); +console.log("\n\x1b[1mportfolio — new methods\x1b[0m"); + +await test("getTokenBalancesByAddress", async () => { + const result = await clientBySlug.portfolio.getTokenBalancesByAddress({ + addresses: [{ address: VITALIK, networks: ["eth-mainnet"] }], + }); + assert(Array.isArray(result.data.tokens), "data.tokens should be an array"); +}); + +await test("getNftsByAddress (paginated body method)", async () => { + const result = await clientBySlug.portfolio.getNftsByAddress({ + addresses: [{ address: VITALIK, networks: ["eth-mainnet"] }], + pageSize: 2, + }); + assert( + Array.isArray(result.data.ownedNfts), + "data.ownedNfts should be an array", + ); +}); + +await test("getNftContractsByAddress", async () => { + const result = await clientBySlug.portfolio.getNftContractsByAddress({ + addresses: [{ address: VITALIK, networks: ["eth-mainnet"] }], + pageSize: 2, + }); + assert( + Array.isArray(result.data.contracts), + "data.contracts should be an array", + ); +}); + +console.log("\n\x1b[1mprices\x1b[0m"); + +await test("getTokenPricesBySymbol (chain-agnostic)", async () => { + const result = await clientBySlug.prices.getTokenPricesBySymbol({ + symbols: ["ETH"], + }); + assert(Array.isArray(result.data), "data should be an array"); + assert((result.data ?? []).length > 0, "expected a price for ETH"); +}); + +await test("getTokenPricesByAddress (network in entry)", async () => { + // USDC on mainnet + const result = await clientBySlug.prices.getTokenPricesByAddress({ + addresses: [ + { + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + network: mainnet, + }, + ], + }); + assert(Array.isArray(result.data), "data should be an array"); +}); + +await test("getHistoricalTokenPrices (symbol form)", async () => { + const end = new Date(); + const start = new Date(end.getTime() - 24 * 60 * 60 * 1000); + const result = await clientBySlug.prices.getHistoricalTokenPrices({ + symbol: "ETH", + startTime: start.toISOString(), + endTime: end.toISOString(), + interval: "1h", + }); + assert(Array.isArray(result.data), "data should be an array"); +}); + +console.log("\n\x1b[1mnft — new methods\x1b[0m"); + +// BAYC — stable, well-known contract +const BAYC = "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D"; + +await test("getContractMetadata", async () => { + const result = await clientBySlug.nft.getContractMetadata({ + contractAddress: BAYC, + }); + assert( + typeof result.address === "string", + "contract metadata should include the address", + ); +}); + +await test("getNftMetadata", async () => { + const result = await clientBySlug.nft.getNftMetadata({ + contractAddress: BAYC, + tokenId: "1", + }); + assert(result.tokenId === "1", "should return the requested token"); +}); + +await test("getFloorPrice", async () => { + const result = await clientBySlug.nft.getFloorPrice({ + contractAddress: BAYC, + }); + assert( + typeof result === "object" && result != null, + "should return floor prices", + ); +}); + +await test("isSpamContract", async () => { + const result = await clientBySlug.nft.isSpamContract({ + contractAddress: BAYC, + }); + assert(typeof result.isSpamContract === "boolean", "should classify spam"); +}); + +await test("getNftsForContract with pageSize-style limit", async () => { + const result = await clientBySlug.nft.getNftsForContract({ + contractAddress: BAYC, + limit: 2, + withMetadata: false, + }); + assert(Array.isArray(result.nfts), "nfts should be an array"); + assert((result.nfts ?? []).length <= 2, "should respect limit"); +}); + +console.log("\n\x1b[1mtoken (JSON-RPC)\x1b[0m"); + +// USDC contract on mainnet +const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; + +await test("getTokenBalances", async () => { + const result = await clientBySlug.token.getTokenBalances({ + address: VITALIK, + tokenSpec: [USDC], + }); + assert( + Array.isArray(result.tokenBalances), + "tokenBalances should be an array", + ); +}); + +await test("getTokenMetadata", async () => { + const result = await clientBySlug.token.getTokenMetadata({ + contractAddress: USDC, + }); + assert(result.symbol === "USDC", "should return USDC metadata"); +}); + +await test("getTokenAllowance", async () => { + const result = await clientBySlug.token.getTokenAllowance({ + contract: USDC, + owner: VITALIK, + spender: VITALIK, + }); + assert(typeof result === "string", "allowance should be a decimal string"); +}); + +console.log("\n\x1b[1mpagination companions\x1b[0m"); + +await test("nft.getNftsForOwnerPages walks two pages", async () => { + let pages = 0; + let lastPageKey: string | undefined; + for await (const page of clientBySlug.nft.getNftsForOwnerPages( + { owner: VITALIK, pageSize: 5, withMetadata: false }, + { maxPages: 2 }, + )) { + pages += 1; + if (pages === 1) lastPageKey = page.pageKey; + } + assert(pages === 2, `expected 2 pages, got ${pages}`); + assert(!!lastPageKey, "first page should carry a cursor"); +}); + +await test("transfers.getAssetTransfersPages walks two pages", async () => { + let pages = 0; + for await (const page of clientBySlug.transfers.getAssetTransfersPages( + { fromAddress: VITALIK, category: ["external"], maxCount: "0x2" }, + { maxPages: 2 }, + )) { + pages += 1; + assert(Array.isArray(page.transfers), "each page carries transfers"); + } + assert(pages === 2, `expected 2 pages, got ${pages}`); +}); + // ─── Summary ────────────────────────────────────────────────────────────────── console.log(`\n${"─".repeat(50)}`); diff --git a/packages/data-apis/src/exports.test.ts b/packages/data-apis/src/exports.test.ts new file mode 100644 index 0000000000..1d2c373fbd --- /dev/null +++ b/packages/data-apis/src/exports.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from "vitest"; +import * as packageExports from "./index.js"; + +describe("package export boundary", () => { + it("exposes exactly the intended runtime exports", () => { + expect(Object.keys(packageExports).sort()).toEqual( + [ + "VERSION", + "computeRarity", + "createDataClient", + "dataActions", + "getAssetTransfers", + "getAssetTransfersPages", + "getCollectionMetadata", + "getCollectionsForOwner", + "getCollectionsForOwnerPages", + "getContractMetadata", + "getContractMetadataBatch", + "getContractsForOwner", + "getContractsForOwnerPages", + "getFloorPrice", + "getHistoricalTokenPrices", + "getNftContractsByAddress", + "getNftContractsByAddressPages", + "getNftMetadata", + "getNftMetadataBatch", + "getNftSales", + "getNftSalesPages", + "getNftsByAddress", + "getNftsByAddressPages", + "getNftsForCollection", + "getNftsForCollectionPages", + "getNftsForContract", + "getNftsForContractPages", + "getNftsForOwner", + "getNftsForOwnerPages", + "getOwnersForContract", + "getOwnersForNft", + "getSpamContracts", + "getTokenAllowance", + "getTokenBalances", + "getTokenBalancesByAddress", + "getTokenMetadata", + "getTokenPricesByAddress", + "getTokenPricesBySymbol", + "getTokensByAddress", + "isAirdropNft", + "isHolderOfContract", + "isSpamContract", + "searchContractMetadata", + "summarizeNftAttributes", + ].sort(), + ); + }); + + it("does not re-export the generated internals barrel", () => { + expect(Object.keys(packageExports)).not.toContain("operations"); + expect(Object.keys(packageExports)).not.toContain("paths"); + }); +}); diff --git a/packages/data-apis/src/index.test-d.ts b/packages/data-apis/src/index.test-d.ts new file mode 100644 index 0000000000..eabc9b7009 --- /dev/null +++ b/packages/data-apis/src/index.test-d.ts @@ -0,0 +1,75 @@ +import { expectTypeOf, test } from "vitest"; +import { createClient, http, type Chain, type Client } from "viem"; +import { mainnet } from "viem/chains"; +import { alchemyTransport, type AlchemyTransport } from "@alchemy/common"; +import { + createDataClient, + dataActions, + getNftsForOwner, + type AlchemyDataClient, + type DataActions, + type GetAssetTransfersResult, + type GetNftsForOwnerParams, + type GetNftsForOwnerResult, + type GetTokensByAddressResult, +} from "./index.js"; + +declare const dataClient: AlchemyDataClient; +declare const baseClient: Client; + +test("createDataClient returns a viem client extended with DataActions", () => { + expectTypeOf( + createDataClient({ apiKey: "key", network: "eth-mainnet" }), + ).toEqualTypeOf(); + expectTypeOf(dataClient).toMatchTypeOf(); +}); + +test("decorator path matches the convenience client surface", () => { + const extended = createClient({ + chain: mainnet, + transport: alchemyTransport({ apiKey: "key" }), + }).extend(dataActions); + expectTypeOf(extended.nft.getNftsForOwner).toEqualTypeOf< + AlchemyDataClient["nft"]["getNftsForOwner"] + >(); +}); + +test("dataActions requires an Alchemy transport client", () => { + const plainHttp = createClient({ chain: mainnet, transport: http() }); + // @ts-expect-error plain http transport is not an AlchemyTransport + dataActions(plainHttp); + dataActions(baseClient); +}); + +test("results carry spec-accurate shapes", () => { + expectTypeOf().toMatchTypeOf<{ + tokens?: unknown[]; + pageKey?: string; + }>(); + // the transfers notFound string branch is collapsed away + expectTypeOf().not.toMatchTypeOf(); +}); + +test("network-scoped params accept all three network formats", () => { + expectTypeOf().toMatchTypeOf< + Chain | string | undefined + >(); + // bracketed wire keys are exposed unbracketed + expectTypeOf().toMatchTypeOf<{ + owner: string; + contractAddresses?: string[]; + excludeFilters?: ("SPAM" | "AIRDROPS")[]; + }>(); +}); + +test("standalone actions return the same results as the decorator", () => { + expectTypeOf(getNftsForOwner).returns.toEqualTypeOf< + Promise + >(); +}); + +test("paginated companions yield pages", () => { + expectTypeOf(dataClient.nft.getNftsForOwnerPages).returns.toEqualTypeOf< + AsyncGenerator + >(); +}); diff --git a/packages/data-apis/vitest.config.ts b/packages/data-apis/vitest.config.ts index 160b2a76d1..22e22c093d 100644 --- a/packages/data-apis/vitest.config.ts +++ b/packages/data-apis/vitest.config.ts @@ -1,10 +1,17 @@ import { defineProject } from "vitest/config"; +const typechecking = process.env["TYPECHECK"] === "true"; + export default defineProject({ test: { name: "alchemy/data-apis", globals: true, include: ["src/**/*.test.ts"], + typecheck: { + enabled: typechecking, + only: typechecking, + ignoreSourceErrors: true, + }, // Unit tests stub fetch; no anvil/bundler setup needed (same as common) setupFiles: [], globalSetup: undefined, From 00aca02e1c771f97d63a3b62f38c3a9eab9b07b2 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Fri, 12 Jun 2026 11:29:06 -0400 Subject: [PATCH 14/20] feat(common): viem-free error root + typed JSON-RPC client over the REST engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AlchemyError: dependency-free error root (plain Error) replicating BaseError's message format + walk() cause traversal. AlchemyApiError (and ServerError/FetchError via inheritance) re-parent onto it — the REST/RPC error family no longer touches viem at runtime. Wallet-facing BaseError (viem-based) is untouched; in-repo instanceof audit found one net-identical site (smart-accounts getRevertErrorData). - networkRegistry throws AlchemyError (was the hidden runtime-viem path for data-apis). - httpEngine: the retry/timeout/abort/request-id attempt loop extracted from AlchemyRestClient (behavior-identical; existing tests pass unmodified) and shared with the new AlchemyJsonRpcClient. - AlchemyJsonRpcClient: typed JSON-RPC over the same engine — 429/5xx/ network retried honoring Retry-After; JSON-RPC-level errors never retried, mapped to AlchemyApiError with the rpc code; request ids and retryAfter now exist on the RPC channel (not possible under viem transports); credential redaction on server-echoed text (redactUrlCredentials moves to common). - viem marked optional in peerDependenciesMeta so data-only consumers aren't forced to install it; rest/types' Prettify inlined (last type-level viem import in the REST surface). - 12 new tests (error-format parity, hierarchy, walk; rpc envelope/retry/ no-retry/redaction/abort). Co-Authored-By: Claude Fable 5 --- packages/common/package.json | 7 +- packages/common/src/errors/AlchemyApiError.ts | 8 +- packages/common/src/errors/AlchemyError.ts | 95 +++++++++++ packages/common/src/index.ts | 10 +- .../common/src/networks/networkRegistry.ts | 10 +- packages/common/src/rest/httpEngine.ts | 117 +++++++++++++ packages/common/src/rest/jsonRpcClient.ts | 136 +++++++++++++++ packages/common/src/rest/restClient.ts | 120 +++---------- packages/common/src/rest/types.ts | 3 +- packages/common/src/utils/redact.ts | 14 ++ .../common/tests/errors/alchemyError.test.ts | 67 ++++++++ .../common/tests/rest/jsonRpcClient.test.ts | 160 ++++++++++++++++++ 12 files changed, 642 insertions(+), 105 deletions(-) create mode 100644 packages/common/src/errors/AlchemyError.ts create mode 100644 packages/common/src/rest/httpEngine.ts create mode 100644 packages/common/src/rest/jsonRpcClient.ts create mode 100644 packages/common/src/utils/redact.ts create mode 100644 packages/common/tests/errors/alchemyError.test.ts create mode 100644 packages/common/tests/rest/jsonRpcClient.test.ts diff --git a/packages/common/package.json b/packages/common/package.json index ee64095aac..f1de6edc8b 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -70,5 +70,10 @@ "bugs": { "url": "https://github.com/alchemyplatform/aa-sdk/issues" }, - "homepage": "https://github.com/alchemyplatform/aa-sdk#readme" + "homepage": "https://github.com/alchemyplatform/aa-sdk#readme", + "peerDependenciesMeta": { + "viem": { + "optional": true + } + } } diff --git a/packages/common/src/errors/AlchemyApiError.ts b/packages/common/src/errors/AlchemyApiError.ts index 006ca134e1..96fb77c312 100644 --- a/packages/common/src/errors/AlchemyApiError.ts +++ b/packages/common/src/errors/AlchemyApiError.ts @@ -1,4 +1,4 @@ -import { BaseError } from "./BaseError.js"; +import { AlchemyError } from "./AlchemyError.js"; /** Normalized failure metadata shared across REST and JSON-RPC channels. */ export type AlchemyApiErrorDetails = { @@ -26,7 +26,7 @@ export type AlchemyApiErrorDetails = { * } * ``` */ -export class AlchemyApiError extends BaseError { +export class AlchemyApiError extends AlchemyError { override name = "AlchemyApiError"; /** HTTP status code, when the failure was an HTTP response. */ @@ -42,7 +42,7 @@ export class AlchemyApiError extends BaseError { * Creates a normalized API error. * * @param {string} shortMessage The headline error message - * @param {AlchemyApiErrorDetails & { cause?: Error; details?: string; metaMessages?: string[] }} [args] Failure metadata plus BaseError options + * @param {AlchemyApiErrorDetails & { cause?: Error; details?: string; metaMessages?: string[] }} [args] Failure metadata plus AlchemyError options */ constructor( shortMessage: string, @@ -57,7 +57,7 @@ export class AlchemyApiError extends BaseError { ...(baseArgs.metaMessages ?? []), ...(requestId ? [`Request ID: ${requestId}`] : []), ]; - // BaseError takes cause XOR details; forward whichever was provided. + // AlchemyError takes cause XOR details; forward whichever was provided. super( shortMessage, baseArgs.cause diff --git a/packages/common/src/errors/AlchemyError.ts b/packages/common/src/errors/AlchemyError.ts new file mode 100644 index 0000000000..f74e078cfd --- /dev/null +++ b/packages/common/src/errors/AlchemyError.ts @@ -0,0 +1,95 @@ +import { VERSION } from "../version.js"; + +type AlchemyErrorParameters = { + docsPath?: string; + docsSlug?: string; + metaMessages?: string[]; +} & ( + | { + cause?: never; + details?: string; + } + | { + cause: Error; + details?: never; + } +); + +/** + * Dependency-free error root for SDK surfaces that must not pull in viem at + * runtime (the Data APIs core and the shared REST/JSON-RPC runtime). Message + * shape matches {@link BaseError} (short message, meta messages, docs link, + * details, version line), and `walk()` provides viem-compatible cause-chain + * traversal — but the class extends plain `Error`. + * + * Wallet-facing errors keep extending {@link BaseError} (viem-based); this + * root exists so `AlchemyApiError` and friends stay viem-free. + */ +export class AlchemyError extends Error { + override name = "AlchemyError"; + readonly version = VERSION; + readonly shortMessage: string; + readonly details?: string; + readonly docsPath?: string; + readonly metaMessages?: string[]; + + /** + * Creates an error with the SDK's standard message formatting. + * + * @param {string} shortMessage The headline error message + * @param {AlchemyErrorParameters} args Docs pointers, meta messages, and cause XOR details + */ + constructor(shortMessage: string, args: AlchemyErrorParameters = {}) { + const details = + args.cause instanceof AlchemyError + ? args.cause.details + : args.cause?.message + ? args.cause.message + : args.details; + const docsPath = + args.cause instanceof AlchemyError + ? args.cause.docsPath || args.docsPath + : args.docsPath; + + super( + [ + shortMessage || "An error occurred.", + "", + ...(args.metaMessages ? [...args.metaMessages, ""] : []), + ...(docsPath + ? [ + `Docs: https://www.alchemy.com/docs/wallets${docsPath}${ + args.docsSlug ? `#${args.docsSlug}` : "" + }`, + ] + : []), + ...(details ? [`Details: ${details}`] : []), + `Version: ${VERSION}`, + ].join("\n"), + args.cause ? { cause: args.cause } : undefined, + ); + + this.shortMessage = shortMessage; + this.details = details; + this.docsPath = docsPath; + this.metaMessages = args.metaMessages; + } + + /** + * Walks the cause chain: with no predicate, returns the deepest error; + * with a predicate, returns the first matching error or null (viem + * BaseError-compatible semantics). + * + * @param {Function} [fn] Optional predicate over each error in the chain + * @returns {Error | null} The deepest error, the first match, or null + */ + walk(fn?: (err: unknown) => boolean): Error | null { + // eslint-disable-next-line @typescript-eslint/no-this-alias + let err: Error = this; + while (true) { + if (fn?.(err)) return err; + if (!(err.cause instanceof Error)) return fn ? null : err; + err = err.cause; + } + } +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 93516801a0..6593cc105e 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -2,10 +2,16 @@ export type * from "./transport/alchemy.js"; export { alchemyTransport, isAlchemyTransport } from "./transport/alchemy.js"; -// http -- exported for @alchemy/data-apis (REST channel); hardening tracked in the data SDK plan +// http — shared REST + JSON-RPC runtime (viem-free); used by @alchemy/data-apis export type * from "./rest/restClient.js"; export type * from "./rest/types.js"; export { AlchemyRestClient } from "./rest/restClient.js"; +export type { + AlchemyJsonRpcClientParams, + JsonRpcRequestFn, + JsonRpcSchema, +} from "./rest/jsonRpcClient.js"; +export { AlchemyJsonRpcClient } from "./rest/jsonRpcClient.js"; // chain registry utilities export { @@ -26,6 +32,7 @@ export { resolveNetwork } from "./networks/networkRegistry.js"; // utils export type * from "./utils/types.js"; export { composeSignals, sleep } from "./utils/signals.js"; +export { redactUrlCredentials } from "./utils/redact.js"; export { assertNever } from "./utils/assertNever.js"; export { raise } from "./utils/raise.js"; export { bigIntMultiply, bigIntMax } from "./utils/bigint.js"; @@ -41,6 +48,7 @@ export { // errors export { BaseError } from "./errors/BaseError.js"; +export { AlchemyError } from "./errors/AlchemyError.js"; export type { AlchemyApiErrorDetails } from "./errors/AlchemyApiError.js"; export { AlchemyApiError } from "./errors/AlchemyApiError.js"; export { ChainNotFoundError } from "./errors/ChainNotFoundError.js"; diff --git a/packages/common/src/networks/networkRegistry.ts b/packages/common/src/networks/networkRegistry.ts index 1c5f7b887c..283970ef82 100644 --- a/packages/common/src/networks/networkRegistry.ts +++ b/packages/common/src/networks/networkRegistry.ts @@ -1,5 +1,5 @@ import type { Chain } from "viem"; -import { BaseError } from "../errors/BaseError.js"; +import { AlchemyError } from "../errors/AlchemyError.js"; import { ALCHEMY_RPC_MAPPING } from "../transport/chainRegistry.js"; /** @@ -94,7 +94,7 @@ export function resolveNetwork(input: NetworkInput): ResolvedNetwork { if (typeof input === "object") { const slug = slugByChainId.get(input.id); if (!slug) { - throw new BaseError( + throw new AlchemyError( `Chain ${input.id} (${input.name}) is not in the Alchemy network registry. ` + `Pass an Alchemy network slug (e.g. "eth-mainnet") instead.`, ); @@ -106,7 +106,7 @@ export function resolveNetwork(input: NetworkInput): ResolvedNetwork { const chainId = Number(input.slice("eip155:".length)); const slug = slugByChainId.get(chainId); if (!Number.isInteger(chainId) || !slug) { - throw new BaseError( + throw new AlchemyError( `CAIP-2 identifier "${input}" is not in the Alchemy network registry.`, ); } @@ -116,7 +116,7 @@ export function resolveNetwork(input: NetworkInput): ResolvedNetwork { if (input.startsWith("solana:")) { const slug = SOLANA_SLUG_BY_CAIP2[input]; if (!slug) { - throw new BaseError( + throw new AlchemyError( `CAIP-2 identifier "${input}" is not a known Solana network.`, ); } @@ -124,7 +124,7 @@ export function resolveNetwork(input: NetworkInput): ResolvedNetwork { } if (!SLUG_PATTERN.test(input)) { - throw new BaseError( + throw new AlchemyError( `"${input}" is not a valid Alchemy network slug (expected lowercase letters, digits, and hyphens).`, ); } diff --git a/packages/common/src/rest/httpEngine.ts b/packages/common/src/rest/httpEngine.ts new file mode 100644 index 0000000000..81dfc8d0d6 --- /dev/null +++ b/packages/common/src/rest/httpEngine.ts @@ -0,0 +1,117 @@ +import { FetchError } from "../errors/FetchError.js"; +import { composeSignals, sleep } from "../utils/signals.js"; + +// Internal request engine shared by AlchemyRestClient and +// AlchemyJsonRpcClient. Not exported from the package. + +/** Parameters for one logical request (spanning retries). */ +export type HttpSendParams = { + url: string; + method: string; + /** Final headers, including auth and X-Alchemy-Client-Request-Id. */ + headers: Headers; + body?: string; + /** Label used in FetchError messages — a route or RPC method, never a URL. */ + errorLabel: string; + requestId: string; + signal?: AbortSignal; + retryCount: number; + retryDelay: number; + timeout: number; +}; + +/** + * Parses a Retry-After response header into milliseconds (integer seconds or + * HTTP-date forms). + * + * @param {Response} response The HTTP response + * @returns {number | undefined} Milliseconds to wait, or undefined when absent/unparseable + */ +export function parseRetryAfter(response: Response): number | undefined { + const header = response.headers.get("Retry-After"); + if (!header) return undefined; + const seconds = Number(header); + if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000); + const date = Date.parse(header); + if (!Number.isNaN(date)) return Math.max(0, date - Date.now()); + return undefined; +} + +/** + * Best-effort extraction of an error code from a JSON error body. + * + * @param {string} bodyText The raw response body + * @returns {number | string | undefined} The error code, when present + */ +export function parseErrorCode(bodyText: string): number | string | undefined { + try { + const parsed = JSON.parse(bodyText); + const code = parsed?.error?.code ?? parsed?.code; + return typeof code === "number" || typeof code === "string" + ? code + : undefined; + } catch { + return undefined; + } +} + +/** + * Sends a request with the SDK's standard retry policy: 429/5xx/network + * failures retried with exponential backoff (honoring Retry-After), a + * per-attempt timeout merged with the caller's signal, and caller-initiated + * aborts rethrown immediately without retrying. Resolves with the final + * Response — ok, or non-ok once the policy is exhausted or the status is + * non-retryable — leaving error mapping to the typed frontends. Throws + * FetchError when network/timeout failures exhaust the retries. + * + * @param {HttpSendParams} params The request and retry configuration + * @returns {Promise<{ response: Response; retryAfter?: number }>} The final response and any Retry-After hint + */ +export async function sendWithRetry({ + url, + method, + headers, + body, + errorLabel, + requestId, + signal: callerSignal, + retryCount, + retryDelay, + timeout, +}: HttpSendParams): Promise<{ response: Response; retryAfter?: number }> { + for (let attempt = 0; ; attempt++) { + // Per-attempt timeout; the caller's signal spans all attempts. + const signal = composeSignals(callerSignal, AbortSignal.timeout(timeout)); + + let response: Response; + try { + response = await fetch(url, { method, body, headers, signal }); + } catch (error) { + // A caller-initiated abort propagates as-is, immediately. + if (callerSignal?.aborted) throw callerSignal.reason; + // Timeout or network failure: retryable. + if (attempt < retryCount) { + await sleep((1 << attempt) * retryDelay, callerSignal); + continue; + } + throw new FetchError( + errorLabel, + method, + error instanceof Error ? error : undefined, + { requestId }, + ); + } + + if (response.ok) { + return { response }; + } + + const retryAfter = parseRetryAfter(response); + const retryable = response.status === 429 || response.status >= 500; + if (retryable && attempt < retryCount) { + await sleep(retryAfter ?? (1 << attempt) * retryDelay, callerSignal); + continue; + } + return { response, retryAfter }; + } +} diff --git a/packages/common/src/rest/jsonRpcClient.ts b/packages/common/src/rest/jsonRpcClient.ts new file mode 100644 index 0000000000..03217f39b6 --- /dev/null +++ b/packages/common/src/rest/jsonRpcClient.ts @@ -0,0 +1,136 @@ +import { AlchemyApiError } from "../errors/AlchemyApiError.js"; +import { ServerError } from "../errors/ServerError.js"; +import { withAlchemyHeaders } from "../utils/headers.js"; +import { redactUrlCredentials } from "../utils/redact.js"; +import { parseErrorCode, sendWithRetry } from "./httpEngine.js"; +import { + DEFAULT_RETRY_COUNT, + DEFAULT_RETRY_DELAY_MS, + DEFAULT_TIMEOUT_MS, + type AlchemyRestClientParams, +} from "./restClient.js"; +import type { RestRequestOptions } from "./types.js"; + +/** + * A typed JSON-RPC schema: a tuple of method entries. Structurally compatible + * with viem's RpcSchema shape, but defined here so consumers need no viem + * dependency. + */ +export type JsonRpcSchema = readonly { + Method: string; + Parameters?: unknown; + ReturnType: unknown; +}[]; + +export type JsonRpcRequestFn = < + method extends Schema[number]["Method"], +>( + args: { + method: method; + params: Extract["Parameters"]; + }, + options?: RestRequestOptions, +) => Promise["ReturnType"]>; + +/** Parameters for creating an AlchemyJsonRpcClient (URL is required: JSON-RPC is always endpoint-scoped). */ +export type AlchemyJsonRpcClientParams = AlchemyRestClientParams & { + url: string; +}; + +/** + * A typed JSON-RPC client over the same HTTP engine as + * {@link AlchemyRestClient}: bounded retries on 429/5xx/network failures + * (honoring Retry-After), per-attempt timeouts, abort support, and a + * per-request X-Alchemy-Client-Request-Id surfaced on thrown errors. + * JSON-RPC-level errors (an `error` object on HTTP 200) are deterministic and + * are never retried; they throw {@link AlchemyApiError} carrying the RPC + * error code. + */ +export class AlchemyJsonRpcClient { + private readonly url: string; + private readonly headers: Headers; + private readonly retryCount: number; + private readonly retryDelay: number; + private readonly timeout: number; + private nextId = 1; + + /** + * Creates a new instance of AlchemyJsonRpcClient. + * + * @param {AlchemyJsonRpcClientParams} params - Endpoint URL plus auth, headers, and retry/timeout defaults. + */ + constructor({ + apiKey, + jwt, + url, + headers, + retryCount, + retryDelay, + timeout, + }: AlchemyJsonRpcClientParams) { + this.url = url; + this.headers = new Headers(withAlchemyHeaders({ headers, apiKey, jwt })); + this.headers.set("Content-Type", "application/json"); + this.retryCount = retryCount ?? DEFAULT_RETRY_COUNT; + this.retryDelay = retryDelay ?? DEFAULT_RETRY_DELAY_MS; + this.timeout = timeout ?? DEFAULT_TIMEOUT_MS; + } + + /** + * Sends a JSON-RPC request and returns its result. + * + * @param {object} args - The request to send + * @param {string} args.method - The JSON-RPC method name + * @param {unknown} args.params - The positional params for the method + * @param {RestRequestOptions} [options] - Per-request signal and retry/timeout overrides + * @returns {Promise} The JSON-RPC result + */ + public request: JsonRpcRequestFn = async ( + { method, params }, + options, + ) => { + const requestId = crypto.randomUUID(); + const headers = new Headers(this.headers); + headers.set("X-Alchemy-Client-Request-Id", requestId); + + const { response, retryAfter } = await sendWithRetry({ + url: this.url, + method: "POST", + headers, + body: JSON.stringify({ + jsonrpc: "2.0", + id: this.nextId++, + method, + params, + }), + errorLabel: method, + requestId, + signal: options?.signal, + retryCount: options?.retryCount ?? this.retryCount, + retryDelay: options?.retryDelay ?? this.retryDelay, + timeout: options?.timeout ?? this.timeout, + }); + + if (!response.ok) { + const bodyText = await response.text(); + throw new ServerError( + redactUrlCredentials(bodyText), + response.status, + new Error(response.statusText), + { requestId, retryAfter, code: parseErrorCode(bodyText) }, + ); + } + + const body = await response.json(); + if (body?.error != null) { + throw new AlchemyApiError( + redactUrlCredentials(body.error.message ?? "JSON-RPC error"), + { code: body.error.code, requestId }, + ); + } + if (!("result" in (body ?? {}))) { + throw new AlchemyApiError("Malformed JSON-RPC response", { requestId }); + } + return body.result; + }; +} diff --git a/packages/common/src/rest/restClient.ts b/packages/common/src/rest/restClient.ts index b8fc3bd4bf..d55fea3cc2 100644 --- a/packages/common/src/rest/restClient.ts +++ b/packages/common/src/rest/restClient.ts @@ -1,14 +1,13 @@ -import { FetchError } from "../errors/FetchError.js"; import { ServerError } from "../errors/ServerError.js"; import { withAlchemyHeaders } from "../utils/headers.js"; -import { composeSignals, sleep } from "../utils/signals.js"; +import { parseErrorCode, sendWithRetry } from "./httpEngine.js"; import type { QueryParams, RestRequestFn, RestRequestSchema } from "./types.js"; const ALCHEMY_API_URL = "https://api.g.alchemy.com"; -const DEFAULT_RETRY_COUNT = 3; -const DEFAULT_RETRY_DELAY_MS = 150; -const DEFAULT_TIMEOUT_MS = 10_000; +export const DEFAULT_RETRY_COUNT = 3; +export const DEFAULT_RETRY_DELAY_MS = 150; +export const DEFAULT_TIMEOUT_MS = 10_000; /** * Parameters for creating an AlchemyRestClient instance. @@ -56,41 +55,6 @@ function serializeQuery(query: QueryParams | undefined): string { return serialized ? `?${serialized}` : ""; } -/** - * Parses a Retry-After response header into milliseconds (integer seconds or - * HTTP-date forms). - * - * @param {Response} response The HTTP response - * @returns {number | undefined} Milliseconds to wait, or undefined when absent/unparseable - */ -function parseRetryAfter(response: Response): number | undefined { - const header = response.headers.get("Retry-After"); - if (!header) return undefined; - const seconds = Number(header); - if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000); - const date = Date.parse(header); - if (!Number.isNaN(date)) return Math.max(0, date - Date.now()); - return undefined; -} - -/** - * Best-effort extraction of an error code from a JSON error body. - * - * @param {string} bodyText The raw response body - * @returns {number | string | undefined} The error code, when present - */ -function parseErrorCode(bodyText: string): number | string | undefined { - try { - const parsed = JSON.parse(bodyText); - const code = parsed?.error?.code ?? parsed?.code; - return typeof code === "number" || typeof code === "string" - ? code - : undefined; - } catch { - return undefined; - } -} - /** * A client for making requests to Alchemy's non-JSON-RPC endpoints, with * typed routes/bodies/queries (via a RestRequestSchema), bounded retries with @@ -137,10 +101,6 @@ export class AlchemyRestClient { */ public request: RestRequestFn = async (params) => { const requestId = crypto.randomUUID(); - const retryCount = params.retryCount ?? this.retryCount; - const retryDelay = params.retryDelay ?? this.retryDelay; - const timeout = params.timeout ?? this.timeout; - const headers = new Headers(this.headers); headers.set("X-Alchemy-Client-Request-Id", requestId); @@ -148,55 +108,29 @@ export class AlchemyRestClient { params.query as QueryParams | undefined, )}`; - for (let attempt = 0; ; attempt++) { - // Per-attempt timeout; the caller's signal spans all attempts. - const signal = composeSignals( - params.signal, - AbortSignal.timeout(timeout), - ); - - let response: Response; - try { - response = await fetch(url, { - method: params.method, - body: params.body ? JSON.stringify(params.body) : undefined, - headers, - signal, - }); - } catch (error) { - // A caller-initiated abort propagates as-is, immediately. - if (params.signal?.aborted) throw params.signal.reason; - // Timeout or network failure: retryable. - if (attempt < retryCount) { - await sleep((1 << attempt) * retryDelay, params.signal); - continue; - } - throw new FetchError( - params.route, - params.method, - error instanceof Error ? error : undefined, - { requestId }, - ); - } - - if (response.ok) { - return response.json(); - } - - const retryAfter = parseRetryAfter(response); - const retryable = response.status === 429 || response.status >= 500; - if (retryable && attempt < retryCount) { - await sleep(retryAfter ?? (1 << attempt) * retryDelay, params.signal); - continue; - } - - const bodyText = await response.text(); - throw new ServerError( - bodyText, - response.status, - new Error(response.statusText), - { requestId, retryAfter, code: parseErrorCode(bodyText) }, - ); + const { response, retryAfter } = await sendWithRetry({ + url, + method: params.method, + headers, + body: params.body ? JSON.stringify(params.body) : undefined, + errorLabel: params.route, + requestId, + signal: params.signal, + retryCount: params.retryCount ?? this.retryCount, + retryDelay: params.retryDelay ?? this.retryDelay, + timeout: params.timeout ?? this.timeout, + }); + + if (response.ok) { + return response.json(); } + + const bodyText = await response.text(); + throw new ServerError( + bodyText, + response.status, + new Error(response.statusText), + { requestId, retryAfter, code: parseErrorCode(bodyText) }, + ); }; } diff --git a/packages/common/src/rest/types.ts b/packages/common/src/rest/types.ts index 5520e0d36a..6761d70c1d 100644 --- a/packages/common/src/rest/types.ts +++ b/packages/common/src/rest/types.ts @@ -1,4 +1,5 @@ -import type { Prettify } from "viem"; +/** Flattens an intersection for readable hover types (viem-equivalent, local to stay viem-free). */ +type Prettify = { [K in keyof T]: T[K] } & {}; /** Values the query serializer accepts (null/undefined entries are skipped). */ export type QueryValue = string | number | boolean | null | undefined; diff --git a/packages/common/src/utils/redact.ts b/packages/common/src/utils/redact.ts new file mode 100644 index 0000000000..39a6f48e0c --- /dev/null +++ b/packages/common/src/utils/redact.ts @@ -0,0 +1,14 @@ +/** + * Redacts credentials that can appear in URLs: keys embedded in "/v2/" + * RPC paths (when a caller configured a key-bearing url) and apiKey query + * params. The header-auth paths never put keys in URLs; this protects + * configured-url escape hatches and any server-echoed text. + * + * @param {string} text Any error text that may embed a URL + * @returns {string} The text with credentials replaced by "[redacted]" + */ +export function redactUrlCredentials(text: string): string { + return text + .replace(/(\/v2\/)[A-Za-z0-9_-]+/g, "$1[redacted]") + .replace(/([?&]apiKey=)[^&\s]+/gi, "$1[redacted]"); +} diff --git a/packages/common/tests/errors/alchemyError.test.ts b/packages/common/tests/errors/alchemyError.test.ts new file mode 100644 index 0000000000..e1c7a5e402 --- /dev/null +++ b/packages/common/tests/errors/alchemyError.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from "vitest"; +import { AlchemyError } from "../../src/errors/AlchemyError.js"; +import { AlchemyApiError } from "../../src/errors/AlchemyApiError.js"; +import { ServerError } from "../../src/errors/ServerError.js"; +import { FetchError } from "../../src/errors/FetchError.js"; +import { BaseError } from "../../src/errors/BaseError.js"; +import { VERSION } from "../../src/version.js"; + +describe("AlchemyError", () => { + it("formats messages like BaseError (short message, meta, details, version)", () => { + const error = new AlchemyError("Something failed", { + metaMessages: ["Request ID: abc"], + details: "boom", + }); + expect(error.message).toBe( + [ + "Something failed", + "", + "Request ID: abc", + "", + "Details: boom", + `Version: ${VERSION}`, + ].join("\n"), + ); + expect(error.shortMessage).toBe("Something failed"); + }); + + it("derives details and docs path from an AlchemyError cause", () => { + const cause = new AlchemyError("inner", { + details: "inner-details", + docsPath: "/path", + }); + const outer = new AlchemyError("outer", { cause }); + expect(outer.details).toBe("inner-details"); + expect(outer.docsPath).toBe("/path"); + expect(outer.message).toContain("Docs: https://www.alchemy.com"); + }); + + it("walk() returns the deepest cause without a predicate and first match with one", () => { + const root = new Error("root"); + const mid = new AlchemyError("mid", { cause: root }); + const top = new AlchemyError("top", { cause: mid }); + expect(top.walk()).toBe(root); + expect( + top.walk((e) => e instanceof AlchemyError && e.shortMessage === "mid"), + ).toBe(mid); + expect(top.walk(() => false)).toBeNull(); + }); + + it("the API error family is viem-free: not instanceof BaseError", () => { + const server = new ServerError("body", 500); + const fetchErr = new FetchError("route", "GET"); + for (const error of [server, fetchErr]) { + expect(error).toBeInstanceOf(AlchemyApiError); + expect(error).toBeInstanceOf(AlchemyError); + expect(error).toBeInstanceOf(Error); + // deliberately NOT instanceof the viem-extending BaseError + expect(error).not.toBeInstanceOf(BaseError); + } + }); + + it("wallet-facing BaseError is untouched (still its own hierarchy)", () => { + const base = new BaseError("wallet error"); + expect(base).toBeInstanceOf(Error); + expect(base).not.toBeInstanceOf(AlchemyError); + }); +}); diff --git a/packages/common/tests/rest/jsonRpcClient.test.ts b/packages/common/tests/rest/jsonRpcClient.test.ts new file mode 100644 index 0000000000..b8579a0a50 --- /dev/null +++ b/packages/common/tests/rest/jsonRpcClient.test.ts @@ -0,0 +1,160 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { AlchemyJsonRpcClient } from "../../src/rest/jsonRpcClient.js"; +import { AlchemyApiError } from "../../src/errors/AlchemyApiError.js"; +import { ServerError } from "../../src/errors/ServerError.js"; + +type TestSchema = readonly [ + { + Method: "alchemy_getThings"; + Parameters: [{ owner: string }]; + ReturnType: { things: string[] }; + }, +]; + +const fetchMock = vi.fn(); + +const rpcResponse = (body: unknown, init?: ResponseInit) => + new Response(JSON.stringify(body), { + status: 200, + headers: { "Content-Type": "application/json" }, + ...init, + }); + +const makeClient = () => + new AlchemyJsonRpcClient({ + apiKey: "test-key", + url: "https://eth-mainnet.example.test/v2", + retryDelay: 1, + }); + +beforeEach(() => { + vi.stubGlobal("fetch", fetchMock); + fetchMock.mockReset(); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe("AlchemyJsonRpcClient", () => { + it("posts the JSON-RPC envelope with monotonic ids and auth headers", async () => { + fetchMock.mockImplementation(async () => + rpcResponse({ jsonrpc: "2.0", id: 1, result: { things: [] } }), + ); + const client = makeClient(); + await client.request({ + method: "alchemy_getThings", + params: [{ owner: "0xa" }], + }); + await client.request({ + method: "alchemy_getThings", + params: [{ owner: "0xb" }], + }); + + const body1 = JSON.parse(fetchMock.mock.calls[0]![1].body); + const body2 = JSON.parse(fetchMock.mock.calls[1]![1].body); + expect(body1).toEqual({ + jsonrpc: "2.0", + id: 1, + method: "alchemy_getThings", + params: [{ owner: "0xa" }], + }); + expect(body2.id).toBe(2); + + const headers = fetchMock.mock.calls[0]![1].headers as Headers; + expect(headers.get("Authorization")).toBe("Bearer test-key"); + expect(headers.get("Content-Type")).toBe("application/json"); + expect(headers.get("X-Alchemy-Client-Request-Id")).toMatch( + /^[0-9a-f-]{36}$/, + ); + }); + + it("retries 429 honoring Retry-After, then succeeds", async () => { + fetchMock + .mockImplementationOnce( + async () => + new Response("{}", { status: 429, headers: { "Retry-After": "0" } }), + ) + .mockImplementationOnce(async () => + rpcResponse({ jsonrpc: "2.0", id: 1, result: { things: ["x"] } }), + ); + const result = await makeClient().request({ + method: "alchemy_getThings", + params: [{ owner: "0xa" }], + }); + expect(result).toEqual({ things: ["x"] }); + expect(fetchMock).toHaveBeenCalledTimes(2); + }); + + it("does NOT retry JSON-RPC-level errors and maps them to AlchemyApiError", async () => { + fetchMock.mockImplementation(async () => + rpcResponse({ + jsonrpc: "2.0", + id: 1, + error: { code: -32602, message: "invalid params" }, + }), + ); + const error = await makeClient() + .request({ method: "alchemy_getThings", params: [{ owner: "0xa" }] }) + .catch((e) => e); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(error).toBeInstanceOf(AlchemyApiError); + expect(error.code).toBe(-32602); + expect(error.requestId).toMatch(/^[0-9a-f-]{36}$/); + }); + + it("redacts credentials echoed in rpc error messages", async () => { + fetchMock.mockImplementation(async () => + rpcResponse({ + jsonrpc: "2.0", + id: 1, + error: { code: -32000, message: "bad key at /v2/supersecret123" }, + }), + ); + const error = await makeClient() + .request({ method: "alchemy_getThings", params: [{ owner: "0xa" }] }) + .catch((e) => e); + expect(error.message).not.toContain("supersecret123"); + expect(error.message).toContain("/v2/[redacted]"); + }); + + it("maps non-ok HTTP responses to ServerError with status and requestId", async () => { + fetchMock.mockImplementation(async () => + rpcResponse({ message: "nope" }, { status: 403 }), + ); + const error = await makeClient() + .request({ method: "alchemy_getThings", params: [{ owner: "0xa" }] }) + .catch((e) => e); + expect(error).toBeInstanceOf(ServerError); + expect(error.status).toBe(403); + expect(error.requestId).toMatch(/^[0-9a-f-]{36}$/); + }); + + it("throws on malformed responses missing result and error", async () => { + fetchMock.mockImplementation(async () => + rpcResponse({ jsonrpc: "2.0", id: 1 }), + ); + await expect( + makeClient().request({ + method: "alchemy_getThings", + params: [{ owner: "0xa" }], + }), + ).rejects.toThrow(/Malformed JSON-RPC response/); + }); + + it("propagates caller aborts immediately", async () => { + const controller = new AbortController(); + fetchMock.mockImplementation(async (_url, init: RequestInit) => { + controller.abort(new Error("user-cancelled")); + throw (init.signal as AbortSignal).reason; + }); + const error = await makeClient() + .request( + { method: "alchemy_getThings", params: [{ owner: "0xa" }] }, + { signal: controller.signal }, + ) + .catch((e) => e); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(error.message).toBe("user-cancelled"); + }); +}); From 928ac4dda8dc99db5d6cc1bf11450f29a95fcc45 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Fri, 12 Jun 2026 11:35:51 -0400 Subject: [PATCH 15/20] =?UTF-8?q?feat(data-apis)!:=20dependency-free=20cor?= =?UTF-8?q?e=20=E2=80=94=20viem=20inverted=20out=20of=20the=20foundation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the leadership-approved direction (Data SDK Foundation doc): - createDataClient returns a plain container ({config, network}) — no viem client underneath. Chainless construction now works (the eager-transport ChainNotFoundError dies): portfolio/prices need no network at all; single-network methods error clearly at call time. - Network inputs narrow to strings (Alchemy slugs + CAIP-2); viem Chain objects move to the adapter layer (bridge: eip155:${chain.id}). - The 7 JSON-RPC methods run on AlchemyJsonRpcClient over the shared HTTP engine — gaining request ids, Retry-After handling, and AlchemyApiError normalization at the source (wrapRpcError and the last viem value imports are deleted). - The viem decorator is parked at src/viem/dataActions.ts (unexported, internally tested — unit + live smoke): it becomes the /viem subpath when adapter demand warrants, per the doc. viem drops to a devDependency; the published package has no viem requirement. - Tests updated throughout; new coverage for chainless construction, the rpc envelope/request-id, and the parked adapter. 247 unit tests, 8 type tests, 30/30 live smoke tests pass on the inverted core. BREAKING CHANGE: AlchemyDataClient is no longer a viem client; dataActions is no longer exported (returns post-v1 as the /viem adapter); network params accept slug/CAIP-2 strings only. Co-Authored-By: Claude Fable 5 --- packages/common/tests/utils/redact.test.ts | 18 +++ packages/data-apis/package.json | 6 +- packages/data-apis/scripts/smoke-test.ts | 26 +++-- .../data-apis/src/actions/nft/nft.test.ts | 2 +- .../src/actions/prices/prices.test.ts | 3 +- .../src/actions/token/getTokenAllowance.ts | 12 +- .../src/actions/token/getTokenBalances.ts | 12 +- .../src/actions/token/getTokenMetadata.ts | 12 +- .../actions/transfers/getAssetTransfers.ts | 23 ++-- packages/data-apis/src/client.ts | 74 ++++-------- packages/data-apis/src/dataClient.test.ts | 59 +++++++--- packages/data-apis/src/decorator.ts | 23 +--- packages/data-apis/src/exports.test.ts | 1 - packages/data-apis/src/index.test-d.ts | 56 ++++------ packages/data-apis/src/index.ts | 4 +- .../data-apis/src/internal/clientHelpers.ts | 105 ++++++++---------- .../data-apis/src/internal/errors.test.ts | 67 ----------- packages/data-apis/src/internal/errors.ts | 46 -------- packages/data-apis/src/internal/paginate.ts | 4 +- packages/data-apis/src/types.ts | 40 +++---- .../data-apis/src/viem/dataActions.test-d.ts | 25 +++++ .../data-apis/src/viem/dataActions.test.ts | 62 +++++++++++ packages/data-apis/src/viem/dataActions.ts | 52 +++++++++ 23 files changed, 358 insertions(+), 374 deletions(-) create mode 100644 packages/common/tests/utils/redact.test.ts delete mode 100644 packages/data-apis/src/internal/errors.test.ts delete mode 100644 packages/data-apis/src/internal/errors.ts create mode 100644 packages/data-apis/src/viem/dataActions.test-d.ts create mode 100644 packages/data-apis/src/viem/dataActions.test.ts create mode 100644 packages/data-apis/src/viem/dataActions.ts diff --git a/packages/common/tests/utils/redact.test.ts b/packages/common/tests/utils/redact.test.ts new file mode 100644 index 0000000000..e03cb8a8df --- /dev/null +++ b/packages/common/tests/utils/redact.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { redactUrlCredentials } from "../../src/utils/redact.js"; + +describe("redactUrlCredentials", () => { + it("redacts /v2/ path segments and apiKey query params", () => { + expect( + redactUrlCredentials( + "https://eth-mainnet.g.alchemy.com/v2/supersecret123 and https://x.test/?apiKey=supersecret123&y=1", + ), + ).toBe( + "https://eth-mainnet.g.alchemy.com/v2/[redacted] and https://x.test/?apiKey=[redacted]&y=1", + ); + }); + + it("leaves credential-free text untouched", () => { + expect(redactUrlCredentials("plain message")).toBe("plain message"); + }); +}); diff --git a/packages/data-apis/package.json b/packages/data-apis/package.json index c6c3882b14..0b372c9808 100644 --- a/packages/data-apis/package.json +++ b/packages/data-apis/package.json @@ -1,10 +1,9 @@ { "name": "@alchemy/data-apis", "version": "5.0.3-alpha.0", - "description": "Alchemy Data APIs SDK: Portfolio, Prices, NFT, Token, and Transfers, as viem-style actions", + "description": "Alchemy Data APIs SDK: Portfolio, Prices, NFT, Token, and Transfers \u2014 typed, dependency-free actions", "keywords": [ "alchemy", - "viem", "nft", "token", "prices", @@ -57,9 +56,6 @@ "dependencies": { "@alchemy/common": "workspace:*" }, - "peerDependencies": { - "viem": "^2.45.0" - }, "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/", diff --git a/packages/data-apis/scripts/smoke-test.ts b/packages/data-apis/scripts/smoke-test.ts index 9314dc6911..49f6125cb4 100644 --- a/packages/data-apis/scripts/smoke-test.ts +++ b/packages/data-apis/scripts/smoke-test.ts @@ -5,18 +5,17 @@ * ALCHEMY_API_KEY= pnpm --filter @alchemy/data-apis smoke-test * * What it covers: - * - createDataClient with slug, viem Chain, and CAIP-2 network inputs + * - createDataClient with slug and CAIP-2 network inputs * - portfolio.getTokensByAddress (REST, multi-network, all three input formats) * - nft.getNftsForOwner (REST, per-network, per-request override) * - transfers.getAssetTransfers (JSON-RPC, per-request network override) */ import { mainnet } from "viem/chains"; -import { BaseError } from "@alchemy/common"; -import { createDataClient } from "../src/client.js"; -import { dataActions } from "../src/decorator.js"; -import { alchemyTransport } from "@alchemy/common"; +import { AlchemyError, alchemyTransport } from "@alchemy/common"; import { createClient } from "viem"; +import { createDataClient } from "../src/client.js"; +import { dataActions } from "../src/viem/dataActions.js"; const API_KEY = process.env.ALCHEMY_API_KEY; if (!API_KEY) { @@ -44,7 +43,7 @@ async function test(name: string, fn: () => Promise) { } function assert(condition: boolean, message: string) { - if (!condition) throw new BaseError(`Assertion failed: ${message}`); + if (!condition) throw new AlchemyError(`Assertion failed: ${message}`); } // ─── Clients ────────────────────────────────────────────────────────────────── @@ -54,16 +53,17 @@ const clientBySlug = createDataClient({ apiKey: API_KEY, network: "eth-mainnet", }); +// CAIP-2 derived from a viem chain — the core's bridge for chain objects const clientByChain = createDataClient({ apiKey: API_KEY, - network: mainnet, + network: `eip155:${mainnet.id}`, }); const clientByCaip2 = createDataClient({ apiKey: API_KEY, network: "eip155:1", }); -// Decorator path: bring-your-own viem client +// Parked viem adapter: bring-your-own viem client (post-v1 /viem surface) const rawViemClient = createClient({ chain: mainnet, transport: alchemyTransport({ apiKey: API_KEY }), @@ -86,13 +86,13 @@ await test("single network (slug)", async () => { assert((result.data.tokens ?? []).length > 0, "expected at least one token"); }); -await test("multi-network: viem Chain + slug + CAIP-2 in one call", async () => { +await test("multi-network: slugs + CAIP-2 in one call", async () => { const result = await clientBySlug.portfolio.getTokensByAddress({ addresses: [ { address: VITALIK, networks: [ - mainnet, // viem Chain + "eth-mainnet", // Alchemy slug "base-mainnet", // Alchemy slug "eip155:137", // CAIP-2 (Polygon) ], @@ -226,7 +226,9 @@ await test("per-request network override (CAIP-2)", async () => { assert(Array.isArray(result.transfers), "transfers should be an array"); }); -console.log("\n\x1b[1mdecorator path (raw viem client + dataActions)\x1b[0m"); +console.log( + "\n\x1b[1mparked viem adapter (raw viem client + dataActions)\x1b[0m", +); await test("client.extend(dataActions) works identically", async () => { const result = await rawViemClient.transfers.getAssetTransfers({ @@ -296,7 +298,7 @@ await test("getTokenPricesByAddress (network in entry)", async () => { addresses: [ { address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - network: mainnet, + network: "eth-mainnet", }, ], }); diff --git a/packages/data-apis/src/actions/nft/nft.test.ts b/packages/data-apis/src/actions/nft/nft.test.ts index 4d0d2f8842..bc32dc32f8 100644 --- a/packages/data-apis/src/actions/nft/nft.test.ts +++ b/packages/data-apis/src/actions/nft/nft.test.ts @@ -81,7 +81,7 @@ describe("nft namespace routing", () => { ], [ "getSpamContracts", - (c) => c.nft.getSpamContracts(), + (c) => c.nft.getSpamContracts({}), `${NFT_BASE}/getSpamContracts`, ], [ diff --git a/packages/data-apis/src/actions/prices/prices.test.ts b/packages/data-apis/src/actions/prices/prices.test.ts index 3b6a82a4eb..c5e519ef41 100644 --- a/packages/data-apis/src/actions/prices/prices.test.ts +++ b/packages/data-apis/src/actions/prices/prices.test.ts @@ -1,5 +1,4 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { mainnet } from "viem/chains"; import { createDataClient } from "../../client.js"; const fetchMock = vi.fn(); @@ -41,7 +40,7 @@ describe("prices namespace", () => { it("by-address resolves each entry's network to a slug in the body", async () => { await makeClient().prices.getTokenPricesByAddress({ addresses: [ - { address: "0xa", network: mainnet }, + { address: "0xa", network: "eth-mainnet" }, { address: "0xb", network: "eip155:8453" }, { address: "0xc", network: "polygon-mainnet" }, ], diff --git a/packages/data-apis/src/actions/token/getTokenAllowance.ts b/packages/data-apis/src/actions/token/getTokenAllowance.ts index d4b311e30b..a3d5d4d93e 100644 --- a/packages/data-apis/src/actions/token/getTokenAllowance.ts +++ b/packages/data-apis/src/actions/token/getTokenAllowance.ts @@ -1,8 +1,4 @@ -import { - getRpcRequest, - type DataClient, -} from "../../internal/clientHelpers.js"; -import { wrapRpcError } from "../../internal/errors.js"; +import { getRpcClient, type DataClient } from "../../internal/clientHelpers.js"; import type { GetTokenAllowanceParams, GetTokenAllowanceResult, @@ -23,9 +19,9 @@ export async function getTokenAllowance( params: GetTokenAllowanceParams, ): Promise { const { network, ...allowanceRequest } = params; - const request = getRpcRequest(client, network); - return request({ + const rpc = getRpcClient(client, network); + return rpc.request({ method: "alchemy_getTokenAllowance", params: [allowanceRequest], - }).catch(wrapRpcError); + }); } diff --git a/packages/data-apis/src/actions/token/getTokenBalances.ts b/packages/data-apis/src/actions/token/getTokenBalances.ts index 1c49066af3..b57b15f3a2 100644 --- a/packages/data-apis/src/actions/token/getTokenBalances.ts +++ b/packages/data-apis/src/actions/token/getTokenBalances.ts @@ -1,8 +1,4 @@ -import { - getRpcRequest, - type DataClient, -} from "../../internal/clientHelpers.js"; -import { wrapRpcError } from "../../internal/errors.js"; +import { getRpcClient, type DataClient } from "../../internal/clientHelpers.js"; import type { TokenRpcSchema } from "../../generated/rpc/token.js"; import type { GetTokenBalancesParams, @@ -24,7 +20,7 @@ export async function getTokenBalances( params: GetTokenBalancesParams, ): Promise { const { network, address, tokenSpec, options } = params; - const request = getRpcRequest(client, network); + const rpc = getRpcClient(client, network); // Positional JSON-RPC params; trailing optionals are omitted when unset. // "erc20" is the spec's default tokenSpec, filled in only when paging @@ -36,8 +32,8 @@ export async function getTokenBalances( ? [address, tokenSpec] : [address]; - return request({ + return rpc.request({ method: "alchemy_getTokenBalances", params: rpcParams, - }).catch(wrapRpcError); + }); } diff --git a/packages/data-apis/src/actions/token/getTokenMetadata.ts b/packages/data-apis/src/actions/token/getTokenMetadata.ts index 5b04484e07..3f955d40df 100644 --- a/packages/data-apis/src/actions/token/getTokenMetadata.ts +++ b/packages/data-apis/src/actions/token/getTokenMetadata.ts @@ -1,8 +1,4 @@ -import { - getRpcRequest, - type DataClient, -} from "../../internal/clientHelpers.js"; -import { wrapRpcError } from "../../internal/errors.js"; +import { getRpcClient, type DataClient } from "../../internal/clientHelpers.js"; import type { GetTokenMetadataParams, GetTokenMetadataResult, @@ -23,9 +19,9 @@ export async function getTokenMetadata( params: GetTokenMetadataParams, ): Promise { const { network, contractAddress } = params; - const request = getRpcRequest(client, network); - return request({ + const rpc = getRpcClient(client, network); + return rpc.request({ method: "alchemy_getTokenMetadata", params: [contractAddress], - }).catch(wrapRpcError); + }); } diff --git a/packages/data-apis/src/actions/transfers/getAssetTransfers.ts b/packages/data-apis/src/actions/transfers/getAssetTransfers.ts index 3a4aa02e38..2d2c204188 100644 --- a/packages/data-apis/src/actions/transfers/getAssetTransfers.ts +++ b/packages/data-apis/src/actions/transfers/getAssetTransfers.ts @@ -1,8 +1,4 @@ -import { - getRpcRequest, - type DataClient, -} from "../../internal/clientHelpers.js"; -import { wrapRpcError } from "../../internal/errors.js"; +import { getRpcClient, type DataClient } from "../../internal/clientHelpers.js"; import type { GetAssetTransfersParams, GetAssetTransfersResult, @@ -10,14 +6,11 @@ import type { /** * Fetches historical asset transfers (external, internal, token) for the - * resolved network via the `alchemy_getAssetTransfers` JSON-RPC method. + * resolved network via the `alchemy_getAssetTransfers` JSON-RPC method. The + * network is resolved per request: an explicit `network` param wins, + * otherwise the client's configured default applies. * - * Without a `network` override this is a plain viem action over the client's - * Alchemy transport. With an override, a transport instance is derived from - * the client's transport config and pointed at the override network's RPC URL - * — the same mechanism the transport's tracing support uses. - * - * @param {DataClient} client A client configured with an Alchemy transport + * @param {DataClient} client A Data API client * @param {GetAssetTransfersParams} params Transfer filters and optional network override * @returns {Promise} The matching transfers and pagination cursor */ @@ -26,9 +19,9 @@ export async function getAssetTransfers( params: GetAssetTransfersParams, ): Promise { const { network, ...rpcParams } = params; - const request = getRpcRequest(client, network); - return request({ + const rpc = getRpcClient(client, network); + return rpc.request({ method: "alchemy_getAssetTransfers", params: [rpcParams], - }).catch(wrapRpcError); + }); } diff --git a/packages/data-apis/src/client.ts b/packages/data-apis/src/client.ts index 0af048d2f5..173b54f89e 100644 --- a/packages/data-apis/src/client.ts +++ b/packages/data-apis/src/client.ts @@ -1,35 +1,29 @@ import { - alchemyTransport, resolveNetwork, - type AlchemyTransportConfig, - type NetworkInput, + type AlchemyNetwork, + type AlchemyRestClientParams, } from "@alchemy/common"; -import { createClient, type Chain, type Client } from "viem"; -import { getRpcUrl } from "./internal/endpoints.js"; import { dataActions, type DataActions } from "./decorator.js"; -import type { AlchemyTransport } from "@alchemy/common"; +import type { DataClient } from "./internal/clientHelpers.js"; -export type AlchemyDataClientOptions = Pick< - AlchemyTransportConfig, - "apiKey" | "jwt" | "url" | "fetchOptions" -> & { +export type AlchemyDataClientOptions = AlchemyRestClientParams & { /** * Default network for network-scoped calls (NFT, Token, Transfers). - * Accepts a viem Chain, an Alchemy slug ("eth-mainnet"), or CAIP-2 - * ("eip155:1"). Multi-network calls (Portfolio) take explicit networks - * per request. + * Accepts an Alchemy slug ("eth-mainnet") or a CAIP-2 id ("eip155:1", + * "solana:mainnet"). Optional: multi-network calls (Portfolio, Prices) + * take networks per request, and single-network calls accept a + * per-request `network` override. Holding a viem chain? The bridge is + * `eip155:${chain.id}`. */ - network?: NetworkInput; + network?: AlchemyNetwork; }; -export type AlchemyDataClient = Client & - DataActions; +export type AlchemyDataClient = DataClient & DataActions; /** - * Creates a Data API client. This is a convenience wrapper over - * `createClient` + `alchemyTransport` + the {@link dataActions} decorator — - * developers already holding a viem client with an Alchemy transport can use - * `client.extend(dataActions)` instead and get the identical behavior. + * Creates a Data API client: a plain, dependency-free container holding the + * connection config (apiKey/jwt/proxy url, retry and timeout defaults) and an + * optional default network, with the Data API actions attached by namespace. * * @example * ```ts @@ -37,43 +31,23 @@ export type AlchemyDataClient = Client & * * const data = createDataClient({ * apiKey: process.env.ALCHEMY_API_KEY, - * network: "eth-mainnet", // or `mainnet` from viem/chains, or "eip155:1" + * network: "eth-mainnet", // or "eip155:1"; optional for portfolio/prices * }); * * const nfts = await data.nft.getNftsForOwner({ owner: "0x..." }); * ``` * - * @param {AlchemyDataClientOptions} options Auth (apiKey/jwt/proxy url) and default network - * @returns {AlchemyDataClient} A viem client extended with the Data API actions + * @param {AlchemyDataClientOptions} options Auth (apiKey/jwt/proxy url), retry/timeout defaults, and default network + * @returns {AlchemyDataClient} The Data API client */ export function createDataClient( options: AlchemyDataClientOptions, ): AlchemyDataClient { - const { network, ...transportConfig } = options; - - // A viem Chain default flows through the transport's registry resolution - // untouched. Slug/CAIP-2 defaults are carried on a minimal chain definition - // (custom.alchemyNetwork) and the RPC URL is resolved up front — chain ID 0 - // mirrors how wallet-apis models Solana chains. - const chain: Chain | undefined = (() => { - if (network == null) return undefined; - if (typeof network === "object") return network; - const resolved = resolveNetwork(network); - return { - id: resolved.chainId ?? 0, - name: resolved.slug, - nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, - rpcUrls: { default: { http: [] } }, - custom: { alchemyNetwork: resolved.slug }, - }; - })(); - - const transport = alchemyTransport({ - ...transportConfig, - ...(chain && chain.custom?.alchemyNetwork && !transportConfig.url - ? { url: getRpcUrl(chain.custom.alchemyNetwork as string) } - : {}), - }); - - return createClient({ chain, transport }).extend(dataActions); + const { network, ...config } = options; + const core: DataClient = { + config, + // Eager resolution keeps fail-fast behavior for malformed network inputs. + network: network != null ? resolveNetwork(network) : undefined, + }; + return Object.assign(core, dataActions(core)); } diff --git a/packages/data-apis/src/dataClient.test.ts b/packages/data-apis/src/dataClient.test.ts index 786656fbe2..fe0ed777b0 100644 --- a/packages/data-apis/src/dataClient.test.ts +++ b/packages/data-apis/src/dataClient.test.ts @@ -1,9 +1,5 @@ -import { alchemyTransport } from "@alchemy/common"; -import { createClient } from "viem"; -import { mainnet, base } from "viem/chains"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createDataClient } from "./client.js"; -import { dataActions } from "./decorator.js"; const fetchMock = vi.fn(); @@ -34,7 +30,10 @@ describe("createDataClient", () => { await data.portfolio.getTokensByAddress({ addresses: [ // all three network input formats in one request - { address: "0xabc", networks: [mainnet, "base-mainnet", "eip155:10"] }, + { + address: "0xabc", + networks: ["eth-mainnet", "base-mainnet", "eip155:10"], + }, { address: "0xdef", networks: ["solana-mainnet"] }, ], }); @@ -70,7 +69,7 @@ describe("createDataClient", () => { "https://eth-mainnet.g.alchemy.com/nft/v3/getNFTsForOwner?owner=0xabc", ); - await data.nft.getNftsForOwner({ owner: "0xabc", network: base }); + await data.nft.getNftsForOwner({ owner: "0xabc", network: "eip155:8453" }); expect(String(fetchMock.mock.calls[1]![0])).toBe( "https://base-mainnet.g.alchemy.com/nft/v3/getNFTsForOwner?owner=0xabc", ); @@ -105,22 +104,50 @@ describe("createDataClient", () => { "https://arb-mainnet.g.alchemy.com/v2", ); }); -}); -describe("dataActions decorator", () => { - it("behaves identically on a vanilla viem client with an Alchemy transport", async () => { + it("sends a well-formed JSON-RPC envelope with a request id header", async () => { fetchMock.mockImplementation(async () => - jsonResponse({ ownedNfts: [], totalCount: 0 }), + jsonResponse({ jsonrpc: "2.0", id: 1, result: { transfers: [] } }), + ); + const data = createDataClient({ + apiKey: "test-key", + network: "eth-mainnet", + }); + await data.transfers.getAssetTransfers({ category: ["erc20"] }); + + const rpcBody = JSON.parse(fetchMock.mock.calls[0]![1].body); + expect(rpcBody.jsonrpc).toBe("2.0"); + expect(typeof rpcBody.id).toBe("number"); + const headers = fetchMock.mock.calls[0]![1].headers as Headers; + expect(headers.get("X-Alchemy-Client-Request-Id")).toMatch( + /^[0-9a-f-]{36}$/, ); + }); - const client = createClient({ - chain: mainnet, - transport: alchemyTransport({ apiKey: "test-key" }), - }).extend(dataActions); + it("constructs without a network: portfolio works, single-network methods error clearly", async () => { + fetchMock.mockImplementation(async () => + jsonResponse({ data: { tokens: [] } }), + ); + const data = createDataClient({ apiKey: "test-key" }); - await client.nft.getNftsForOwner({ owner: "0xabc" }); + await data.portfolio.getTokensByAddress({ + addresses: [{ address: "0xabc", networks: ["eth-mainnet"] }], + }); expect(String(fetchMock.mock.calls[0]![0])).toBe( - "https://eth-mainnet.g.alchemy.com/nft/v3/getNFTsForOwner?owner=0xabc", + "https://api.g.alchemy.com/data/v1/assets/tokens/by-address", + ); + + await expect(data.nft.getNftsForOwner({ owner: "0xabc" })).rejects.toThrow( + /No network available/, ); + + fetchMock.mockImplementation(async () => + jsonResponse({ ownedNfts: [], totalCount: 0 }), + ); + const result = await data.nft.getNftsForOwner({ + owner: "0xabc", + network: "eth-mainnet", + }); + expect(result.totalCount).toBe(0); }); }); diff --git a/packages/data-apis/src/decorator.ts b/packages/data-apis/src/decorator.ts index a1799532dc..746d9877c5 100644 --- a/packages/data-apis/src/decorator.ts +++ b/packages/data-apis/src/decorator.ts @@ -212,25 +212,12 @@ export type DataActions = { }; /** - * A viem client decorator that attaches the Data API actions, grouped by - * namespace, to any client configured with an Alchemy transport. + * Builds the namespaced Data API actions over a core data client. Used by + * {@link createDataClient}; ecosystem adapters (e.g. the parked viem adapter + * in src/viem/) construct a core client and delegate here, so every surface + * shares one implementation. * - * @example - * ```ts - * import { createClient } from "viem"; - * import { mainnet } from "viem/chains"; - * import { alchemyTransport } from "@alchemy/common"; - * import { dataActions } from "@alchemy/data-apis"; - * - * const client = createClient({ - * chain: mainnet, - * transport: alchemyTransport({ apiKey: "..." }), - * }).extend(dataActions); - * - * const nfts = await client.nft.getNftsForOwner({ owner: "0x..." }); - * ``` - * - * @param {DataClient} client The client to decorate + * @param {DataClient} client The core client (config + default network) * @returns {DataActions} The namespaced Data API actions */ export function dataActions(client: DataClient): DataActions { diff --git a/packages/data-apis/src/exports.test.ts b/packages/data-apis/src/exports.test.ts index 1d2c373fbd..5052e4d0a0 100644 --- a/packages/data-apis/src/exports.test.ts +++ b/packages/data-apis/src/exports.test.ts @@ -8,7 +8,6 @@ describe("package export boundary", () => { "VERSION", "computeRarity", "createDataClient", - "dataActions", "getAssetTransfers", "getAssetTransfersPages", "getCollectionMetadata", diff --git a/packages/data-apis/src/index.test-d.ts b/packages/data-apis/src/index.test-d.ts index eabc9b7009..e6360765bd 100644 --- a/packages/data-apis/src/index.test-d.ts +++ b/packages/data-apis/src/index.test-d.ts @@ -1,10 +1,6 @@ import { expectTypeOf, test } from "vitest"; -import { createClient, http, type Chain, type Client } from "viem"; -import { mainnet } from "viem/chains"; -import { alchemyTransport, type AlchemyTransport } from "@alchemy/common"; import { createDataClient, - dataActions, getNftsForOwner, type AlchemyDataClient, type DataActions, @@ -15,30 +11,38 @@ import { } from "./index.js"; declare const dataClient: AlchemyDataClient; -declare const baseClient: Client; -test("createDataClient returns a viem client extended with DataActions", () => { +test("createDataClient returns a plain container with the namespaced actions", () => { expectTypeOf( createDataClient({ apiKey: "key", network: "eth-mainnet" }), ).toEqualTypeOf(); expectTypeOf(dataClient).toMatchTypeOf(); + // the container is plain config + network — no chain-library client shape + expectTypeOf(dataClient.config).toMatchTypeOf<{ + apiKey?: string; + jwt?: string; + url?: string; + }>(); }); -test("decorator path matches the convenience client surface", () => { - const extended = createClient({ - chain: mainnet, - transport: alchemyTransport({ apiKey: "key" }), - }).extend(dataActions); - expectTypeOf(extended.nft.getNftsForOwner).toEqualTypeOf< - AlchemyDataClient["nft"]["getNftsForOwner"] - >(); +test("network is optional at construction and string-only", () => { + expectTypeOf( + createDataClient({ apiKey: "key" }), + ).toEqualTypeOf(); + // @ts-expect-error viem Chain objects are not accepted by the core + createDataClient({ apiKey: "key", network: { id: 1 } }); }); -test("dataActions requires an Alchemy transport client", () => { - const plainHttp = createClient({ chain: mainnet, transport: http() }); - // @ts-expect-error plain http transport is not an AlchemyTransport - dataActions(plainHttp); - dataActions(baseClient); +test("network-scoped params accept slug and CAIP-2 strings only", () => { + expectTypeOf().toMatchTypeOf< + string | undefined + >(); + // bracketed wire keys are exposed unbracketed + expectTypeOf().toMatchTypeOf<{ + owner: string; + contractAddresses?: string[]; + excludeFilters?: ("SPAM" | "AIRDROPS")[]; + }>(); }); test("results carry spec-accurate shapes", () => { @@ -50,19 +54,7 @@ test("results carry spec-accurate shapes", () => { expectTypeOf().not.toMatchTypeOf(); }); -test("network-scoped params accept all three network formats", () => { - expectTypeOf().toMatchTypeOf< - Chain | string | undefined - >(); - // bracketed wire keys are exposed unbracketed - expectTypeOf().toMatchTypeOf<{ - owner: string; - contractAddresses?: string[]; - excludeFilters?: ("SPAM" | "AIRDROPS")[]; - }>(); -}); - -test("standalone actions return the same results as the decorator", () => { +test("standalone actions return the same results as the client methods", () => { expectTypeOf(getNftsForOwner).returns.toEqualTypeOf< Promise >(); diff --git a/packages/data-apis/src/index.ts b/packages/data-apis/src/index.ts index d815e9895b..d8f5b5c879 100644 --- a/packages/data-apis/src/index.ts +++ b/packages/data-apis/src/index.ts @@ -2,9 +2,9 @@ export type { AlchemyDataClient, AlchemyDataClientOptions } from "./client.js"; export { createDataClient } from "./client.js"; -// decorator +// actions surface type (the viem adapter ships as /viem post-v1; the +// dataActions decorator itself is internal to the core) export type { DataActions } from "./decorator.js"; -export { dataActions } from "./decorator.js"; // per-request options export type { RequestOptions } from "./internal/clientHelpers.js"; diff --git a/packages/data-apis/src/internal/clientHelpers.ts b/packages/data-apis/src/internal/clientHelpers.ts index c90abde4fd..74e28cc83d 100644 --- a/packages/data-apis/src/internal/clientHelpers.ts +++ b/packages/data-apis/src/internal/clientHelpers.ts @@ -1,20 +1,25 @@ import { + AlchemyError, + AlchemyJsonRpcClient, AlchemyRestClient, - alchemyTransport, resolveNetwork, - type AlchemyTransport, - type AlchemyTransportConfig, - type NetworkInput, + type AlchemyNetwork, + type AlchemyRestClientParams, type ResolvedNetwork, type RestRequestSchema, } from "@alchemy/common"; -import { BaseError } from "@alchemy/common"; -import type { Chain, Client, EIP1193RequestFn } from "viem"; import { getRpcUrl } from "./endpoints.js"; import type { DataRpcSchema } from "../schema/rpc.js"; -/** The minimal client shape data actions operate on. */ -export type DataClient = Client; +/** + * The minimal client shape data actions operate on: validated connection + * config plus an optional pre-resolved default network. A plain object — no + * chain-library client underneath. + */ +export type DataClient = { + config: AlchemyRestClientParams; + network?: ResolvedNetwork; +}; /** Per-request options accepted by data actions. */ export type RequestOptions = { @@ -22,55 +27,36 @@ export type RequestOptions = { signal?: AbortSignal; }; -/** - * Reads the AlchemyTransportConfig back off an instantiated client transport. - * The transport attaches its creation config to the transport value precisely - * to support deriving new instances (tracing relies on the same mechanism). - * - * @param {DataClient} client The client to read from - * @returns {AlchemyTransportConfig} The transport's creation config - */ -export function getTransportConfig(client: DataClient): AlchemyTransportConfig { - return client.transport.config as AlchemyTransportConfig; -} - /** * Resolves the network for a request: explicit per-request input wins, - * otherwise the client's chain (set at client creation). The - * `custom.alchemyNetwork` field carries slug-configured defaults (e.g. Solana - * or escape-hatch networks that have no registry chain ID). + * otherwise the client's default network (set at client creation). * - * @param {DataClient} client The client whose chain provides the default - * @param {NetworkInput} [override] Per-request network override + * @param {DataClient} client The client whose default network applies + * @param {AlchemyNetwork} [override] Per-request network override (slug or CAIP-2) * @returns {ResolvedNetwork} The resolved network */ export function resolveRequestNetwork( client: DataClient, - override?: NetworkInput, + override?: AlchemyNetwork, ): ResolvedNetwork { if (override != null) { return resolveNetwork(override); } - const chain = client.chain; - const customSlug = (chain?.custom as { alchemyNetwork?: string } | undefined) - ?.alchemyNetwork; - if (customSlug) { - return resolveNetwork(customSlug); + if (client.network) { + return client.network; } - if (chain) { - return resolveNetwork(chain); - } - throw new BaseError( - "No network available: pass `network` on the request or configure the client with a network/chain.", + throw new AlchemyError( + "No network available: pass `network` on the request or configure the client with a network.", ); } /** - * Builds a typed REST client for a service base URL, reusing the auth - * (apiKey/JWT) and headers from the viem client's Alchemy transport so REST - * and JSON-RPC requests share one connection config. + * Builds a typed REST client for a service base URL, reusing the client's + * auth and retry/timeout configuration so REST and JSON-RPC requests share + * one connection config. The client-level `url` override applies to the + * JSON-RPC channel only; REST URLs are service-scoped by construction. * - * @param {DataClient} client The client whose transport supplies auth + * @param {DataClient} client The client whose config supplies auth * @param {string} url The service base URL * @returns {AlchemyRestClient} A typed REST client */ @@ -78,31 +64,28 @@ export function getRestClient( client: DataClient, url: string, ): AlchemyRestClient { - const { apiKey, jwt } = getTransportConfig(client); - return new AlchemyRestClient({ apiKey, jwt, url }); + const { url: _rpcUrlOverride, ...config } = client.config; + return new AlchemyRestClient({ ...config, url }); } /** - * Resolves the JSON-RPC request function for an action: the client's own - * transport when no override is given, otherwise a transport instance derived - * from the client's transport config and pointed at the override network's - * RPC URL (the same mechanism the transport's tracing support uses). + * Builds a typed JSON-RPC client for the resolved network: the client's + * configured `url` (proxy escape hatch) wins, otherwise the network-scoped + * Alchemy RPC URL. Auth and retry/timeout config come from the client. * - * @param {DataClient} client The client whose transport (and config) is used - * @param {NetworkInput} [network] Optional per-request network override - * @returns {EIP1193RequestFn} A typed JSON-RPC request function + * @param {DataClient} client The client whose config supplies auth + * @param {AlchemyNetwork} [network] Optional per-request network override + * @returns {AlchemyJsonRpcClient} A typed JSON-RPC client */ -export function getRpcRequest( +export function getRpcClient( client: DataClient, - network?: NetworkInput, -): EIP1193RequestFn { - if (!network) { - return client.request as EIP1193RequestFn; - } - const { slug } = resolveRequestNetwork(client, network); - const derived = alchemyTransport({ - ...getTransportConfig(client), - url: getRpcUrl(slug), - })({ retryCount: 0 }); - return derived.request as EIP1193RequestFn; + network?: AlchemyNetwork, +): AlchemyJsonRpcClient { + const { url, ...config } = client.config; + const resolvedUrl = + url ?? getRpcUrl(resolveRequestNetwork(client, network).slug); + return new AlchemyJsonRpcClient({ + ...config, + url: resolvedUrl, + }); } diff --git a/packages/data-apis/src/internal/errors.test.ts b/packages/data-apis/src/internal/errors.test.ts deleted file mode 100644 index 90624df8b8..0000000000 --- a/packages/data-apis/src/internal/errors.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { AlchemyApiError } from "@alchemy/common"; -import { HttpRequestError, RpcRequestError } from "viem"; -import { describe, expect, it } from "vitest"; -import { redactUrlCredentials, wrapRpcError } from "./errors.js"; - -const SECRET = "supersecretapikey123"; - -describe("redactUrlCredentials", () => { - it("redacts /v2/ path segments and apiKey query params", () => { - expect( - redactUrlCredentials( - `https://eth-mainnet.g.alchemy.com/v2/${SECRET} and https://x.test/?apiKey=${SECRET}&y=1`, - ), - ).toBe( - "https://eth-mainnet.g.alchemy.com/v2/[redacted] and https://x.test/?apiKey=[redacted]&y=1", - ); - }); -}); - -describe("wrapRpcError", () => { - it("maps RpcRequestError to AlchemyApiError with the rpc code, redacted", () => { - const viemError = new RpcRequestError({ - body: { method: "alchemy_getAssetTransfers" }, - url: `https://eth-mainnet.g.alchemy.com/v2/${SECRET}`, - error: { code: -32602, message: "invalid params" }, - }); - const wrapped = (() => { - try { - wrapRpcError(viemError); - } catch (e) { - return e as AlchemyApiError; - } - throw new Error("did not throw"); - })(); - expect(wrapped).toBeInstanceOf(AlchemyApiError); - expect(wrapped.code).toBe(-32602); - expect(wrapped.message).not.toContain(SECRET); - }); - - it("maps HttpRequestError to AlchemyApiError with the status, redacted", () => { - const viemError = new HttpRequestError({ - url: `https://eth-mainnet.g.alchemy.com/v2/${SECRET}`, - status: 503, - details: `service unavailable at /v2/${SECRET}`, - }); - const wrapped = (() => { - try { - wrapRpcError(viemError); - } catch (e) { - return e as AlchemyApiError; - } - throw new Error("did not throw"); - })(); - expect(wrapped).toBeInstanceOf(AlchemyApiError); - expect(wrapped.status).toBe(503); - expect(wrapped.message).not.toContain(SECRET); - expect(JSON.stringify(wrapped)).not.toContain(SECRET); - }); - - it("passes AlchemyApiError and unknown errors through untouched", () => { - const original = new AlchemyApiError("already normalized", { status: 400 }); - expect(() => wrapRpcError(original)).toThrow(original); - - const unknown = new Error("not a viem error"); - expect(() => wrapRpcError(unknown)).toThrow(unknown); - }); -}); diff --git a/packages/data-apis/src/internal/errors.ts b/packages/data-apis/src/internal/errors.ts deleted file mode 100644 index 81f27a052e..0000000000 --- a/packages/data-apis/src/internal/errors.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { AlchemyApiError } from "@alchemy/common"; -import { HttpRequestError, RpcRequestError } from "viem"; - -/** - * Redacts credentials that can appear in URLs: keys embedded in "/v2/" - * RPC paths (when a caller configured a key-bearing url) and apiKey query - * params. The header-auth paths never put keys in URLs; this protects the - * configured-url escape hatch. - * - * @param {string} text Any error text that may embed a URL - * @returns {string} The text with credentials replaced by "[redacted]" - */ -export function redactUrlCredentials(text: string): string { - return text - .replace(/(\/v2\/)[A-Za-z0-9_-]+/g, "$1[redacted]") - .replace(/([?&]apiKey=)[^&\s]+/gi, "$1[redacted]"); -} - -/** - * Normalizes viem JSON-RPC failures into the {@link AlchemyApiError} family - * (the REST channel is normalized inside AlchemyRestClient), so consumers - * handle one error shape across both channels. URL-bearing viem errors are - * carried as redacted details rather than as a cause, so credential-bearing - * URLs never leak through error chains. - * - * @param {unknown} error The error thrown by `client.request` - * @returns {never} Always throws - */ -export function wrapRpcError(error: unknown): never { - if (error instanceof AlchemyApiError) { - throw error; - } - if (error instanceof RpcRequestError) { - throw new AlchemyApiError(redactUrlCredentials(error.shortMessage), { - code: error.code, - details: redactUrlCredentials(error.details), - }); - } - if (error instanceof HttpRequestError) { - throw new AlchemyApiError(redactUrlCredentials(error.shortMessage), { - status: error.status, - details: redactUrlCredentials(error.details ?? ""), - }); - } - throw error; -} diff --git a/packages/data-apis/src/internal/paginate.ts b/packages/data-apis/src/internal/paginate.ts index 58f8c3e243..80f018fc0c 100644 --- a/packages/data-apis/src/internal/paginate.ts +++ b/packages/data-apis/src/internal/paginate.ts @@ -1,4 +1,4 @@ -import { BaseError } from "@alchemy/common"; +import { AlchemyError } from "@alchemy/common"; /** Options accepted by the `*Pages` async-iterator actions. */ export type PaginateOptions = { @@ -45,7 +45,7 @@ export async function* paginate({ const next = nextCursor(page); if (next == null || next === "") return; if (seen.has(next)) { - throw new BaseError( + throw new AlchemyError( `Pagination cursor "${next}" was repeated by the server; stopping to avoid an infinite loop.`, ); } diff --git a/packages/data-apis/src/types.ts b/packages/data-apis/src/types.ts index c50caef5e8..aa4e88c9f4 100644 --- a/packages/data-apis/src/types.ts +++ b/packages/data-apis/src/types.ts @@ -1,4 +1,4 @@ -import type { NetworkInput } from "@alchemy/common"; +import type { AlchemyNetwork } from "@alchemy/common"; import type { ComputeRarityQuery, ComputeRarityResponse, @@ -89,12 +89,12 @@ type Unbracket = { /** Query params of a network-scoped method, plus the SDK's network override. */ type NetworkScoped = Unbracket & { /** Overrides the client-level network for this request. */ - network?: NetworkInput; + network?: AlchemyNetwork; }; -/** Distributes over a union, replacing wire `network: string` with NetworkInput. */ -type WithNetworkInput = T extends { network: string } - ? Omit & { network: NetworkInput } +/** Distributes over a union, replacing wire `network: string` with AlchemyNetwork. */ +type WithAlchemyNetwork = T extends { network: string } + ? Omit & { network: AlchemyNetwork } : T; // ─── Portfolio (REST, global, multi-network) ──────────────────────────────── @@ -102,14 +102,14 @@ type WithNetworkInput = T extends { network: string } /** An address paired with the networks to query it on. */ export interface PortfolioAddressEntry { address: string; - /** Networks to query; accepts viem Chains, Alchemy slugs, or CAIP-2 ids. */ - networks: NetworkInput[]; + /** Networks to query; accepts Alchemy slugs or CAIP-2 ids. */ + networks: AlchemyNetwork[]; } /** * Replaces each wire-format address entry's `networks` (slug strings — an * enum in some specs, deliberately widened here to support the SDK's - * escape-hatch slugs) with the SDK's three-format NetworkInput, preserving + * escape-hatch slugs) with the SDK's three-format AlchemyNetwork, preserving * any other per-entry fields the operation defines (e.g. per-address * include/exclude filters). */ @@ -119,8 +119,8 @@ type PortfolioParams = Omit< > & { addresses: Array< Omit & { - /** Networks to query; accepts viem Chains, Alchemy slugs, or CAIP-2 ids. */ - networks: NetworkInput[]; + /** Networks to query; accepts Alchemy slugs or CAIP-2 ids. */ + networks: AlchemyNetwork[]; } >; }; @@ -151,8 +151,8 @@ export type GetTokenPricesBySymbolResult = GetTokenPricesBySymbolResponse; /** A token address paired with the network it lives on. */ export interface PriceAddressEntry { address: string; - /** Accepts a viem Chain, an Alchemy slug, or a CAIP-2 id. */ - network: NetworkInput; + /** Accepts an Alchemy slug or a CAIP-2 id. */ + network: AlchemyNetwork; } export type GetTokenPricesByAddressParams = Omit< @@ -164,7 +164,7 @@ export type GetTokenPricesByAddressParams = Omit< export type GetTokenPricesByAddressResult = GetTokenPricesByAddressResponse; export type GetHistoricalTokenPricesParams = - WithNetworkInput; + WithAlchemyNetwork; export type GetHistoricalTokenPricesResult = GetHistoricalTokenPricesResponse; // ─── NFT (REST, network-scoped) ────────────────────────────────────────────── @@ -184,7 +184,7 @@ export type GetNftMetadataResult = GetNftMetadataResponse; export type GetNftMetadataBatchParams = GetNftMetadataBatchBody & { /** Overrides the client-level network for this request. */ - network?: NetworkInput; + network?: AlchemyNetwork; }; export type GetNftMetadataBatchResult = GetNftMetadataBatchResponse; @@ -193,7 +193,7 @@ export type GetContractMetadataResult = GetContractMetadataResponse; export type GetContractMetadataBatchParams = GetContractMetadataBatchBody & { /** Overrides the client-level network for this request. */ - network?: NetworkInput; + network?: AlchemyNetwork; }; export type GetContractMetadataBatchResult = GetContractMetadataBatchResponse; @@ -228,7 +228,7 @@ export type SearchContractMetadataResult = SearchContractMetadataResponse; export type GetSpamContractsParams = { /** Overrides the client-level network for this request. */ - network?: NetworkInput; + network?: AlchemyNetwork; }; export type GetSpamContractsResult = GetSpamContractsResponse; @@ -258,7 +258,7 @@ export type GetTokenBalancesParams = { /** Paging options (pageKey/maxCount; only valid with the "erc20" spec). */ options?: AlchemyGetTokenBalancesOptionsParam; /** Overrides the client-level network for this request. */ - network?: NetworkInput; + network?: AlchemyNetwork; }; export type GetTokenBalancesResult = AlchemyGetTokenBalancesResult; @@ -266,13 +266,13 @@ export type GetTokenMetadataParams = { /** The token contract address. */ contractAddress: AlchemyGetTokenMetadataParams; /** Overrides the client-level network for this request. */ - network?: NetworkInput; + network?: AlchemyNetwork; }; export type GetTokenMetadataResult = AlchemyGetTokenMetadataResult; export type GetTokenAllowanceParams = AlchemyGetTokenAllowanceParams & { /** Overrides the client-level network for this request. */ - network?: NetworkInput; + network?: AlchemyNetwork; }; export type GetTokenAllowanceResult = AlchemyGetTokenAllowanceResult; @@ -281,7 +281,7 @@ export type GetTokenAllowanceResult = AlchemyGetTokenAllowanceResult; /** Generated RPC params plus the SDK's network override. */ export type GetAssetTransfersParams = AlchemyGetAssetTransfersParams & { /** Overrides the client-level network for this request. */ - network?: NetworkInput; + network?: AlchemyNetwork; }; /** diff --git a/packages/data-apis/src/viem/dataActions.test-d.ts b/packages/data-apis/src/viem/dataActions.test-d.ts new file mode 100644 index 0000000000..2ef15b090d --- /dev/null +++ b/packages/data-apis/src/viem/dataActions.test-d.ts @@ -0,0 +1,25 @@ +import { alchemyTransport, type AlchemyTransport } from "@alchemy/common"; +import { createClient, http, type Chain, type Client } from "viem"; +import { mainnet } from "viem/chains"; +import { expectTypeOf, test } from "vitest"; +import type { DataActions } from "../decorator.js"; +import { dataActions } from "./dataActions.js"; + +declare const baseClient: Client; + +test("the parked adapter extends viem clients with the core action surface", () => { + const extended = createClient({ + chain: mainnet, + transport: alchemyTransport({ apiKey: "key" }), + }).extend(dataActions); + expectTypeOf(extended.nft.getNftsForOwner).toEqualTypeOf< + DataActions["nft"]["getNftsForOwner"] + >(); + expectTypeOf(dataActions(baseClient)).toEqualTypeOf(); +}); + +test("the adapter requires an Alchemy transport", () => { + const plainHttp = createClient({ chain: mainnet, transport: http() }); + // @ts-expect-error plain http transport is not an AlchemyTransport + dataActions(plainHttp); +}); diff --git a/packages/data-apis/src/viem/dataActions.test.ts b/packages/data-apis/src/viem/dataActions.test.ts new file mode 100644 index 0000000000..fd648567b4 --- /dev/null +++ b/packages/data-apis/src/viem/dataActions.test.ts @@ -0,0 +1,62 @@ +import { alchemyTransport } from "@alchemy/common"; +import { createClient } from "viem"; +import { mainnet } from "viem/chains"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { dataActions } from "./dataActions.js"; + +const fetchMock = vi.fn(); + +const jsonResponse = (body: unknown) => + new Response(JSON.stringify(body), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + +beforeEach(() => { + vi.stubGlobal("fetch", fetchMock); + fetchMock.mockReset(); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe("parked viem adapter", () => { + it("extends a viem client with an Alchemy transport, reusing its auth and chain", async () => { + fetchMock.mockImplementation(async () => + jsonResponse({ ownedNfts: [], totalCount: 0 }), + ); + + const client = createClient({ + chain: mainnet, + transport: alchemyTransport({ apiKey: "test-key" }), + }).extend(dataActions); + + await client.nft.getNftsForOwner({ owner: "0xabc" }); + expect(String(fetchMock.mock.calls[0]![0])).toBe( + "https://eth-mainnet.g.alchemy.com/nft/v3/getNFTsForOwner?owner=0xabc", + ); + const headers = fetchMock.mock.calls[0]![1].headers as Headers; + expect(headers.get("Authorization")).toBe("Bearer test-key"); + }); + + it("routes JSON-RPC through the shared runtime (envelope, network-scoped url)", async () => { + fetchMock.mockImplementation(async () => + jsonResponse({ jsonrpc: "2.0", id: 1, result: { transfers: [] } }), + ); + + const client = createClient({ + chain: mainnet, + transport: alchemyTransport({ apiKey: "test-key" }), + }).extend(dataActions); + + const result = await client.transfers.getAssetTransfers({ + category: ["erc20"], + }); + expect(result).toEqual({ transfers: [] }); + expect(String(fetchMock.mock.calls[0]![0])).toContain( + "https://eth-mainnet.g.alchemy.com/v2", + ); + expect(JSON.parse(fetchMock.mock.calls[0]![1].body).jsonrpc).toBe("2.0"); + }); +}); diff --git a/packages/data-apis/src/viem/dataActions.ts b/packages/data-apis/src/viem/dataActions.ts new file mode 100644 index 0000000000..4f4f0884d7 --- /dev/null +++ b/packages/data-apis/src/viem/dataActions.ts @@ -0,0 +1,52 @@ +// PARKED: the viem adapter for @alchemy/data-apis. Not exported from the +// package; it becomes the `/viem` subpath export when adapter demand +// warrants (post-v1, per the approved standalone-core direction). Kept here +// with tests so it stays working against the core. + +import { + resolveNetwork, + type AlchemyTransport, + type AlchemyTransportConfig, +} from "@alchemy/common"; +import type { Chain, Client } from "viem"; +import { + dataActions as coreDataActions, + type DataActions, +} from "../decorator.js"; +import type { DataClient } from "../internal/clientHelpers.js"; + +/** + * Attaches the Data API actions to an existing viem client configured with + * an Alchemy transport: `client.extend(dataActions)`. Auth and retry/timeout + * settings are read from the transport's config; the client's chain (when + * present and registry-known) becomes the default network. + * + * @param {Client} client A viem client with an Alchemy transport + * @returns {DataActions} The namespaced Data API actions + */ +export function dataActions( + client: Client, +): DataActions { + const config = client.transport.config as AlchemyTransportConfig; + const chain = client.chain; + const customSlug = (chain?.custom as { alchemyNetwork?: string } | undefined) + ?.alchemyNetwork; + + const core: DataClient = { + config: { + apiKey: config.apiKey, + jwt: config.jwt, + url: config.url, + headers: config.fetchOptions?.headers, + retryCount: config.retryCount, + retryDelay: config.retryDelay, + timeout: config.timeout, + }, + network: customSlug + ? resolveNetwork(customSlug) + : chain + ? resolveNetwork(chain) + : undefined, + }; + return coreDataActions(core); +} From 8940ab550b2dbc47c6d1590e42fbb32b12edc92d Mon Sep 17 00:00:00 2001 From: blake duncan Date: Fri, 12 Jun 2026 11:37:22 -0400 Subject: [PATCH 16/20] chore(api-codegen): relocate to repo root per private-package convention packages/ is for published packages; private tooling lives at the root (like halp). Updates workspace globs, vitest projects, REPO_ROOT resolution, the data-apis generate script, eslintignore, CI drift-gate and spec-bump workflow paths, and generated-file banners (regenerated; pipeline verified idempotent post-move). Co-Authored-By: Claude Fable 5 --- .eslintignore | 2 +- .github/workflows/bump-api-specs.yml | 4 +- .github/workflows/on-pull-request.yml | 2 +- .../api-codegen => api-codegen}/README.md | 0 .../api-codegen => api-codegen}/package.json | 0 .../specs/nft.json | 0 .../specs/portfolio.json | 0 .../specs/prices.json | 0 .../specs/specs.lock.json | 0 .../specs/token.json | 0 .../specs/transfers.json | 0 .../api-codegen => api-codegen}/src/cli.ts | 0 .../api-codegen => api-codegen}/src/errors.ts | 0 .../api-codegen => api-codegen}/src/format.ts | 2 +- .../src/generate.ts | 0 .../api-codegen => api-codegen}/src/hash.ts | 0 .../api-codegen => api-codegen}/src/index.ts | 0 .../src/manifest.ts | 0 .../api-codegen => api-codegen}/src/paths.ts | 2 +- .../src/rest/normalizePath.ts | 0 .../src/rest/openapiTypes.ts | 0 .../src/rest/schemaEmitter.ts | 0 .../src/rpc/openrpcWalker.ts | 0 .../src/rpc/rpcEmitter.ts | 0 .../src/schemaWalk.ts | 0 .../src/snapshot.ts | 0 .../src/targets.ts | 0 .../api-codegen => api-codegen}/src/write.ts | 0 .../tests/fixtures/rest.fixture.json | 0 .../tests/fixtures/rpc.fixture.json | 0 .../tests/manifest.test.ts | 0 .../tests/normalizePath.test.ts | 0 .../tests/rpcEmitter.test.ts | 0 .../tests/schemaEmitter.test.ts | 0 .../api-codegen => api-codegen}/tsconfig.json | 0 .../vitest.config.ts | 0 packages/data-apis/package.json | 2 +- .../src/generated/rest/nft.schema.ts | 2 +- .../data-apis/src/generated/rest/nft.types.ts | 2 +- .../src/generated/rest/portfolio.schema.ts | 2 +- .../src/generated/rest/portfolio.types.ts | 2 +- .../src/generated/rest/prices.schema.ts | 2 +- .../src/generated/rest/prices.types.ts | 2 +- packages/data-apis/src/generated/rpc/token.ts | 2 +- .../data-apis/src/generated/rpc/transfers.ts | 2 +- pnpm-lock.yaml | 44 +++++++++---------- pnpm-workspace.yaml | 1 + vitest.config.ts | 2 +- 48 files changed, 37 insertions(+), 40 deletions(-) rename {packages/api-codegen => api-codegen}/README.md (100%) rename {packages/api-codegen => api-codegen}/package.json (100%) rename {packages/api-codegen => api-codegen}/specs/nft.json (100%) rename {packages/api-codegen => api-codegen}/specs/portfolio.json (100%) rename {packages/api-codegen => api-codegen}/specs/prices.json (100%) rename {packages/api-codegen => api-codegen}/specs/specs.lock.json (100%) rename {packages/api-codegen => api-codegen}/specs/token.json (100%) rename {packages/api-codegen => api-codegen}/specs/transfers.json (100%) rename {packages/api-codegen => api-codegen}/src/cli.ts (100%) rename {packages/api-codegen => api-codegen}/src/errors.ts (100%) rename {packages/api-codegen => api-codegen}/src/format.ts (94%) rename {packages/api-codegen => api-codegen}/src/generate.ts (100%) rename {packages/api-codegen => api-codegen}/src/hash.ts (100%) rename {packages/api-codegen => api-codegen}/src/index.ts (100%) rename {packages/api-codegen => api-codegen}/src/manifest.ts (100%) rename {packages/api-codegen => api-codegen}/src/paths.ts (90%) rename {packages/api-codegen => api-codegen}/src/rest/normalizePath.ts (100%) rename {packages/api-codegen => api-codegen}/src/rest/openapiTypes.ts (100%) rename {packages/api-codegen => api-codegen}/src/rest/schemaEmitter.ts (100%) rename {packages/api-codegen => api-codegen}/src/rpc/openrpcWalker.ts (100%) rename {packages/api-codegen => api-codegen}/src/rpc/rpcEmitter.ts (100%) rename {packages/api-codegen => api-codegen}/src/schemaWalk.ts (100%) rename {packages/api-codegen => api-codegen}/src/snapshot.ts (100%) rename {packages/api-codegen => api-codegen}/src/targets.ts (100%) rename {packages/api-codegen => api-codegen}/src/write.ts (100%) rename {packages/api-codegen => api-codegen}/tests/fixtures/rest.fixture.json (100%) rename {packages/api-codegen => api-codegen}/tests/fixtures/rpc.fixture.json (100%) rename {packages/api-codegen => api-codegen}/tests/manifest.test.ts (100%) rename {packages/api-codegen => api-codegen}/tests/normalizePath.test.ts (100%) rename {packages/api-codegen => api-codegen}/tests/rpcEmitter.test.ts (100%) rename {packages/api-codegen => api-codegen}/tests/schemaEmitter.test.ts (100%) rename {packages/api-codegen => api-codegen}/tsconfig.json (100%) rename {packages/api-codegen => api-codegen}/vitest.config.ts (100%) diff --git a/.eslintignore b/.eslintignore index 1b7d6301c4..bdbb96ce86 100644 --- a/.eslintignore +++ b/.eslintignore @@ -17,4 +17,4 @@ pnpm-lock.yaml # codegen: committed spec snapshots (generated .ts files instead carry a # file-level eslint-disable banner so lint-staged can pass them explicitly) -packages/api-codegen/specs/** +api-codegen/specs/** diff --git a/.github/workflows/bump-api-specs.yml b/.github/workflows/bump-api-specs.yml index 76fa4af8db..1db65f8e4b 100644 --- a/.github/workflows/bump-api-specs.yml +++ b/.github/workflows/bump-api-specs.yml @@ -36,7 +36,7 @@ jobs: working-directory: .docs-checkout - name: Snapshot specs from docs main - run: pnpm tsx packages/api-codegen/src/cli.ts snapshot --docs "$GITHUB_WORKSPACE/.docs-checkout" + run: pnpm tsx api-codegen/src/cli.ts snapshot --docs "$GITHUB_WORKSPACE/.docs-checkout" - name: Regenerate SDK internals run: | @@ -55,7 +55,7 @@ jobs: Automated re-snapshot of the bundled OpenAPI/OpenRPC specs from `alchemyplatform/docs` main, plus regenerated SDK internals. - Review the `packages/api-codegen/specs/` diff for what moved + Review the `api-codegen/specs/` diff for what moved upstream and the `src/generated/` diff for the type impact. Public types in `packages/data-apis/src/types.ts` are aliases — check whether any public-surface change warrants a semver note. diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index ad879fbf10..130e528600 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -189,7 +189,7 @@ jobs: - name: Check generated API code is up to date run: | pnpm run generate - git diff --exit-code packages/*/src/generated packages/api-codegen/specs || \ + git diff --exit-code packages/*/src/generated api-codegen/specs || \ (echo "::error::Generated API code is out of date or spec snapshots were hand-edited. Run 'pnpm generate' and commit the changes." && exit 1) - name: Check SDK reference docs are up to date diff --git a/packages/api-codegen/README.md b/api-codegen/README.md similarity index 100% rename from packages/api-codegen/README.md rename to api-codegen/README.md diff --git a/packages/api-codegen/package.json b/api-codegen/package.json similarity index 100% rename from packages/api-codegen/package.json rename to api-codegen/package.json diff --git a/packages/api-codegen/specs/nft.json b/api-codegen/specs/nft.json similarity index 100% rename from packages/api-codegen/specs/nft.json rename to api-codegen/specs/nft.json diff --git a/packages/api-codegen/specs/portfolio.json b/api-codegen/specs/portfolio.json similarity index 100% rename from packages/api-codegen/specs/portfolio.json rename to api-codegen/specs/portfolio.json diff --git a/packages/api-codegen/specs/prices.json b/api-codegen/specs/prices.json similarity index 100% rename from packages/api-codegen/specs/prices.json rename to api-codegen/specs/prices.json diff --git a/packages/api-codegen/specs/specs.lock.json b/api-codegen/specs/specs.lock.json similarity index 100% rename from packages/api-codegen/specs/specs.lock.json rename to api-codegen/specs/specs.lock.json diff --git a/packages/api-codegen/specs/token.json b/api-codegen/specs/token.json similarity index 100% rename from packages/api-codegen/specs/token.json rename to api-codegen/specs/token.json diff --git a/packages/api-codegen/specs/transfers.json b/api-codegen/specs/transfers.json similarity index 100% rename from packages/api-codegen/specs/transfers.json rename to api-codegen/specs/transfers.json diff --git a/packages/api-codegen/src/cli.ts b/api-codegen/src/cli.ts similarity index 100% rename from packages/api-codegen/src/cli.ts rename to api-codegen/src/cli.ts diff --git a/packages/api-codegen/src/errors.ts b/api-codegen/src/errors.ts similarity index 100% rename from packages/api-codegen/src/errors.ts rename to api-codegen/src/errors.ts diff --git a/packages/api-codegen/src/format.ts b/api-codegen/src/format.ts similarity index 94% rename from packages/api-codegen/src/format.ts rename to api-codegen/src/format.ts index ef07525d3e..079bb496c8 100644 --- a/packages/api-codegen/src/format.ts +++ b/api-codegen/src/format.ts @@ -3,7 +3,7 @@ import prettier from "prettier"; const BANNER = `/* eslint-disable -- machine-generated file */ // AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. // Regenerate with \`pnpm generate\`. Spec provenance (docs repo commit + checksums) -// is recorded in packages/api-codegen/specs/specs.lock.json. +// is recorded in api-codegen/specs/specs.lock.json. `; diff --git a/packages/api-codegen/src/generate.ts b/api-codegen/src/generate.ts similarity index 100% rename from packages/api-codegen/src/generate.ts rename to api-codegen/src/generate.ts diff --git a/packages/api-codegen/src/hash.ts b/api-codegen/src/hash.ts similarity index 100% rename from packages/api-codegen/src/hash.ts rename to api-codegen/src/hash.ts diff --git a/packages/api-codegen/src/index.ts b/api-codegen/src/index.ts similarity index 100% rename from packages/api-codegen/src/index.ts rename to api-codegen/src/index.ts diff --git a/packages/api-codegen/src/manifest.ts b/api-codegen/src/manifest.ts similarity index 100% rename from packages/api-codegen/src/manifest.ts rename to api-codegen/src/manifest.ts diff --git a/packages/api-codegen/src/paths.ts b/api-codegen/src/paths.ts similarity index 90% rename from packages/api-codegen/src/paths.ts rename to api-codegen/src/paths.ts index 8e17b3db14..b50375a2e4 100644 --- a/packages/api-codegen/src/paths.ts +++ b/api-codegen/src/paths.ts @@ -8,7 +8,7 @@ export const PACKAGE_DIR = resolve( ); /** Absolute path of the aa-sdk repo root. */ -export const REPO_ROOT = resolve(PACKAGE_DIR, "../.."); +export const REPO_ROOT = resolve(PACKAGE_DIR, ".."); /** Absolute path of the committed spec snapshots directory. */ export const SPECS_DIR = resolve(PACKAGE_DIR, "specs"); diff --git a/packages/api-codegen/src/rest/normalizePath.ts b/api-codegen/src/rest/normalizePath.ts similarity index 100% rename from packages/api-codegen/src/rest/normalizePath.ts rename to api-codegen/src/rest/normalizePath.ts diff --git a/packages/api-codegen/src/rest/openapiTypes.ts b/api-codegen/src/rest/openapiTypes.ts similarity index 100% rename from packages/api-codegen/src/rest/openapiTypes.ts rename to api-codegen/src/rest/openapiTypes.ts diff --git a/packages/api-codegen/src/rest/schemaEmitter.ts b/api-codegen/src/rest/schemaEmitter.ts similarity index 100% rename from packages/api-codegen/src/rest/schemaEmitter.ts rename to api-codegen/src/rest/schemaEmitter.ts diff --git a/packages/api-codegen/src/rpc/openrpcWalker.ts b/api-codegen/src/rpc/openrpcWalker.ts similarity index 100% rename from packages/api-codegen/src/rpc/openrpcWalker.ts rename to api-codegen/src/rpc/openrpcWalker.ts diff --git a/packages/api-codegen/src/rpc/rpcEmitter.ts b/api-codegen/src/rpc/rpcEmitter.ts similarity index 100% rename from packages/api-codegen/src/rpc/rpcEmitter.ts rename to api-codegen/src/rpc/rpcEmitter.ts diff --git a/packages/api-codegen/src/schemaWalk.ts b/api-codegen/src/schemaWalk.ts similarity index 100% rename from packages/api-codegen/src/schemaWalk.ts rename to api-codegen/src/schemaWalk.ts diff --git a/packages/api-codegen/src/snapshot.ts b/api-codegen/src/snapshot.ts similarity index 100% rename from packages/api-codegen/src/snapshot.ts rename to api-codegen/src/snapshot.ts diff --git a/packages/api-codegen/src/targets.ts b/api-codegen/src/targets.ts similarity index 100% rename from packages/api-codegen/src/targets.ts rename to api-codegen/src/targets.ts diff --git a/packages/api-codegen/src/write.ts b/api-codegen/src/write.ts similarity index 100% rename from packages/api-codegen/src/write.ts rename to api-codegen/src/write.ts diff --git a/packages/api-codegen/tests/fixtures/rest.fixture.json b/api-codegen/tests/fixtures/rest.fixture.json similarity index 100% rename from packages/api-codegen/tests/fixtures/rest.fixture.json rename to api-codegen/tests/fixtures/rest.fixture.json diff --git a/packages/api-codegen/tests/fixtures/rpc.fixture.json b/api-codegen/tests/fixtures/rpc.fixture.json similarity index 100% rename from packages/api-codegen/tests/fixtures/rpc.fixture.json rename to api-codegen/tests/fixtures/rpc.fixture.json diff --git a/packages/api-codegen/tests/manifest.test.ts b/api-codegen/tests/manifest.test.ts similarity index 100% rename from packages/api-codegen/tests/manifest.test.ts rename to api-codegen/tests/manifest.test.ts diff --git a/packages/api-codegen/tests/normalizePath.test.ts b/api-codegen/tests/normalizePath.test.ts similarity index 100% rename from packages/api-codegen/tests/normalizePath.test.ts rename to api-codegen/tests/normalizePath.test.ts diff --git a/packages/api-codegen/tests/rpcEmitter.test.ts b/api-codegen/tests/rpcEmitter.test.ts similarity index 100% rename from packages/api-codegen/tests/rpcEmitter.test.ts rename to api-codegen/tests/rpcEmitter.test.ts diff --git a/packages/api-codegen/tests/schemaEmitter.test.ts b/api-codegen/tests/schemaEmitter.test.ts similarity index 100% rename from packages/api-codegen/tests/schemaEmitter.test.ts rename to api-codegen/tests/schemaEmitter.test.ts diff --git a/packages/api-codegen/tsconfig.json b/api-codegen/tsconfig.json similarity index 100% rename from packages/api-codegen/tsconfig.json rename to api-codegen/tsconfig.json diff --git a/packages/api-codegen/vitest.config.ts b/api-codegen/vitest.config.ts similarity index 100% rename from packages/api-codegen/vitest.config.ts rename to api-codegen/vitest.config.ts diff --git a/packages/data-apis/package.json b/packages/data-apis/package.json index 0b372c9808..0efce3e6ae 100644 --- a/packages/data-apis/package.json +++ b/packages/data-apis/package.json @@ -43,7 +43,7 @@ "build:esm": "tsc --project tsconfig.build.json --outDir ./dist/esm", "build:types": "tsc --project tsconfig.build.json --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", "clean": "rm -rf ./dist", - "generate": "tsx ../api-codegen/src/cli.ts --target data-apis", + "generate": "tsx ../../api-codegen/src/cli.ts --target data-apis", "test": "vitest", "test:run": "vitest run", "smoke-test": "tsx scripts/smoke-test.ts" diff --git a/packages/data-apis/src/generated/rest/nft.schema.ts b/packages/data-apis/src/generated/rest/nft.schema.ts index 624b45287b..a93909c478 100644 --- a/packages/data-apis/src/generated/rest/nft.schema.ts +++ b/packages/data-apis/src/generated/rest/nft.schema.ts @@ -1,7 +1,7 @@ /* eslint-disable -- machine-generated file */ // AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. // Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) -// is recorded in packages/api-codegen/specs/specs.lock.json. +// is recorded in api-codegen/specs/specs.lock.json. import type { RestRequestSchema } from "@alchemy/common"; import type { operations } from "./nft.types.js"; diff --git a/packages/data-apis/src/generated/rest/nft.types.ts b/packages/data-apis/src/generated/rest/nft.types.ts index 523cc926b4..6db05ca066 100644 --- a/packages/data-apis/src/generated/rest/nft.types.ts +++ b/packages/data-apis/src/generated/rest/nft.types.ts @@ -1,7 +1,7 @@ /* eslint-disable -- machine-generated file */ // AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. // Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) -// is recorded in packages/api-codegen/specs/specs.lock.json. +// is recorded in api-codegen/specs/specs.lock.json. export interface paths { "/v3/{apiKey}/getNFTsForOwner": { diff --git a/packages/data-apis/src/generated/rest/portfolio.schema.ts b/packages/data-apis/src/generated/rest/portfolio.schema.ts index 3bfdc519b7..15a1e5edbe 100644 --- a/packages/data-apis/src/generated/rest/portfolio.schema.ts +++ b/packages/data-apis/src/generated/rest/portfolio.schema.ts @@ -1,7 +1,7 @@ /* eslint-disable -- machine-generated file */ // AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. // Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) -// is recorded in packages/api-codegen/specs/specs.lock.json. +// is recorded in api-codegen/specs/specs.lock.json. import type { RestRequestSchema } from "@alchemy/common"; import type { operations } from "./portfolio.types.js"; diff --git a/packages/data-apis/src/generated/rest/portfolio.types.ts b/packages/data-apis/src/generated/rest/portfolio.types.ts index b0adf078e7..cda8efef9b 100644 --- a/packages/data-apis/src/generated/rest/portfolio.types.ts +++ b/packages/data-apis/src/generated/rest/portfolio.types.ts @@ -1,7 +1,7 @@ /* eslint-disable -- machine-generated file */ // AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. // Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) -// is recorded in packages/api-codegen/specs/specs.lock.json. +// is recorded in api-codegen/specs/specs.lock.json. export interface paths { "/{apiKey}/assets/tokens/by-address": { diff --git a/packages/data-apis/src/generated/rest/prices.schema.ts b/packages/data-apis/src/generated/rest/prices.schema.ts index 4b5b0df657..8be28582ea 100644 --- a/packages/data-apis/src/generated/rest/prices.schema.ts +++ b/packages/data-apis/src/generated/rest/prices.schema.ts @@ -1,7 +1,7 @@ /* eslint-disable -- machine-generated file */ // AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. // Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) -// is recorded in packages/api-codegen/specs/specs.lock.json. +// is recorded in api-codegen/specs/specs.lock.json. import type { RestRequestSchema } from "@alchemy/common"; import type { operations } from "./prices.types.js"; diff --git a/packages/data-apis/src/generated/rest/prices.types.ts b/packages/data-apis/src/generated/rest/prices.types.ts index 8afa73c9f2..e0ff18ad0d 100644 --- a/packages/data-apis/src/generated/rest/prices.types.ts +++ b/packages/data-apis/src/generated/rest/prices.types.ts @@ -1,7 +1,7 @@ /* eslint-disable -- machine-generated file */ // AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. // Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) -// is recorded in packages/api-codegen/specs/specs.lock.json. +// is recorded in api-codegen/specs/specs.lock.json. export interface paths { "/{apiKey}/tokens/by-symbol": { diff --git a/packages/data-apis/src/generated/rpc/token.ts b/packages/data-apis/src/generated/rpc/token.ts index 036e4dcde4..364c6eb735 100644 --- a/packages/data-apis/src/generated/rpc/token.ts +++ b/packages/data-apis/src/generated/rpc/token.ts @@ -1,7 +1,7 @@ /* eslint-disable -- machine-generated file */ // AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. // Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) -// is recorded in packages/api-codegen/specs/specs.lock.json. +// is recorded in api-codegen/specs/specs.lock.json. export type AlchemyGetTokenBalancesAddressParam = string; diff --git a/packages/data-apis/src/generated/rpc/transfers.ts b/packages/data-apis/src/generated/rpc/transfers.ts index 19b69c30be..4b1059d6be 100644 --- a/packages/data-apis/src/generated/rpc/transfers.ts +++ b/packages/data-apis/src/generated/rpc/transfers.ts @@ -1,7 +1,7 @@ /* eslint-disable -- machine-generated file */ // AUTO-GENERATED by @alchemy/api-codegen — DO NOT EDIT. // Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) -// is recorded in packages/api-codegen/specs/specs.lock.json. +// is recorded in api-codegen/specs/specs.lock.json. export interface AlchemyGetAssetTransfersParams { fromBlock?: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ca8616a60..2aa9002493 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -182,6 +182,21 @@ importers: specifier: ^2.45.0 version: 2.46.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.4.3) + api-codegen: + devDependencies: + json-schema-to-typescript: + specifier: 15.0.4 + version: 15.0.4 + openapi-typescript: + specifier: 7.13.0 + version: 7.13.0(typescript@5.9.3) + prettier: + specifier: 3.3.3 + version: 3.3.3 + typescript-template: + specifier: workspace:* + version: link:../templates/typescript + halp: dependencies: '@inquirer/prompts': @@ -220,21 +235,6 @@ importers: specifier: ^2.45.0 version: 2.46.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.4.3) - packages/api-codegen: - devDependencies: - json-schema-to-typescript: - specifier: 15.0.4 - version: 15.0.4 - openapi-typescript: - specifier: 7.13.0 - version: 7.13.0(typescript@5.9.3) - prettier: - specifier: 3.3.3 - version: 3.3.3 - typescript-template: - specifier: workspace:* - version: link:../../templates/typescript - packages/common: dependencies: zod: @@ -259,7 +259,7 @@ importers: devDependencies: '@alchemy/api-codegen': specifier: workspace:* - version: link:../api-codegen + version: link:../../api-codegen typescript-template: specifier: workspace:* version: link:../../templates/typescript @@ -9156,7 +9156,7 @@ snapshots: '@types/node': 20.5.1 chalk: 4.1.2 cosmiconfig: 8.3.6(typescript@5.9.3) - cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.9.3))(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.9.3))(typescript@5.9.3) + cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.9.3))(ts-node@10.9.2(@types/node@22.19.15)(typescript@5.9.3))(typescript@5.9.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -12835,7 +12835,7 @@ snapshots: core-util-is@1.0.3: {} - cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.9.3))(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.9.3))(typescript@5.9.3): + cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.9.3))(ts-node@10.9.2(@types/node@22.19.15)(typescript@5.9.3))(typescript@5.9.3): dependencies: '@types/node': 20.5.1 cosmiconfig: 8.3.6(typescript@5.9.3) @@ -13856,10 +13856,6 @@ snapshots: dependencies: format: 0.2.2 - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -17621,8 +17617,8 @@ snapshots: tinyglobby@0.2.12: dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 tinyglobby@0.2.17: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 23cadf3e32..24f1442ab9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,4 +2,5 @@ packages: - "templates/*" - ".vitest" - "halp" + - "api-codegen" - "packages/*" diff --git a/vitest.config.ts b/vitest.config.ts index 00b629b360..69c9617b4d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,6 +3,6 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { maxWorkers: 4, - projects: [".vitest", "packages/*"], + projects: [".vitest", "packages/*", "api-codegen"], }, }); From 4683bd7c048099dc55d8c5b20ae8405372227dbd Mon Sep 17 00:00:00 2001 From: blake duncan Date: Fri, 12 Jun 2026 11:40:11 -0400 Subject: [PATCH 17/20] docs(data-apis): core-first README + regenerated reference docs README leads with the dependency-free quickstart (no chain library), documents string-only network inputs with the eip155 bridge for chain holders, proxy/jwt usage, and the post-v1 ecosystem-adapter plan. Reference docs regenerated for the inverted public surface. Co-Authored-By: Claude Fable 5 --- docs/docs.yml | 20 +- docs/pages/reference/common/src/README.mdx | 103 ++++---- .../common/src/classes/AlchemyApiError.mdx | 133 +++++++++- .../common/src/classes/AlchemyError.mdx | 240 ++++++++++++++++++ .../src/classes/AlchemyJsonRpcClient.mdx | 126 +++++++++ .../common/src/classes/AlchemyRestClient.mdx | 4 +- .../common/src/classes/BaseError.mdx | 1 - .../common/src/classes/FetchError.mdx | 127 ++++++++- .../common/src/classes/ServerError.mdx | 127 ++++++++- .../src/functions/redactUrlCredentials.mdx | 54 ++++ .../AlchemyJsonRpcClientParams.mdx | 40 +++ .../type-aliases/AlchemyRestClientParams.mdx | 2 +- .../src/type-aliases/JsonRpcRequestFn.mdx | 120 +++++++++ .../common/src/type-aliases/JsonRpcSchema.mdx | 18 ++ .../common/src/type-aliases/QueryParams.mdx | 2 +- .../common/src/type-aliases/QueryValue.mdx | 2 +- .../common/src/type-aliases/RestRequestFn.mdx | 2 +- .../src/type-aliases/RestRequestOptions.mdx | 2 +- .../src/type-aliases/RestRequestParams.mdx | 2 +- .../src/type-aliases/RestRequestSchema.mdx | 2 +- .../src/variables/DEFAULT_RETRY_COUNT.mdx | 14 + .../src/variables/DEFAULT_RETRY_DELAY_MS.mdx | 14 + .../src/variables/DEFAULT_TIMEOUT_MS.mdx | 14 + docs/pages/reference/data-apis/src/README.mdx | 168 +++++++++--- .../src/functions/createDataClient.mdx | 15 +- .../data-apis/src/functions/dataActions.mdx | 68 ----- .../src/functions/getAssetTransfers.mdx | 13 +- .../src/functions/getTokenAllowance.mdx | 2 +- .../src/functions/getTokenBalances.mdx | 2 +- .../src/functions/getTokenMetadata.mdx | 2 +- .../src/interfaces/PortfolioAddressEntry.mdx | 4 +- .../src/interfaces/PriceAddressEntry.mdx | 4 +- .../src/type-aliases/AlchemyDataClient.mdx | 5 +- .../type-aliases/AlchemyDataClientOptions.mdx | 18 +- .../src/type-aliases/DataActions.mdx | 4 +- .../type-aliases/GetAssetTransfersParams.mdx | 2 +- .../GetContractMetadataBatchParams.mdx | 2 +- .../GetHistoricalTokenPricesParams.mdx | 2 +- .../GetNftMetadataBatchParams.mdx | 2 +- .../type-aliases/GetSpamContractsParams.mdx | 2 +- .../type-aliases/GetTokenAllowanceParams.mdx | 2 +- .../type-aliases/GetTokenBalancesParams.mdx | 2 +- .../type-aliases/GetTokenMetadataParams.mdx | 2 +- .../src/type-aliases/RequestOptions.mdx | 2 +- packages/common/src/utils/redact.ts | 2 +- packages/data-apis/README.md | 90 ++++--- packages/data-apis/package.json | 2 +- packages/data-apis/src/decorator.ts | 2 +- 48 files changed, 1324 insertions(+), 264 deletions(-) create mode 100644 docs/pages/reference/common/src/classes/AlchemyError.mdx create mode 100644 docs/pages/reference/common/src/classes/AlchemyJsonRpcClient.mdx create mode 100644 docs/pages/reference/common/src/functions/redactUrlCredentials.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/AlchemyJsonRpcClientParams.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/JsonRpcRequestFn.mdx create mode 100644 docs/pages/reference/common/src/type-aliases/JsonRpcSchema.mdx create mode 100644 docs/pages/reference/common/src/variables/DEFAULT_RETRY_COUNT.mdx create mode 100644 docs/pages/reference/common/src/variables/DEFAULT_RETRY_DELAY_MS.mdx create mode 100644 docs/pages/reference/common/src/variables/DEFAULT_TIMEOUT_MS.mdx delete mode 100644 docs/pages/reference/data-apis/src/functions/dataActions.mdx diff --git a/docs/docs.yml b/docs/docs.yml index 534e6f7ed4..4f01129079 100644 --- a/docs/docs.yml +++ b/docs/docs.yml @@ -515,8 +515,6 @@ navigation: path: wallets/pages/reference/data-apis/src/functions/computeRarity.mdx - page: createDataClient path: wallets/pages/reference/data-apis/src/functions/createDataClient.mdx - - page: dataActions - path: wallets/pages/reference/data-apis/src/functions/dataActions.mdx - page: getAssetTransfers path: wallets/pages/reference/data-apis/src/functions/getAssetTransfers.mdx - page: getAssetTransfersPages @@ -781,6 +779,10 @@ navigation: path: wallets/pages/reference/common/src/classes/AccountNotFoundError.mdx - page: AlchemyApiError path: wallets/pages/reference/common/src/classes/AlchemyApiError.mdx + - page: AlchemyError + path: wallets/pages/reference/common/src/classes/AlchemyError.mdx + - page: AlchemyJsonRpcClient + path: wallets/pages/reference/common/src/classes/AlchemyJsonRpcClient.mdx - page: AlchemyRestClient path: wallets/pages/reference/common/src/classes/AlchemyRestClient.mdx - page: BaseError @@ -823,6 +825,8 @@ navigation: path: wallets/pages/reference/common/src/functions/lowerAddress.mdx - page: raise path: wallets/pages/reference/common/src/functions/raise.mdx + - page: redactUrlCredentials + path: wallets/pages/reference/common/src/functions/redactUrlCredentials.mdx - page: resolveNetwork path: wallets/pages/reference/common/src/functions/resolveNetwork.mdx - page: sleep @@ -839,6 +843,8 @@ navigation: path: wallets/pages/reference/common/src/type-aliases/AlchemyApiErrorDetails.mdx - page: AlchemyConnectionConfig path: wallets/pages/reference/common/src/type-aliases/AlchemyConnectionConfig.mdx + - page: AlchemyJsonRpcClientParams + path: wallets/pages/reference/common/src/type-aliases/AlchemyJsonRpcClientParams.mdx - page: AlchemyNetwork path: wallets/pages/reference/common/src/type-aliases/AlchemyNetwork.mdx - page: AlchemyRestClientParams @@ -847,6 +853,10 @@ navigation: path: wallets/pages/reference/common/src/type-aliases/AlchemyTransport.mdx - page: ExtractRpcMethod path: wallets/pages/reference/common/src/type-aliases/ExtractRpcMethod.mdx + - page: JsonRpcRequestFn + path: wallets/pages/reference/common/src/type-aliases/JsonRpcRequestFn.mdx + - page: JsonRpcSchema + path: wallets/pages/reference/common/src/type-aliases/JsonRpcSchema.mdx - page: KnownAlchemyNetwork path: wallets/pages/reference/common/src/type-aliases/KnownAlchemyNetwork.mdx - page: NetworkInput @@ -871,3 +881,9 @@ navigation: contents: - page: AlchemyConnectionConfigSchema path: wallets/pages/reference/common/src/variables/AlchemyConnectionConfigSchema.mdx + - page: DEFAULT_RETRY_COUNT + path: wallets/pages/reference/common/src/variables/DEFAULT_RETRY_COUNT.mdx + - page: DEFAULT_RETRY_DELAY_MS + path: wallets/pages/reference/common/src/variables/DEFAULT_RETRY_DELAY_MS.mdx + - page: DEFAULT_TIMEOUT_MS + path: wallets/pages/reference/common/src/variables/DEFAULT_TIMEOUT_MS.mdx diff --git a/docs/pages/reference/common/src/README.mdx b/docs/pages/reference/common/src/README.mdx index a5d7f866ed..61f99b1b67 100644 --- a/docs/pages/reference/common/src/README.mdx +++ b/docs/pages/reference/common/src/README.mdx @@ -44,18 +44,20 @@ MIT ## Classes -| Class | Description | -| :--------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| [AccountNotFoundError](/wallets/reference/common/classes/AccountNotFoundError) | This error is thrown when an account could not be found to execute a specific action. It extends the `BaseError` class. | -| [AlchemyApiError](/wallets/reference/common/classes/AlchemyApiError) | The normalized error family for Alchemy API failures. Both the REST channel (AlchemyRestClient → ServerError/FetchError, which extend this class) and SDK JSON-RPC actions surface failures as AlchemyApiError, so consumers can handle status/code/requestId/retryAfter uniformly: | -| [AlchemyRestClient](/wallets/reference/common/classes/AlchemyRestClient) | A client for making requests to Alchemy's non-JSON-RPC endpoints, with typed routes/bodies/queries (via a RestRequestSchema), bounded retries with exponential backoff (429/5xx/network only, honoring Retry-After), per-attempt timeouts, abort support, and a per-request idempotency id sent as X-Alchemy-Client-Request-Id and surfaced on thrown errors. | -| [BaseError](/wallets/reference/common/classes/BaseError) | A custom error class that extends from `ViemBaseError`. This class allows for error messages to include links to relevant documentation based on provided `docsPath` and `docsSlug` parameters. This is based on on viem's BaseError type (obviously from the import and extend) we want the errors here to point to our docs if we supply a docsPath though | -| [ChainNotFoundError](/wallets/reference/common/classes/ChainNotFoundError) | Error class representing a "Chain Not Found" error, typically thrown when no chain is supplied to the client. | -| [ConnectionConfigError](/wallets/reference/common/classes/ConnectionConfigError) | Error class for connection configuration validation failures. | -| [FetchError](/wallets/reference/common/classes/FetchError) | Error class representing a "Fetch Error" error, typically thrown when a fetch request fails. | -| [InvalidRequestError](/wallets/reference/common/classes/InvalidRequestError) | This error is thrown when an invalid request is made. It extends the `BaseError` class. | -| [MethodUnsupportedError](/wallets/reference/common/classes/MethodUnsupportedError) | This error is thrown when an unknown method is called. It extends the `BaseError` class. | -| [ServerError](/wallets/reference/common/classes/ServerError) | Error class representing a "Server Error" error, typically thrown when a server request fails. | +| Class | Description | +| :--------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [AccountNotFoundError](/wallets/reference/common/classes/AccountNotFoundError) | This error is thrown when an account could not be found to execute a specific action. It extends the `BaseError` class. | +| [AlchemyApiError](/wallets/reference/common/classes/AlchemyApiError) | The normalized error family for Alchemy API failures. Both the REST channel (AlchemyRestClient → ServerError/FetchError, which extend this class) and SDK JSON-RPC actions surface failures as AlchemyApiError, so consumers can handle status/code/requestId/retryAfter uniformly: | +| [AlchemyError](/wallets/reference/common/classes/AlchemyError) | Dependency-free error root for SDK surfaces that must not pull in viem at runtime (the Data APIs core and the shared REST/JSON-RPC runtime). Message shape matches [BaseError](/wallets/reference/common/classes/BaseError) (short message, meta messages, docs link, details, version line), and `walk()` provides viem-compatible cause-chain traversal — but the class extends plain `Error`. | +| [AlchemyJsonRpcClient](/wallets/reference/common/classes/AlchemyJsonRpcClient) | A typed JSON-RPC client over the same HTTP engine as [AlchemyRestClient](/wallets/reference/common/classes/AlchemyRestClient): bounded retries on 429/5xx/network failures (honoring Retry-After), per-attempt timeouts, abort support, and a per-request X-Alchemy-Client-Request-Id surfaced on thrown errors. JSON-RPC-level errors (an `error` object on HTTP 200) are deterministic and are never retried; they throw [AlchemyApiError](/wallets/reference/common/classes/AlchemyApiError) carrying the RPC error code. | +| [AlchemyRestClient](/wallets/reference/common/classes/AlchemyRestClient) | A client for making requests to Alchemy's non-JSON-RPC endpoints, with typed routes/bodies/queries (via a RestRequestSchema), bounded retries with exponential backoff (429/5xx/network only, honoring Retry-After), per-attempt timeouts, abort support, and a per-request idempotency id sent as X-Alchemy-Client-Request-Id and surfaced on thrown errors. | +| [BaseError](/wallets/reference/common/classes/BaseError) | A custom error class that extends from `ViemBaseError`. This class allows for error messages to include links to relevant documentation based on provided `docsPath` and `docsSlug` parameters. This is based on on viem's BaseError type (obviously from the import and extend) we want the errors here to point to our docs if we supply a docsPath though | +| [ChainNotFoundError](/wallets/reference/common/classes/ChainNotFoundError) | Error class representing a "Chain Not Found" error, typically thrown when no chain is supplied to the client. | +| [ConnectionConfigError](/wallets/reference/common/classes/ConnectionConfigError) | Error class for connection configuration validation failures. | +| [FetchError](/wallets/reference/common/classes/FetchError) | Error class representing a "Fetch Error" error, typically thrown when a fetch request fails. | +| [InvalidRequestError](/wallets/reference/common/classes/InvalidRequestError) | This error is thrown when an invalid request is made. It extends the `BaseError` class. | +| [MethodUnsupportedError](/wallets/reference/common/classes/MethodUnsupportedError) | This error is thrown when an unknown method is called. It extends the `BaseError` class. | +| [ServerError](/wallets/reference/common/classes/ServerError) | Error class representing a "Server Error" error, typically thrown when a server request fails. | ## Interfaces @@ -65,47 +67,54 @@ MIT ## Type Aliases -| Type Alias | Description | -| :---------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [AlchemyApiErrorDetails](/wallets/reference/common/type-aliases/AlchemyApiErrorDetails) | Normalized failure metadata shared across REST and JSON-RPC channels. | -| [AlchemyConnectionConfig](/wallets/reference/common/type-aliases/AlchemyConnectionConfig) | TypeScript type derived from the schema for external consumption. This provides clean type inference without exposing Zod implementation details. | -| [AlchemyNetwork](/wallets/reference/common/type-aliases/AlchemyNetwork) | An Alchemy network identifier. Known slugs get autocomplete; arbitrary strings are accepted as an escape hatch so new networks work without an SDK release. | -| [AlchemyRestClientParams](/wallets/reference/common/type-aliases/AlchemyRestClientParams) | Parameters for creating an AlchemyRestClient instance. | -| [AlchemyTransport](/wallets/reference/common/type-aliases/AlchemyTransport) | - | -| [ExtractRpcMethod](/wallets/reference/common/type-aliases/ExtractRpcMethod) | - | -| [KnownAlchemyNetwork](/wallets/reference/common/type-aliases/KnownAlchemyNetwork) | Known Alchemy network slugs for autocomplete. | -| [NetworkInput](/wallets/reference/common/type-aliases/NetworkInput) | Any accepted network input: a viem Chain, an Alchemy network slug (e.g. "eth-mainnet"), or a CAIP-2 identifier (e.g. "eip155:1", "solana:mainnet"). | -| [Never](/wallets/reference/common/type-aliases/Never) | - | -| [QueryParams](/wallets/reference/common/type-aliases/QueryParams) | A query-params object; array values serialize as repeated keys. | -| [QueryValue](/wallets/reference/common/type-aliases/QueryValue) | Values the query serializer accepts (null/undefined entries are skipped). | -| [ResolvedNetwork](/wallets/reference/common/type-aliases/ResolvedNetwork) | A resolved network: the Alchemy slug used for URL construction and REST payloads, plus the numeric chain ID when one exists (EVM only). | -| [RestRequestFn](/wallets/reference/common/type-aliases/RestRequestFn) | - | -| [RestRequestOptions](/wallets/reference/common/type-aliases/RestRequestOptions) | Per-request runtime options; values override the client-level defaults. | -| [RestRequestParams](/wallets/reference/common/type-aliases/RestRequestParams) | - | -| [RestRequestSchema](/wallets/reference/common/type-aliases/RestRequestSchema) | - | +| Type Alias | Description | +| :---------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [AlchemyApiErrorDetails](/wallets/reference/common/type-aliases/AlchemyApiErrorDetails) | Normalized failure metadata shared across REST and JSON-RPC channels. | +| [AlchemyConnectionConfig](/wallets/reference/common/type-aliases/AlchemyConnectionConfig) | TypeScript type derived from the schema for external consumption. This provides clean type inference without exposing Zod implementation details. | +| [AlchemyJsonRpcClientParams](/wallets/reference/common/type-aliases/AlchemyJsonRpcClientParams) | Parameters for creating an AlchemyJsonRpcClient (URL is required: JSON-RPC is always endpoint-scoped). | +| [AlchemyNetwork](/wallets/reference/common/type-aliases/AlchemyNetwork) | An Alchemy network identifier. Known slugs get autocomplete; arbitrary strings are accepted as an escape hatch so new networks work without an SDK release. | +| [AlchemyRestClientParams](/wallets/reference/common/type-aliases/AlchemyRestClientParams) | Parameters for creating an AlchemyRestClient instance. | +| [AlchemyTransport](/wallets/reference/common/type-aliases/AlchemyTransport) | - | +| [ExtractRpcMethod](/wallets/reference/common/type-aliases/ExtractRpcMethod) | - | +| [JsonRpcRequestFn](/wallets/reference/common/type-aliases/JsonRpcRequestFn) | - | +| [JsonRpcSchema](/wallets/reference/common/type-aliases/JsonRpcSchema) | A typed JSON-RPC schema: a tuple of method entries. Structurally compatible with viem's RpcSchema shape, but defined here so consumers need no viem dependency. | +| [KnownAlchemyNetwork](/wallets/reference/common/type-aliases/KnownAlchemyNetwork) | Known Alchemy network slugs for autocomplete. | +| [NetworkInput](/wallets/reference/common/type-aliases/NetworkInput) | Any accepted network input: a viem Chain, an Alchemy network slug (e.g. "eth-mainnet"), or a CAIP-2 identifier (e.g. "eip155:1", "solana:mainnet"). | +| [Never](/wallets/reference/common/type-aliases/Never) | - | +| [QueryParams](/wallets/reference/common/type-aliases/QueryParams) | A query-params object; array values serialize as repeated keys. | +| [QueryValue](/wallets/reference/common/type-aliases/QueryValue) | Values the query serializer accepts (null/undefined entries are skipped). | +| [ResolvedNetwork](/wallets/reference/common/type-aliases/ResolvedNetwork) | A resolved network: the Alchemy slug used for URL construction and REST payloads, plus the numeric chain ID when one exists (EVM only). | +| [RestRequestFn](/wallets/reference/common/type-aliases/RestRequestFn) | - | +| [RestRequestOptions](/wallets/reference/common/type-aliases/RestRequestOptions) | Per-request runtime options; values override the client-level defaults. | +| [RestRequestParams](/wallets/reference/common/type-aliases/RestRequestParams) | - | +| [RestRequestSchema](/wallets/reference/common/type-aliases/RestRequestSchema) | - | ## Variables | Variable | Description | | :------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | [AlchemyConnectionConfigSchema](/wallets/reference/common/variables/AlchemyConnectionConfigSchema) | Main connection configuration allowing flexible combinations. Can specify URL, auth method, or both together. | +| [DEFAULT_RETRY_COUNT](/wallets/reference/common/variables/DEFAULT_RETRY_COUNT) | - | +| [DEFAULT_RETRY_DELAY_MS](/wallets/reference/common/variables/DEFAULT_RETRY_DELAY_MS) | - | +| [DEFAULT_TIMEOUT_MS](/wallets/reference/common/variables/DEFAULT_TIMEOUT_MS) | - | ## Functions -| Function | Description | -| :----------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [alchemyTransport](/wallets/reference/common/functions/alchemyTransport) | Creates an Alchemy HTTP transport for connecting to Alchemy's services. | -| [assertNever](/wallets/reference/common/functions/assertNever) | Asserts that a value is never. | -| [bigIntMax](/wallets/reference/common/functions/bigIntMax) | Returns the max bigint in a list of bigints | -| [bigIntMultiply](/wallets/reference/common/functions/bigIntMultiply) | Given a bigint and a number (which can be a float), returns the bigint value. Note: this function has loss and will round down to the nearest integer. | -| [composeSignals](/wallets/reference/common/functions/composeSignals) | Combines multiple abort signals into one that aborts when any input aborts. Uses AbortSignal.any where available, with an addEventListener fallback. | -| [getAlchemyRpcUrl](/wallets/reference/common/functions/getAlchemyRpcUrl) | Gets the Alchemy RPC base URL for a given chain ID. | -| [getSupportedChainIds](/wallets/reference/common/functions/getSupportedChainIds) | Gets all supported chain IDs from the registry. | -| [isAlchemyConnectionConfig](/wallets/reference/common/functions/isAlchemyConnectionConfig) | Type guard to check if a value is a valid Alchemy connection config. | -| [isAlchemyTransport](/wallets/reference/common/functions/isAlchemyTransport) | A type guard for the transport to determine if it is an Alchemy transport. Used in cases where we would like to do switching depending on the transport. | -| [isChainSupported](/wallets/reference/common/functions/isChainSupported) | Checks if a chain ID is supported by the Alchemy RPC registry. | -| [lowerAddress](/wallets/reference/common/functions/lowerAddress) | Lowercase an address | -| [raise](/wallets/reference/common/functions/raise) | Raises an error. | -| [resolveNetwork](/wallets/reference/common/functions/resolveNetwork) | Resolves any accepted network input — viem Chain, Alchemy network slug, or CAIP-2 identifier — to the Alchemy network slug (and chain ID when one exists). All three forms resolve against the same daikon-generated registry. | -| [sleep](/wallets/reference/common/functions/sleep) | Waits for a duration, rejecting immediately with the signal's reason if the signal aborts first. | -| [validateAlchemyConnectionConfig](/wallets/reference/common/functions/validateAlchemyConnectionConfig) | Validates an Alchemy connection configuration object. | +| Function | Description | +| :----------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [alchemyTransport](/wallets/reference/common/functions/alchemyTransport) | Creates an Alchemy HTTP transport for connecting to Alchemy's services. | +| [assertNever](/wallets/reference/common/functions/assertNever) | Asserts that a value is never. | +| [bigIntMax](/wallets/reference/common/functions/bigIntMax) | Returns the max bigint in a list of bigints | +| [bigIntMultiply](/wallets/reference/common/functions/bigIntMultiply) | Given a bigint and a number (which can be a float), returns the bigint value. Note: this function has loss and will round down to the nearest integer. | +| [composeSignals](/wallets/reference/common/functions/composeSignals) | Combines multiple abort signals into one that aborts when any input aborts. Uses AbortSignal.any where available, with an addEventListener fallback. | +| [getAlchemyRpcUrl](/wallets/reference/common/functions/getAlchemyRpcUrl) | Gets the Alchemy RPC base URL for a given chain ID. | +| [getSupportedChainIds](/wallets/reference/common/functions/getSupportedChainIds) | Gets all supported chain IDs from the registry. | +| [isAlchemyConnectionConfig](/wallets/reference/common/functions/isAlchemyConnectionConfig) | Type guard to check if a value is a valid Alchemy connection config. | +| [isAlchemyTransport](/wallets/reference/common/functions/isAlchemyTransport) | A type guard for the transport to determine if it is an Alchemy transport. Used in cases where we would like to do switching depending on the transport. | +| [isChainSupported](/wallets/reference/common/functions/isChainSupported) | Checks if a chain ID is supported by the Alchemy RPC registry. | +| [lowerAddress](/wallets/reference/common/functions/lowerAddress) | Lowercase an address | +| [raise](/wallets/reference/common/functions/raise) | Raises an error. | +| [redactUrlCredentials](/wallets/reference/common/functions/redactUrlCredentials) | Redacts credentials that can appear in URLs: keys embedded in `/v2/{key}` paths RPC paths (when a caller configured a key-bearing url) and apiKey query params. The header-auth paths never put keys in URLs; this protects configured-url escape hatches and any server-echoed text. | +| [resolveNetwork](/wallets/reference/common/functions/resolveNetwork) | Resolves any accepted network input — viem Chain, Alchemy network slug, or CAIP-2 identifier — to the Alchemy network slug (and chain ID when one exists). All three forms resolve against the same daikon-generated registry. | +| [sleep](/wallets/reference/common/functions/sleep) | Waits for a duration, rejecting immediately with the signal's reason if the signal aborts first. | +| [validateAlchemyConnectionConfig](/wallets/reference/common/functions/validateAlchemyConnectionConfig) | Validates an Alchemy connection configuration object. | diff --git a/docs/pages/reference/common/src/classes/AlchemyApiError.mdx b/docs/pages/reference/common/src/classes/AlchemyApiError.mdx index 74272e42c8..de0d4016b6 100644 --- a/docs/pages/reference/common/src/classes/AlchemyApiError.mdx +++ b/docs/pages/reference/common/src/classes/AlchemyApiError.mdx @@ -24,7 +24,7 @@ try { ... } catch (e) { ## Extends -- [`BaseError`](BaseError) +- [`AlchemyError`](AlchemyError) ## Extended by @@ -79,7 +79,7 @@ Creates a normalized API error. - Failure metadata plus BaseError options + Failure metadata plus AlchemyError options @@ -92,7 +92,7 @@ Creates a normalized API error. #### Overrides -[`BaseError`](BaseError).[`constructor`](BaseError#constructor) +[`AlchemyError`](AlchemyError).[`constructor`](AlchemyError#constructor) ## Properties @@ -125,6 +125,60 @@ Creates a normalized API error. + + +
`details?` + + + + `string` + + + + `undefined` + + + + ‐ + + + + + + `docsPath?` + + + + `string` + + + + `undefined` + + + + ‐ + + + + + + `metaMessages?` + + + + `string`\[] + + + + `undefined` + + + + ‐ + + + `name` @@ -179,6 +233,24 @@ Creates a normalized API error. + + + `shortMessage` + + + + `string` + + + + `undefined` + + + + ‐ + + + `status?` @@ -203,7 +275,7 @@ Creates a normalized API error. - `string` + `"5.0.3"` @@ -217,3 +289,56 @@ Creates a normalized API error. + +## Methods + +### walk() + +```ts +walk(fn?): null | Error; +``` + +Defined in: [packages/common/src/errors/AlchemyError.ts:86](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/AlchemyError.ts#L86) + +Walks the cause chain: with no predicate, returns the deepest error; +with a predicate, returns the first matching error or null (viem +BaseError-compatible semantics). + +#### Parameters + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `fn?` + + (`err`) => `boolean` + + Optional predicate over each error in the chain +
+ +#### Returns + +`null` | `Error` + +The deepest error, the first match, or null + +#### Inherited from + +[`AlchemyError`](AlchemyError).[`walk`](AlchemyError#walk) diff --git a/docs/pages/reference/common/src/classes/AlchemyError.mdx b/docs/pages/reference/common/src/classes/AlchemyError.mdx new file mode 100644 index 0000000000..946405faba --- /dev/null +++ b/docs/pages/reference/common/src/classes/AlchemyError.mdx @@ -0,0 +1,240 @@ +--- +title: AlchemyError +description: Dependency-free error root for SDK surfaces that must not pull in viem at runtime (the Data APIs core and the shared REST/JSON-RPC runtime). Message shape matches BaseError (short message, meta messages, docs link, details, version line), and `walk()` provides viem-compatible cause-chain traversal — but the class extends plain `Error`. Wallet-facing errors keep extending BaseError (viem-based); this root exists so `AlchemyApiError` and friends stay viem-free. +slug: wallets/reference/common/classes/AlchemyError +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +Defined in: [packages/common/src/errors/AlchemyError.ts:28](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/AlchemyError.ts#L28) + +Dependency-free error root for SDK surfaces that must not pull in viem at +runtime (the Data APIs core and the shared REST/JSON-RPC runtime). Message +shape matches [BaseError](BaseError) (short message, meta messages, docs link, +details, version line), and `walk()` provides viem-compatible cause-chain +traversal — but the class extends plain `Error`. + +Wallet-facing errors keep extending [BaseError](BaseError) (viem-based); this +root exists so `AlchemyApiError` and friends stay viem-free. + +## Extends + +- `Error` + +## Extended by + +- [`AlchemyApiError`](AlchemyApiError) + +## Constructors + +### Constructor + +```ts +new AlchemyError(shortMessage, args): AlchemyError; +``` + +Defined in: [packages/common/src/errors/AlchemyError.ts:42](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/AlchemyError.ts#L42) + +Creates an error with the SDK's standard message formatting. + +#### Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `shortMessage` + + `string` + + The headline error message +
+ `args` + + `AlchemyErrorParameters` + + Docs pointers, meta messages, and cause XOR details +
+ +#### Returns + +`AlchemyError` + +#### Overrides + +```ts +Error.constructor; +``` + +## Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDefault value
+ `details?` + + `string` + + `undefined` +
+ `docsPath?` + + `string` + + `undefined` +
+ `metaMessages?` + + `string`\[] + + `undefined` +
+ `name` + + `string` + + `"AlchemyError"` +
+ `shortMessage` + + `string` + + `undefined` +
+ `version` + + `"5.0.3"` + + `VERSION` +
+ +## Methods + +### walk() + +```ts +walk(fn?): null | Error; +``` + +Defined in: [packages/common/src/errors/AlchemyError.ts:86](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/AlchemyError.ts#L86) + +Walks the cause chain: with no predicate, returns the deepest error; +with a predicate, returns the first matching error or null (viem +BaseError-compatible semantics). + +#### Parameters + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `fn?` + + (`err`) => `boolean` + + Optional predicate over each error in the chain +
+ +#### Returns + +`null` | `Error` + +The deepest error, the first match, or null diff --git a/docs/pages/reference/common/src/classes/AlchemyJsonRpcClient.mdx b/docs/pages/reference/common/src/classes/AlchemyJsonRpcClient.mdx new file mode 100644 index 0000000000..1a390a3e5e --- /dev/null +++ b/docs/pages/reference/common/src/classes/AlchemyJsonRpcClient.mdx @@ -0,0 +1,126 @@ +--- +title: AlchemyJsonRpcClient +description: "A typed JSON-RPC client over the same HTTP engine as AlchemyRestClient: bounded retries on 429/5xx/network failures (honoring Retry-After), per-attempt timeouts, abort support, and a per-request X-Alchemy-Client-Request-Id surfaced on thrown errors. JSON-RPC-level errors (an `error` object on HTTP 200) are deterministic and are never retried; they throw AlchemyApiError carrying the RPC error code." +slug: wallets/reference/common/classes/AlchemyJsonRpcClient +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +Defined in: [packages/common/src/rest/jsonRpcClient.ts:49](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/jsonRpcClient.ts#L49) + +A typed JSON-RPC client over the same HTTP engine as +[AlchemyRestClient](AlchemyRestClient): bounded retries on 429/5xx/network failures +(honoring Retry-After), per-attempt timeouts, abort support, and a +per-request X-Alchemy-Client-Request-Id surfaced on thrown errors. +JSON-RPC-level errors (an `error` object on HTTP 200) are deterministic and +are never retried; they throw [AlchemyApiError](AlchemyApiError) carrying the RPC +error code. + +## Type Parameters + + + + + + + + + + + + + +
Type Parameter
+ `Schema` *extends* [`JsonRpcSchema`](../type-aliases/JsonRpcSchema) +
+ +## Constructors + +### Constructor + +```ts +new AlchemyJsonRpcClient(params): AlchemyJsonRpcClient; +``` + +Defined in: [packages/common/src/rest/jsonRpcClient.ts:62](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/jsonRpcClient.ts#L62) + +Creates a new instance of AlchemyJsonRpcClient. + +#### Parameters + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `params` + + [`AlchemyJsonRpcClientParams`](../type-aliases/AlchemyJsonRpcClientParams) + + Endpoint URL plus auth, headers, and retry/timeout defaults. +
+ +#### Returns + +`AlchemyJsonRpcClient`\<`Schema`> + +## Properties + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ `request` + + [`JsonRpcRequestFn`](../type-aliases/JsonRpcRequestFn)\<`Schema`> + + Sends a JSON-RPC request and returns its result. + + **Param** + + The request to send + + **Param** + + The JSON-RPC method name + + **Param** + + The positional params for the method + + **Param** + + Per-request signal and retry/timeout overrides +
diff --git a/docs/pages/reference/common/src/classes/AlchemyRestClient.mdx b/docs/pages/reference/common/src/classes/AlchemyRestClient.mdx index f6c7b4a980..edde64ed90 100644 --- a/docs/pages/reference/common/src/classes/AlchemyRestClient.mdx +++ b/docs/pages/reference/common/src/classes/AlchemyRestClient.mdx @@ -7,7 +7,7 @@ layout: reference {/* This file is auto-generated by TypeDoc. Do not edit manually. */} -Defined in: [packages/common/src/rest/restClient.ts:101](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/restClient.ts#L101) +Defined in: [packages/common/src/rest/restClient.ts:65](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/restClient.ts#L65) A client for making requests to Alchemy's non-JSON-RPC endpoints, with typed routes/bodies/queries (via a RestRequestSchema), bounded retries with @@ -41,7 +41,7 @@ as X-Alchemy-Client-Request-Id and surfaced on thrown errors. new AlchemyRestClient(params): AlchemyRestClient; ``` -Defined in: [packages/common/src/rest/restClient.ts:113](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/restClient.ts#L113) +Defined in: [packages/common/src/rest/restClient.ts:77](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/restClient.ts#L77) Creates a new instance of AlchemyRestClient. diff --git a/docs/pages/reference/common/src/classes/BaseError.mdx b/docs/pages/reference/common/src/classes/BaseError.mdx index 3b4dc5e5ce..6901313b0f 100644 --- a/docs/pages/reference/common/src/classes/BaseError.mdx +++ b/docs/pages/reference/common/src/classes/BaseError.mdx @@ -19,7 +19,6 @@ we want the errors here to point to our docs if we supply a docsPath though ## Extended by -- [`AlchemyApiError`](AlchemyApiError) - [`ChainNotFoundError`](ChainNotFoundError) - [`AccountNotFoundError`](AccountNotFoundError) - [`ConnectionConfigError`](ConnectionConfigError) diff --git a/docs/pages/reference/common/src/classes/FetchError.mdx b/docs/pages/reference/common/src/classes/FetchError.mdx index 6cf6e9b1da..582bd65e95 100644 --- a/docs/pages/reference/common/src/classes/FetchError.mdx +++ b/docs/pages/reference/common/src/classes/FetchError.mdx @@ -141,6 +141,60 @@ Initializes a new fetch error for a request that failed before a response. + + +
`details?` + + + + `string` + + + + `undefined` + + + + ‐ + + + + + + `docsPath?` + + + + `string` + + + + `undefined` + + + + ‐ + + + + + + `metaMessages?` + + + + `string`\[] + + + + `undefined` + + + + ‐ + + + `name` @@ -195,6 +249,24 @@ Initializes a new fetch error for a request that failed before a response. + + + `shortMessage` + + + + `string` + + + + `undefined` + + + + ‐ + + + `status?` @@ -219,7 +291,7 @@ Initializes a new fetch error for a request that failed before a response. - `string` + `"5.0.3"` @@ -233,3 +305,56 @@ Initializes a new fetch error for a request that failed before a response. + +## Methods + +### walk() + +```ts +walk(fn?): null | Error; +``` + +Defined in: [packages/common/src/errors/AlchemyError.ts:86](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/AlchemyError.ts#L86) + +Walks the cause chain: with no predicate, returns the deepest error; +with a predicate, returns the first matching error or null (viem +BaseError-compatible semantics). + +#### Parameters + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `fn?` + + (`err`) => `boolean` + + Optional predicate over each error in the chain +
+ +#### Returns + +`null` | `Error` + +The deepest error, the first match, or null + +#### Inherited from + +[`AlchemyApiError`](AlchemyApiError).[`walk`](AlchemyApiError#walk) diff --git a/docs/pages/reference/common/src/classes/ServerError.mdx b/docs/pages/reference/common/src/classes/ServerError.mdx index ae246e9c6e..e850afa827 100644 --- a/docs/pages/reference/common/src/classes/ServerError.mdx +++ b/docs/pages/reference/common/src/classes/ServerError.mdx @@ -141,6 +141,60 @@ Initializes a new server error for a failed HTTP response. + + +
`details?` + + + + `string` + + + + `undefined` + + + + ‐ + + + + + + `docsPath?` + + + + `string` + + + + `undefined` + + + + ‐ + + + + + + `metaMessages?` + + + + `string`\[] + + + + `undefined` + + + + ‐ + + + `name` @@ -195,6 +249,24 @@ Initializes a new server error for a failed HTTP response. + + + `shortMessage` + + + + `string` + + + + `undefined` + + + + ‐ + + + `status?` @@ -219,7 +291,7 @@ Initializes a new server error for a failed HTTP response. - `string` + `"5.0.3"` @@ -233,3 +305,56 @@ Initializes a new server error for a failed HTTP response. + +## Methods + +### walk() + +```ts +walk(fn?): null | Error; +``` + +Defined in: [packages/common/src/errors/AlchemyError.ts:86](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/errors/AlchemyError.ts#L86) + +Walks the cause chain: with no predicate, returns the deepest error; +with a predicate, returns the first matching error or null (viem +BaseError-compatible semantics). + +#### Parameters + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `fn?` + + (`err`) => `boolean` + + Optional predicate over each error in the chain +
+ +#### Returns + +`null` | `Error` + +The deepest error, the first match, or null + +#### Inherited from + +[`AlchemyApiError`](AlchemyApiError).[`walk`](AlchemyApiError#walk) diff --git a/docs/pages/reference/common/src/functions/redactUrlCredentials.mdx b/docs/pages/reference/common/src/functions/redactUrlCredentials.mdx new file mode 100644 index 0000000000..340fdf18f1 --- /dev/null +++ b/docs/pages/reference/common/src/functions/redactUrlCredentials.mdx @@ -0,0 +1,54 @@ +--- +title: redactUrlCredentials +description: Overview of the redactUrlCredentials function +slug: wallets/reference/common/functions/redactUrlCredentials +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +function redactUrlCredentials(text): string; +``` + +Defined in: [packages/common/src/utils/redact.ts:10](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/utils/redact.ts#L10) + +Redacts credentials that can appear in URLs: keys embedded in `/v2/{key}` paths +RPC paths (when a caller configured a key-bearing url) and apiKey query +params. The header-auth paths never put keys in URLs; this protects +configured-url escape hatches and any server-echoed text. + +## Parameters + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ `text` + + `string` + + Any error text that may embed a URL +
+ +## Returns + +`string` + +The text with credentials replaced by "\[redacted]" diff --git a/docs/pages/reference/common/src/type-aliases/AlchemyJsonRpcClientParams.mdx b/docs/pages/reference/common/src/type-aliases/AlchemyJsonRpcClientParams.mdx new file mode 100644 index 0000000000..6d2e43d01c --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/AlchemyJsonRpcClientParams.mdx @@ -0,0 +1,40 @@ +--- +title: AlchemyJsonRpcClientParams +description: "Parameters for creating an AlchemyJsonRpcClient (URL is required: JSON-RPC is always endpoint-scoped)." +slug: wallets/reference/common/type-aliases/AlchemyJsonRpcClientParams +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type AlchemyJsonRpcClientParams = AlchemyRestClientParams & object; +``` + +Defined in: [packages/common/src/rest/jsonRpcClient.ts:36](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/jsonRpcClient.ts#L36) + +Parameters for creating an AlchemyJsonRpcClient (URL is required: JSON-RPC is always endpoint-scoped). + +## Type Declaration + + + + + + + + + + + + + + + + + +
NameType
+ `url` + + `string` +
diff --git a/docs/pages/reference/common/src/type-aliases/AlchemyRestClientParams.mdx b/docs/pages/reference/common/src/type-aliases/AlchemyRestClientParams.mdx index f9368f5e45..3121d69bc1 100644 --- a/docs/pages/reference/common/src/type-aliases/AlchemyRestClientParams.mdx +++ b/docs/pages/reference/common/src/type-aliases/AlchemyRestClientParams.mdx @@ -11,7 +11,7 @@ layout: reference type AlchemyRestClientParams = object; ``` -Defined in: [packages/common/src/rest/restClient.ts:16](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/restClient.ts#L16) +Defined in: [packages/common/src/rest/restClient.ts:15](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/restClient.ts#L15) Parameters for creating an AlchemyRestClient instance. diff --git a/docs/pages/reference/common/src/type-aliases/JsonRpcRequestFn.mdx b/docs/pages/reference/common/src/type-aliases/JsonRpcRequestFn.mdx new file mode 100644 index 0000000000..05ddc1fb9e --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/JsonRpcRequestFn.mdx @@ -0,0 +1,120 @@ +--- +title: JsonRpcRequestFn +description: Overview of JsonRpcRequestFn +slug: wallets/reference/common/type-aliases/JsonRpcRequestFn +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type JsonRpcRequestFn = ( + args, + options?, +) => Promise< + Extract< + Schema[number], + { + Method: method; + } + >["ReturnType"] +>; +``` + +Defined in: [packages/common/src/rest/jsonRpcClient.ts:25](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/jsonRpcClient.ts#L25) + +## Type Parameters + + + + + + + + + + + + + +
Type Parameter
+ `Schema` *extends* [`JsonRpcSchema`](JsonRpcSchema) +
+ +## Type Parameters + + + + + + + + + + + + + +
Type Parameter
+ `method` *extends* `Schema`\[`number`]\[`"Method"`] +
+ +## Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ `args` + + \{ `method`: `method`; `params`: `Extract`\<`Schema`\[`number`], \{ `Method`: `method`; }>\[`"Parameters"`]; } +
+ `args.method` + + `method` +
+ `args.params?` + + `Extract`\<`Schema`\[`number`], \{ `Method`: `method`; }>\[`"Parameters"`] +
+ `options?` + + [`RestRequestOptions`](RestRequestOptions) +
+ +## Returns + +`Promise`\<`Extract`\<`Schema`\[`number`], \{ +`Method`: `method`; +}>\[`"ReturnType"`]> diff --git a/docs/pages/reference/common/src/type-aliases/JsonRpcSchema.mdx b/docs/pages/reference/common/src/type-aliases/JsonRpcSchema.mdx new file mode 100644 index 0000000000..4160d8a6f3 --- /dev/null +++ b/docs/pages/reference/common/src/type-aliases/JsonRpcSchema.mdx @@ -0,0 +1,18 @@ +--- +title: JsonRpcSchema +description: "A typed JSON-RPC schema: a tuple of method entries. Structurally compatible with viem's RpcSchema shape, but defined here so consumers need no viem dependency." +slug: wallets/reference/common/type-aliases/JsonRpcSchema +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +type JsonRpcSchema = readonly object[]; +``` + +Defined in: [packages/common/src/rest/jsonRpcClient.ts:19](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/jsonRpcClient.ts#L19) + +A typed JSON-RPC schema: a tuple of method entries. Structurally compatible +with viem's RpcSchema shape, but defined here so consumers need no viem +dependency. diff --git a/docs/pages/reference/common/src/type-aliases/QueryParams.mdx b/docs/pages/reference/common/src/type-aliases/QueryParams.mdx index 1fbe615516..15b9712e0c 100644 --- a/docs/pages/reference/common/src/type-aliases/QueryParams.mdx +++ b/docs/pages/reference/common/src/type-aliases/QueryParams.mdx @@ -11,6 +11,6 @@ layout: reference type QueryParams = Record; ``` -Defined in: [packages/common/src/rest/types.ts:7](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L7) +Defined in: [packages/common/src/rest/types.ts:8](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L8) A query-params object; array values serialize as repeated keys. diff --git a/docs/pages/reference/common/src/type-aliases/QueryValue.mdx b/docs/pages/reference/common/src/type-aliases/QueryValue.mdx index f4a7b99373..077bc4b20d 100644 --- a/docs/pages/reference/common/src/type-aliases/QueryValue.mdx +++ b/docs/pages/reference/common/src/type-aliases/QueryValue.mdx @@ -11,6 +11,6 @@ layout: reference type QueryValue = string | number | boolean | null | undefined; ``` -Defined in: [packages/common/src/rest/types.ts:4](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L4) +Defined in: [packages/common/src/rest/types.ts:5](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L5) Values the query serializer accepts (null/undefined entries are skipped). diff --git a/docs/pages/reference/common/src/type-aliases/RestRequestFn.mdx b/docs/pages/reference/common/src/type-aliases/RestRequestFn.mdx index 628b472429..d9c6ce4d65 100644 --- a/docs/pages/reference/common/src/type-aliases/RestRequestFn.mdx +++ b/docs/pages/reference/common/src/type-aliases/RestRequestFn.mdx @@ -13,7 +13,7 @@ type RestRequestFn = <_parameters, _returnType>( ) => Promise<_returnType>; ``` -Defined in: [packages/common/src/rest/types.ts:69](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L69) +Defined in: [packages/common/src/rest/types.ts:70](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L70) ## Type Parameters diff --git a/docs/pages/reference/common/src/type-aliases/RestRequestOptions.mdx b/docs/pages/reference/common/src/type-aliases/RestRequestOptions.mdx index 9684d008a5..d353dff493 100644 --- a/docs/pages/reference/common/src/type-aliases/RestRequestOptions.mdx +++ b/docs/pages/reference/common/src/type-aliases/RestRequestOptions.mdx @@ -11,7 +11,7 @@ layout: reference type RestRequestOptions = object; ``` -Defined in: [packages/common/src/rest/types.ts:18](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L18) +Defined in: [packages/common/src/rest/types.ts:19](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L19) Per-request runtime options; values override the client-level defaults. diff --git a/docs/pages/reference/common/src/type-aliases/RestRequestParams.mdx b/docs/pages/reference/common/src/type-aliases/RestRequestParams.mdx index dd0ae54a18..dcacb61adc 100644 --- a/docs/pages/reference/common/src/type-aliases/RestRequestParams.mdx +++ b/docs/pages/reference/common/src/type-aliases/RestRequestParams.mdx @@ -11,7 +11,7 @@ layout: reference type RestRequestParams = Schema extends RestRequestSchema ? { [K in keyof Schema]: Prettify<{ method: Schema[K] extends Schema[number] ? Schema[K]["Method"] : never; route: Schema[K] extends Schema[number] ? Schema[K]["Route"] : never } & (Schema[K] extends Schema[number] ? Schema[K]["Body"] extends undefined ? { body?: undefined } : { body: (...)[(...)]["Body"] } : never) & (Schema[K] extends Schema[number] ? EntryQuery extends undefined ? { query?: undefined } : undefined extends EntryQuery<(...)[(...)]> ? { query?: EntryQuery<(...)> } : { query: EntryQuery<(...)> } : never) & RestRequestOptions> }[number] : object & RestRequestOptions; ``` -Defined in: [packages/common/src/rest/types.ts:37](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L37) +Defined in: [packages/common/src/rest/types.ts:38](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L38) ## Type Parameters diff --git a/docs/pages/reference/common/src/type-aliases/RestRequestSchema.mdx b/docs/pages/reference/common/src/type-aliases/RestRequestSchema.mdx index a536fa02f6..e73f88f102 100644 --- a/docs/pages/reference/common/src/type-aliases/RestRequestSchema.mdx +++ b/docs/pages/reference/common/src/type-aliases/RestRequestSchema.mdx @@ -11,4 +11,4 @@ layout: reference type RestRequestSchema = readonly object[]; ``` -Defined in: [packages/common/src/rest/types.ts:9](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L9) +Defined in: [packages/common/src/rest/types.ts:10](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/types.ts#L10) diff --git a/docs/pages/reference/common/src/variables/DEFAULT_RETRY_COUNT.mdx b/docs/pages/reference/common/src/variables/DEFAULT_RETRY_COUNT.mdx new file mode 100644 index 0000000000..6e202601f6 --- /dev/null +++ b/docs/pages/reference/common/src/variables/DEFAULT_RETRY_COUNT.mdx @@ -0,0 +1,14 @@ +--- +title: DEFAULT_RETRY_COUNT +description: Overview of DEFAULT_RETRY_COUNT +slug: wallets/reference/common/variables/DEFAULT_RETRY_COUNT +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +const DEFAULT_RETRY_COUNT: 3 = 3; +``` + +Defined in: [packages/common/src/rest/restClient.ts:8](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/restClient.ts#L8) diff --git a/docs/pages/reference/common/src/variables/DEFAULT_RETRY_DELAY_MS.mdx b/docs/pages/reference/common/src/variables/DEFAULT_RETRY_DELAY_MS.mdx new file mode 100644 index 0000000000..fc16631dcb --- /dev/null +++ b/docs/pages/reference/common/src/variables/DEFAULT_RETRY_DELAY_MS.mdx @@ -0,0 +1,14 @@ +--- +title: DEFAULT_RETRY_DELAY_MS +description: Overview of DEFAULT_RETRY_DELAY_MS +slug: wallets/reference/common/variables/DEFAULT_RETRY_DELAY_MS +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +const DEFAULT_RETRY_DELAY_MS: 150 = 150; +``` + +Defined in: [packages/common/src/rest/restClient.ts:9](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/restClient.ts#L9) diff --git a/docs/pages/reference/common/src/variables/DEFAULT_TIMEOUT_MS.mdx b/docs/pages/reference/common/src/variables/DEFAULT_TIMEOUT_MS.mdx new file mode 100644 index 0000000000..6fbbb796a2 --- /dev/null +++ b/docs/pages/reference/common/src/variables/DEFAULT_TIMEOUT_MS.mdx @@ -0,0 +1,14 @@ +--- +title: DEFAULT_TIMEOUT_MS +description: Overview of DEFAULT_TIMEOUT_MS +slug: wallets/reference/common/variables/DEFAULT_TIMEOUT_MS +layout: reference +--- + +{/* This file is auto-generated by TypeDoc. Do not edit manually. */} + +```ts +const DEFAULT_TIMEOUT_MS: 10000 = 10_000; +``` + +Defined in: [packages/common/src/rest/restClient.ts:10](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/common/src/rest/restClient.ts#L10) diff --git a/docs/pages/reference/data-apis/src/README.mdx b/docs/pages/reference/data-apis/src/README.mdx index ce454ee350..0afc44b2c6 100644 --- a/docs/pages/reference/data-apis/src/README.mdx +++ b/docs/pages/reference/data-apis/src/README.mdx @@ -7,60 +7,145 @@ layout: reference {/* This file is auto-generated by TypeDoc. Do not edit manually. */} -A vertical-slice prototype of the Data APIs SDK, built to prove the architecture -before scaling to the full v1 surface (Portfolio, Prices, NFT, Token, Transfers). +Alchemy's Data APIs — Portfolio, Prices, NFT, Token, and Transfers — as typed, +**dependency-free** actions. No chain library required: the package's only +dependency is `@alchemy/common`, and nothing EVM- or ecosystem-specific sits +in the foundation. **Currently published under the `alpha` dist-tag.** -## What this proves +```bash +npm install @alchemy/data-apis@alpha +``` + +## Quickstart -One method per seam, not full coverage: +```ts +import { createDataClient } from "@alchemy/data-apis"; -| Method | Channel | What it demonstrates | -| ------------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| `portfolio.getTokensByAddress` | REST → global `api.g.alchemy.com/data/v1` | Multi-network request bodies via `AlchemyRestClient`; networks are payload, the client's chain is not involved | -| `nft.getNftsForOwner` | REST → `{network}.g.alchemy.com/nft/v3` | Network-scoped endpoint resolution with per-request `network` override falling back to the client default | -| `transfers.getAssetTransfers` | JSON-RPC → `AlchemyTransport` | Plain viem action; network override derives a transport instance from `client.transport.config` | +const data = createDataClient({ + apiKey: process.env.ALCHEMY_API_KEY, + network: "eth-mainnet", // optional — see Networks below +}); -Plus the two entry points: +const nfts = await data.nft.getNftsForOwner({ owner: "0x..." }); +``` + +Every action is also individually importable +(`import { getNftsForOwner } from "@alchemy/data-apis"`) for tree-shaking and +composability, taking `(client, params)`. + +## Namespaces ```ts -// Data-only developers (no viem knowledge required) -const data = createDataClient({ apiKey, network: "eth-mainnet" }); - -// Developers already on a viem client with an Alchemy transport -const client = createClient({ - chain: mainnet, - transport: alchemyTransport({ apiKey }), -}).extend(dataActions); +// Portfolio — multi-network queries; networks travel per request. +// No client-level network needed at all. +const tokens = await data.portfolio.getTokensByAddress({ + addresses: [ + { + address: "0x...", + networks: ["eth-mainnet", "base-mainnet", "solana-mainnet"], + }, + ], +}); +// also: getTokenBalancesByAddress, getNftsByAddress, getNftContractsByAddress + +// Prices — chain-agnostic or address+network +const prices = await data.prices.getTokenPricesBySymbol({ + symbols: ["ETH", "USDC"], +}); +// also: getTokenPricesByAddress, getHistoricalTokenPrices + +// NFT — full v3 read surface (21 methods) +const owned = await data.nft.getNftsForOwner({ owner: "0x..." }); + +// Token — balances, metadata, allowance (JSON-RPC) +const balances = await data.token.getTokenBalances({ address: "0x..." }); + +// Transfers — historical transfer queries (JSON-RPC) +const transfers = await data.transfers.getAssetTransfers({ + category: ["erc20"], +}); ``` -Network inputs accept all three formats everywhere, resolved by -`resolveNetwork()` in `@alchemy/common`: a viem `Chain`, an Alchemy slug -(`"eth-mainnet"`), or CAIP-2 (`"eip155:1"`, `"solana:mainnet"`). The slug ↔ -chain-ID mapping is derived from the existing daikon-generated -`ALCHEMY_RPC_MAPPING` — no second registry. +### Networks + +Network inputs are strings, both ecosystem-neutral: Alchemy slugs +(`"eth-mainnet"`, exactly as the dashboard and API responses use) or CAIP-2 +ids (`"eip155:1"`, `"solana:mainnet"`). Holding a viem chain object? The +bridge is one expression: `` `eip155:${chain.id}` ``. + +The client-level `network` is optional and only a default: multi-network +methods (Portfolio, Prices-by-address) take networks in the request itself, +and every single-network method accepts a per-request `network` override that +wins over the default. Registry-unknown slugs pass through as an escape +hatch, so newly launched networks work without an SDK release. + +### Proxy / frontend usage + +Pass `url` to route traffic through your backend so no API key ships +client-side: `createDataClient({ url: "https://your-backend/alchemy" })`. +`jwt` auth is also supported. + +### Pagination + +Paginated methods have `*Pages` companions returning async generators that +manage cursors for you (and refuse to loop forever on repeated cursors): + +```ts +for await (const page of data.nft.getNftsForOwnerPages( + { owner: "0x..." }, + { maxPages: 10, signal: controller.signal }, +)) { + for (const nft of page.ownedNfts ?? []) { + // ... + } +} +``` + +### Errors + +Both channels (REST and JSON-RPC) normalize failures into `AlchemyApiError` +from `@alchemy/common`, carrying `status`, `code`, `requestId` (the +client-generated `X-Alchemy-Client-Request-Id`), and `retryAfter` when known. +Requests retry 429/5xx/network failures with exponential backoff (honoring +`Retry-After`) and time out per attempt; pass an `AbortSignal` via the +per-request options to cancel. JSON-RPC-level errors are never retried. + +## Ecosystem adapters + +The core is deliberately chain-library-free (see the "Data SDK Foundation" +decision doc). Adapters ship as demand warrants, post-v1 — first in line is +`@alchemy/data-apis/viem`, a `dataActions` decorator for existing viem and +wallet-apis clients (`client.extend(dataActions)`). Its implementation is +already parked and tested at `src/viem/`; it is packaging work away from +shipping. Adapters depend on their ecosystem's library; the core never does. ## Generated internals Param/result types are generated from the docs repo's bundled OpenAPI/OpenRPC -specs by `@alchemy/api-codegen` (see that package's README for the -snapshot/generate pipeline). `src/generated/` is committed, machine-owned, and -never re-exported directly: the public types in `src/types.ts` are -hand-reviewed aliases, and `codegen.manifest.ts` maps spec operations to the -generated surface — referencing a renamed/removed spec operation fails -`pnpm generate` loudly. +specs by `api-codegen` (repo root; see its README for the snapshot/generate +pipeline). `src/generated/` is committed, machine-owned, and never +re-exported directly: the public types in `src/types.ts` are hand-reviewed +aliases, and `codegen.manifest.ts` maps spec operations to the generated +surface — referencing a renamed/removed spec operation fails `pnpm generate` +loudly. + +## Releasing -## Companion changes in @alchemy/common +While in alpha this package is deliberately **not** in `lerna.json`'s +fixed-version publish set, so the regular release workflow can't ship it as +`latest`. To publish an alpha: -- `networks/networkRegistry.ts`: `resolveNetwork` + network types (slug map - derived from the registry URLs; to be emitted by ws-tools properly) -- `AlchemyRestClient` is now exported (was written for signer v5 but unexported) +```bash +pnpm --filter @alchemy/data-apis build +cd packages/data-apis && pnpm publish --tag alpha +``` -## Deliberately out of scope (tracked in the data SDK scope plan) +Graduation checklist (when the team blesses a stable release): -- Rest client hardening: retries, timeouts, request-id, first-class query params -- Pagination iterators, error normalization, the SDK manifest, remaining methods -- ws-tools generator change to emit `{ slug, chainId, caip2 }` entries + - the `KnownAlchemyNetwork` union +1. Bump `version` to the shared monorepo version (drop the `-alpha.N` suffix). +2. Add `"packages/data-apis"` to `lerna.json`'s `packages` array. +3. Remove `publishConfig.tag`. +4. Announce the surface as semver-stable. ## Interfaces @@ -78,7 +163,7 @@ generated surface — referencing a renamed/removed spec operation fails | [AssetTransfer](/wallets/reference/data-apis/type-aliases/AssetTransfer) | - | | [ComputeRarityParams](/wallets/reference/data-apis/type-aliases/ComputeRarityParams) | - | | [ComputeRarityResult](/wallets/reference/data-apis/type-aliases/ComputeRarityResult) | - | -| [DataActions](/wallets/reference/data-apis/type-aliases/DataActions) | The namespaced Data API actions attached by the [dataActions](/wallets/reference/data-apis/functions/dataActions) decorator. | +| [DataActions](/wallets/reference/data-apis/type-aliases/DataActions) | The namespaced Data API actions available on a Data API client. | | [DataRpcSchema](/wallets/reference/data-apis/type-aliases/DataRpcSchema) | viem RpcSchema entries for the Data JSON-RPC methods. Attach to a client to get typed `client.request({ method: "alchemy_getAssetTransfers", ... })`. | | [GetAssetTransfersParams](/wallets/reference/data-apis/type-aliases/GetAssetTransfersParams) | Generated RPC params plus the SDK's network override. | | [GetAssetTransfersResult](/wallets/reference/data-apis/type-aliases/GetAssetTransfersResult) | The spec result is `oneOf: ["Not Found (null)" string, object]`; the string branch is a docs-spec artifact the SDK deliberately does not surface, so it is collapsed away here (and in [DataRpcSchema](/wallets/reference/data-apis/type-aliases/DataRpcSchema)). | @@ -160,9 +245,8 @@ generated surface — referencing a renamed/removed spec operation fails | Function | Description | | :---------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [computeRarity](/wallets/reference/data-apis/functions/computeRarity) | Computes attribute rarity for a specific NFT. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | -| [createDataClient](/wallets/reference/data-apis/functions/createDataClient) | Creates a Data API client. This is a convenience wrapper over `createClient` + `alchemyTransport` + the [dataActions](/wallets/reference/data-apis/functions/dataActions) decorator — developers already holding a viem client with an Alchemy transport can use `client.extend(dataActions)` instead and get the identical behavior. | -| [dataActions](/wallets/reference/data-apis/functions/dataActions) | A viem client decorator that attaches the Data API actions, grouped by namespace, to any client configured with an Alchemy transport. | -| [getAssetTransfers](/wallets/reference/data-apis/functions/getAssetTransfers) | Fetches historical asset transfers (external, internal, token) for the resolved network via the `alchemy_getAssetTransfers` JSON-RPC method. | +| [createDataClient](/wallets/reference/data-apis/functions/createDataClient) | Creates a Data API client: a plain, dependency-free container holding the connection config (apiKey/jwt/proxy url, retry and timeout defaults) and an optional default network, with the Data API actions attached by namespace. | +| [getAssetTransfers](/wallets/reference/data-apis/functions/getAssetTransfers) | Fetches historical asset transfers (external, internal, token) for the resolved network via the `alchemy_getAssetTransfers` JSON-RPC method. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured default applies. | | [getAssetTransfersPages](/wallets/reference/data-apis/functions/getAssetTransfersPages) | Auto-paginating companion to [getAssetTransfers](/wallets/reference/data-apis/functions/getAssetTransfers): yields whole pages until the cursor is exhausted (or `maxPages` is hit), guarding against repeated cursors. Note: JSON-RPC requests run through the client's viem transport, which owns the fetch — the abort signal is honored between pages, not mid-request. | | [getCollectionMetadata](/wallets/reference/data-apis/functions/getCollectionMetadata) | Fetches metadata for an NFT collection by slug. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | | [getCollectionsForOwner](/wallets/reference/data-apis/functions/getCollectionsForOwner) | Fetches all NFT collections an address holds tokens from. The network is resolved per request: an explicit `network` param wins, otherwise the client's configured network/chain applies. | diff --git a/docs/pages/reference/data-apis/src/functions/createDataClient.mdx b/docs/pages/reference/data-apis/src/functions/createDataClient.mdx index 5b239f6298..24c38e0db7 100644 --- a/docs/pages/reference/data-apis/src/functions/createDataClient.mdx +++ b/docs/pages/reference/data-apis/src/functions/createDataClient.mdx @@ -11,12 +11,11 @@ layout: reference function createDataClient(options): AlchemyDataClient; ``` -Defined in: [packages/data-apis/src/client.ts:49](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/client.ts#L49) +Defined in: [packages/data-apis/src/client.ts:43](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/client.ts#L43) -Creates a Data API client. This is a convenience wrapper over -`createClient` + `alchemyTransport` + the [dataActions](dataActions) decorator — -developers already holding a viem client with an Alchemy transport can use -`client.extend(dataActions)` instead and get the identical behavior. +Creates a Data API client: a plain, dependency-free container holding the +connection config (apiKey/jwt/proxy url, retry and timeout defaults) and an +optional default network, with the Data API actions attached by namespace. ## Example @@ -25,7 +24,7 @@ import { createDataClient } from "@alchemy/data-apis"; const data = createDataClient({ apiKey: process.env.ALCHEMY_API_KEY, - network: "eth-mainnet", // or `mainnet` from viem/chains, or "eip155:1" + network: "eth-mainnet", // or "eip155:1"; optional for portfolio/prices }); const nfts = await data.nft.getNftsForOwner({ owner: "0x..." }); @@ -53,7 +52,7 @@ const nfts = await data.nft.getNftsForOwner({ owner: "0x..." }); - Auth (apiKey/jwt/proxy url) and default network + Auth (apiKey/jwt/proxy url), retry/timeout defaults, and default network @@ -64,4 +63,4 @@ const nfts = await data.nft.getNftsForOwner({ owner: "0x..." }); [`AlchemyDataClient`](../type-aliases/AlchemyDataClient) -A viem client extended with the Data API actions +The Data API client diff --git a/docs/pages/reference/data-apis/src/functions/dataActions.mdx b/docs/pages/reference/data-apis/src/functions/dataActions.mdx deleted file mode 100644 index 2e331ef7f0..0000000000 --- a/docs/pages/reference/data-apis/src/functions/dataActions.mdx +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: dataActions -description: Overview of the dataActions function -slug: wallets/reference/data-apis/functions/dataActions -layout: reference ---- - -{/* This file is auto-generated by TypeDoc. Do not edit manually. */} - -```ts -function dataActions(client): DataActions; -``` - -Defined in: [packages/data-apis/src/decorator.ts:236](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/decorator.ts#L236) - -A viem client decorator that attaches the Data API actions, grouped by -namespace, to any client configured with an Alchemy transport. - -## Example - -```ts -import { createClient } from "viem"; -import { mainnet } from "viem/chains"; -import { alchemyTransport } from "@alchemy/common"; -import { dataActions } from "@alchemy/data-apis"; - -const client = createClient({ - chain: mainnet, - transport: alchemyTransport({ apiKey: "..." }), -}).extend(dataActions); - -const nfts = await client.nft.getNftsForOwner({ owner: "0x..." }); -``` - -## Parameters - - - - - - - - - - - - - - - - - - - - -
ParameterTypeDescription
- `client` - - `DataClient` - - The client to decorate -
- -## Returns - -[`DataActions`](../type-aliases/DataActions) - -The namespaced Data API actions diff --git a/docs/pages/reference/data-apis/src/functions/getAssetTransfers.mdx b/docs/pages/reference/data-apis/src/functions/getAssetTransfers.mdx index 0827b8b112..b62c7c38d5 100644 --- a/docs/pages/reference/data-apis/src/functions/getAssetTransfers.mdx +++ b/docs/pages/reference/data-apis/src/functions/getAssetTransfers.mdx @@ -17,15 +17,12 @@ function getAssetTransfers( }>; ``` -Defined in: [packages/data-apis/src/actions/transfers/getAssetTransfers.ts:24](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/transfers/getAssetTransfers.ts#L24) +Defined in: [packages/data-apis/src/actions/transfers/getAssetTransfers.ts:17](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/transfers/getAssetTransfers.ts#L17) Fetches historical asset transfers (external, internal, token) for the -resolved network via the `alchemy_getAssetTransfers` JSON-RPC method. - -Without a `network` override this is a plain viem action over the client's -Alchemy transport. With an override, a transport instance is derived from -the client's transport config and pointed at the override network's RPC URL -— the same mechanism the transport's tracing support uses. +resolved network via the `alchemy_getAssetTransfers` JSON-RPC method. The +network is resolved per request: an explicit `network` param wins, +otherwise the client's configured default applies. ## Parameters @@ -49,7 +46,7 @@ the client's transport config and pointed at the override network's RPC URL - A client configured with an Alchemy transport + A Data API client diff --git a/docs/pages/reference/data-apis/src/functions/getTokenAllowance.mdx b/docs/pages/reference/data-apis/src/functions/getTokenAllowance.mdx index 10ce833065..479f5aa017 100644 --- a/docs/pages/reference/data-apis/src/functions/getTokenAllowance.mdx +++ b/docs/pages/reference/data-apis/src/functions/getTokenAllowance.mdx @@ -11,7 +11,7 @@ layout: reference function getTokenAllowance(client, params): Promise; ``` -Defined in: [packages/data-apis/src/actions/token/getTokenAllowance.ts:21](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/token/getTokenAllowance.ts#L21) +Defined in: [packages/data-apis/src/actions/token/getTokenAllowance.ts:17](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/token/getTokenAllowance.ts#L17) Fetches the ERC-20 allowance a spender has from an owner via the `alchemy_getTokenAllowance` JSON-RPC method. Without a `network` override diff --git a/docs/pages/reference/data-apis/src/functions/getTokenBalances.mdx b/docs/pages/reference/data-apis/src/functions/getTokenBalances.mdx index 56c890a6d7..fc29f0648e 100644 --- a/docs/pages/reference/data-apis/src/functions/getTokenBalances.mdx +++ b/docs/pages/reference/data-apis/src/functions/getTokenBalances.mdx @@ -14,7 +14,7 @@ function getTokenBalances( ): Promise; ``` -Defined in: [packages/data-apis/src/actions/token/getTokenBalances.ts:22](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/token/getTokenBalances.ts#L22) +Defined in: [packages/data-apis/src/actions/token/getTokenBalances.ts:18](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/token/getTokenBalances.ts#L18) Fetches ERC-20 (and native) token balances for an address via the `alchemy_getTokenBalances` JSON-RPC method. Without a `network` override diff --git a/docs/pages/reference/data-apis/src/functions/getTokenMetadata.mdx b/docs/pages/reference/data-apis/src/functions/getTokenMetadata.mdx index f414681d29..a019ef0d27 100644 --- a/docs/pages/reference/data-apis/src/functions/getTokenMetadata.mdx +++ b/docs/pages/reference/data-apis/src/functions/getTokenMetadata.mdx @@ -14,7 +14,7 @@ function getTokenMetadata( ): Promise; ``` -Defined in: [packages/data-apis/src/actions/token/getTokenMetadata.ts:21](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/token/getTokenMetadata.ts#L21) +Defined in: [packages/data-apis/src/actions/token/getTokenMetadata.ts:17](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/actions/token/getTokenMetadata.ts#L17) Fetches metadata (name, symbol, decimals, logo) for a token contract via the `alchemy_getTokenMetadata` JSON-RPC method. Without a `network` diff --git a/docs/pages/reference/data-apis/src/interfaces/PortfolioAddressEntry.mdx b/docs/pages/reference/data-apis/src/interfaces/PortfolioAddressEntry.mdx index 6fb5e366d5..9b1d15006a 100644 --- a/docs/pages/reference/data-apis/src/interfaces/PortfolioAddressEntry.mdx +++ b/docs/pages/reference/data-apis/src/interfaces/PortfolioAddressEntry.mdx @@ -43,11 +43,11 @@ An address paired with the networks to query it on. - `NetworkInput`\[] + `AlchemyNetwork`\[] - Networks to query; accepts viem Chains, Alchemy slugs, or CAIP-2 ids. + Networks to query; accepts Alchemy slugs or CAIP-2 ids. diff --git a/docs/pages/reference/data-apis/src/interfaces/PriceAddressEntry.mdx b/docs/pages/reference/data-apis/src/interfaces/PriceAddressEntry.mdx index 12ac07a82d..8efb398e9d 100644 --- a/docs/pages/reference/data-apis/src/interfaces/PriceAddressEntry.mdx +++ b/docs/pages/reference/data-apis/src/interfaces/PriceAddressEntry.mdx @@ -43,11 +43,11 @@ A token address paired with the network it lives on. - `NetworkInput` + `AlchemyNetwork` - Accepts a viem Chain, an Alchemy slug, or a CAIP-2 id. + Accepts an Alchemy slug or a CAIP-2 id. diff --git a/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClient.mdx b/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClient.mdx index 33421c4134..dfa97d5541 100644 --- a/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClient.mdx +++ b/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClient.mdx @@ -8,8 +8,7 @@ layout: reference {/* This file is auto-generated by TypeDoc. Do not edit manually. */} ```ts -type AlchemyDataClient = Client & - DataActions; +type AlchemyDataClient = DataClient & DataActions; ``` -Defined in: [packages/data-apis/src/client.ts:25](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/client.ts#L25) +Defined in: [packages/data-apis/src/client.ts:21](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/client.ts#L21) diff --git a/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClientOptions.mdx b/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClientOptions.mdx index 3ae36fac92..233cde6db6 100644 --- a/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClientOptions.mdx +++ b/docs/pages/reference/data-apis/src/type-aliases/AlchemyDataClientOptions.mdx @@ -8,14 +8,10 @@ layout: reference {/* This file is auto-generated by TypeDoc. Do not edit manually. */} ```ts -type AlchemyDataClientOptions = Pick< - AlchemyTransportConfig, - "apiKey" | "jwt" | "url" | "fetchOptions" -> & - object; +type AlchemyDataClientOptions = AlchemyRestClientParams & object; ``` -Defined in: [packages/data-apis/src/client.ts:12](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/client.ts#L12) +Defined in: [packages/data-apis/src/client.ts:9](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/client.ts#L9) ## Type Declaration @@ -35,14 +31,16 @@ Defined in: [packages/data-apis/src/client.ts:12](https://github.com/alchemyplat - `NetworkInput` + `AlchemyNetwork` Default network for network-scoped calls (NFT, Token, Transfers). - Accepts a viem Chain, an Alchemy slug ("eth-mainnet"), or CAIP-2 - ("eip155:1"). Multi-network calls (Portfolio) take explicit networks - per request. + Accepts an Alchemy slug ("eth-mainnet") or a CAIP-2 id ("eip155:1", + "solana:mainnet"). Optional: multi-network calls (Portfolio, Prices) + take networks per request, and single-network calls accept a + per-request `network` override. Holding a viem chain? The bridge is + `eip155:${chain.id}`. diff --git a/docs/pages/reference/data-apis/src/type-aliases/DataActions.mdx b/docs/pages/reference/data-apis/src/type-aliases/DataActions.mdx index 2575659a81..fa46d9da67 100644 --- a/docs/pages/reference/data-apis/src/type-aliases/DataActions.mdx +++ b/docs/pages/reference/data-apis/src/type-aliases/DataActions.mdx @@ -1,6 +1,6 @@ --- title: DataActions -description: The namespaced Data API actions attached by the dataActions decorator. +description: The namespaced Data API actions available on a Data API client. slug: wallets/reference/data-apis/type-aliases/DataActions layout: reference --- @@ -13,7 +13,7 @@ type DataActions = object; Defined in: [packages/data-apis/src/decorator.ts:64](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/decorator.ts#L64) -The namespaced Data API actions attached by the [dataActions](../functions/dataActions) decorator. +The namespaced Data API actions available on a Data API client. ## Properties diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetAssetTransfersParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetAssetTransfersParams.mdx index 4adef767a7..aa48b00493 100644 --- a/docs/pages/reference/data-apis/src/type-aliases/GetAssetTransfersParams.mdx +++ b/docs/pages/reference/data-apis/src/type-aliases/GetAssetTransfersParams.mdx @@ -33,7 +33,7 @@ Generated RPC params plus the SDK's network override. - `NetworkInput` + `AlchemyNetwork` diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchParams.mdx index 102e4c55d7..3ea1bbccc9 100644 --- a/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchParams.mdx +++ b/docs/pages/reference/data-apis/src/type-aliases/GetContractMetadataBatchParams.mdx @@ -31,7 +31,7 @@ Defined in: [packages/data-apis/src/types.ts:194](https://github.com/alchemyplat - `NetworkInput` + `AlchemyNetwork` diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesParams.mdx index f0b4b5cf45..dd05ca2c05 100644 --- a/docs/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesParams.mdx +++ b/docs/pages/reference/data-apis/src/type-aliases/GetHistoricalTokenPricesParams.mdx @@ -9,7 +9,7 @@ layout: reference ```ts type GetHistoricalTokenPricesParams = - WithNetworkInput; + WithAlchemyNetwork; ``` Defined in: [packages/data-apis/src/types.ts:166](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/types.ts#L166) diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchParams.mdx index ee0e595eb9..59034c872b 100644 --- a/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchParams.mdx +++ b/docs/pages/reference/data-apis/src/type-aliases/GetNftMetadataBatchParams.mdx @@ -31,7 +31,7 @@ Defined in: [packages/data-apis/src/types.ts:185](https://github.com/alchemyplat - `NetworkInput` + `AlchemyNetwork` diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetSpamContractsParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetSpamContractsParams.mdx index 8bbc8aef0b..86fdbfce6d 100644 --- a/docs/pages/reference/data-apis/src/type-aliases/GetSpamContractsParams.mdx +++ b/docs/pages/reference/data-apis/src/type-aliases/GetSpamContractsParams.mdx @@ -31,7 +31,7 @@ Defined in: [packages/data-apis/src/types.ts:229](https://github.com/alchemyplat - `NetworkInput` + `AlchemyNetwork` diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceParams.mdx index f664a3d457..4234ccc53a 100644 --- a/docs/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceParams.mdx +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenAllowanceParams.mdx @@ -31,7 +31,7 @@ Defined in: [packages/data-apis/src/types.ts:273](https://github.com/alchemyplat - `NetworkInput` + `AlchemyNetwork` diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesParams.mdx index 97e04e5efc..317da940c1 100644 --- a/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesParams.mdx +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenBalancesParams.mdx @@ -45,7 +45,7 @@ Defined in: [packages/data-apis/src/types.ts:253](https://github.com/alchemyplat - `NetworkInput` + `AlchemyNetwork` diff --git a/docs/pages/reference/data-apis/src/type-aliases/GetTokenMetadataParams.mdx b/docs/pages/reference/data-apis/src/type-aliases/GetTokenMetadataParams.mdx index 6a8f2ef68f..1d3b2f492b 100644 --- a/docs/pages/reference/data-apis/src/type-aliases/GetTokenMetadataParams.mdx +++ b/docs/pages/reference/data-apis/src/type-aliases/GetTokenMetadataParams.mdx @@ -45,7 +45,7 @@ Defined in: [packages/data-apis/src/types.ts:265](https://github.com/alchemyplat - `NetworkInput` + `AlchemyNetwork` diff --git a/docs/pages/reference/data-apis/src/type-aliases/RequestOptions.mdx b/docs/pages/reference/data-apis/src/type-aliases/RequestOptions.mdx index 23f240cbbf..d7f3742383 100644 --- a/docs/pages/reference/data-apis/src/type-aliases/RequestOptions.mdx +++ b/docs/pages/reference/data-apis/src/type-aliases/RequestOptions.mdx @@ -11,7 +11,7 @@ layout: reference type RequestOptions = object; ``` -Defined in: [packages/data-apis/src/internal/clientHelpers.ts:20](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/internal/clientHelpers.ts#L20) +Defined in: [packages/data-apis/src/internal/clientHelpers.ts:25](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/data-apis/src/internal/clientHelpers.ts#L25) Per-request options accepted by data actions. diff --git a/packages/common/src/utils/redact.ts b/packages/common/src/utils/redact.ts index 39a6f48e0c..f1f09da562 100644 --- a/packages/common/src/utils/redact.ts +++ b/packages/common/src/utils/redact.ts @@ -1,5 +1,5 @@ /** - * Redacts credentials that can appear in URLs: keys embedded in "/v2/" + * Redacts credentials that can appear in URLs: keys embedded in `/v2/{key}` paths * RPC paths (when a caller configured a key-bearing url) and apiKey query * params. The header-auth paths never put keys in URLs; this protects * configured-url escape hatches and any server-echoed text. diff --git a/packages/data-apis/README.md b/packages/data-apis/README.md index 33cfd75ec5..5a2a3a4d78 100644 --- a/packages/data-apis/README.md +++ b/packages/data-apis/README.md @@ -1,48 +1,42 @@ # @alchemy/data-apis -Alchemy's Data APIs — Portfolio, Prices, NFT, Token, and Transfers — as -typed, viem-style actions. **Currently published under the `alpha` dist-tag.** +Alchemy's Data APIs — Portfolio, Prices, NFT, Token, and Transfers — as typed, +**dependency-free** actions. No chain library required: the package's only +dependency is `@alchemy/common`, and nothing EVM- or ecosystem-specific sits +in the foundation. **Currently published under the `alpha` dist-tag.** ```bash -npm install @alchemy/data-apis@alpha viem +npm install @alchemy/data-apis@alpha ``` ## Quickstart -Two equivalent entry points: - ```ts -// Data-only developers (no viem knowledge required) import { createDataClient } from "@alchemy/data-apis"; const data = createDataClient({ apiKey: process.env.ALCHEMY_API_KEY, - network: "eth-mainnet", // or `mainnet` from viem/chains, or "eip155:1" + network: "eth-mainnet", // optional — see Networks below }); -// Developers already holding a viem client with an Alchemy transport -import { createClient } from "viem"; -import { mainnet } from "viem/chains"; -import { alchemyTransport } from "@alchemy/common"; -import { dataActions } from "@alchemy/data-apis"; - -const client = createClient({ - chain: mainnet, - transport: alchemyTransport({ apiKey: process.env.ALCHEMY_API_KEY }), -}).extend(dataActions); +const nfts = await data.nft.getNftsForOwner({ owner: "0x..." }); ``` Every action is also individually importable (`import { getNftsForOwner } from "@alchemy/data-apis"`) for tree-shaking and -composability. +composability, taking `(client, params)`. ## Namespaces ```ts -// Portfolio — multi-network queries; networks travel per request +// Portfolio — multi-network queries; networks travel per request. +// No client-level network needed at all. const tokens = await data.portfolio.getTokensByAddress({ addresses: [ - { address: "0x...", networks: [mainnet, "base-mainnet", "solana-mainnet"] }, + { + address: "0x...", + networks: ["eth-mainnet", "base-mainnet", "solana-mainnet"], + }, ], }); // also: getTokenBalancesByAddress, getNftsByAddress, getNftContractsByAddress @@ -53,9 +47,8 @@ const prices = await data.prices.getTokenPricesBySymbol({ }); // also: getTokenPricesByAddress, getHistoricalTokenPrices -// NFT — full v3 read surface (21 methods): ownership, metadata (+ batch), -// contracts/collections, owners, sales, floor price, search, spam/airdrop/rarity -const nfts = await data.nft.getNftsForOwner({ owner: "0x..." }); +// NFT — full v3 read surface (21 methods) +const owned = await data.nft.getNftsForOwner({ owner: "0x..." }); // Token — balances, metadata, allowance (JSON-RPC) const balances = await data.token.getTokenBalances({ address: "0x..." }); @@ -66,14 +59,24 @@ const transfers = await data.transfers.getAssetTransfers({ }); ``` -### Networks: three formats, everywhere +### Networks + +Network inputs are strings, both ecosystem-neutral: Alchemy slugs +(`"eth-mainnet"`, exactly as the dashboard and API responses use) or CAIP-2 +ids (`"eip155:1"`, `"solana:mainnet"`). Holding a viem chain object? The +bridge is one expression: `` `eip155:${chain.id}` ``. -Anywhere a network is accepted you can pass a viem `Chain`, an Alchemy slug -(`"eth-mainnet"`), or a CAIP-2 id (`"eip155:1"`, `"solana:mainnet"`) — -resolved by `resolveNetwork()` in `@alchemy/common`. Single-network methods -use the client's default network with a per-request `network` override; -multi-network methods (Portfolio, Prices-by-address) take networks in the -request itself. Registry-unknown slugs are passed through as an escape hatch. +The client-level `network` is optional and only a default: multi-network +methods (Portfolio, Prices-by-address) take networks in the request itself, +and every single-network method accepts a per-request `network` override that +wins over the default. Registry-unknown slugs pass through as an escape +hatch, so newly launched networks work without an SDK release. + +### Proxy / frontend usage + +Pass `url` to route traffic through your backend so no API key ships +client-side: `createDataClient({ url: "https://your-backend/alchemy" })`. +`jwt` auth is also supported. ### Pagination @@ -96,19 +99,28 @@ for await (const page of data.nft.getNftsForOwnerPages( Both channels (REST and JSON-RPC) normalize failures into `AlchemyApiError` from `@alchemy/common`, carrying `status`, `code`, `requestId` (the client-generated `X-Alchemy-Client-Request-Id`), and `retryAfter` when known. -REST requests retry 429/5xx/network failures with exponential backoff -(honoring `Retry-After`) and time out per attempt; pass an `AbortSignal` via -the per-request options to cancel. +Requests retry 429/5xx/network failures with exponential backoff (honoring +`Retry-After`) and time out per attempt; pass an `AbortSignal` via the +per-request options to cancel. JSON-RPC-level errors are never retried. + +## Ecosystem adapters + +The core is deliberately chain-library-free (see the "Data SDK Foundation" +decision doc). Adapters ship as demand warrants, post-v1 — first in line is +`@alchemy/data-apis/viem`, a `dataActions` decorator for existing viem and +wallet-apis clients (`client.extend(dataActions)`). Its implementation is +already parked and tested at `src/viem/`; it is packaging work away from +shipping. Adapters depend on their ecosystem's library; the core never does. ## Generated internals Param/result types are generated from the docs repo's bundled OpenAPI/OpenRPC -specs by `@alchemy/api-codegen` (see that package's README for the -snapshot/generate pipeline). `src/generated/` is committed, machine-owned, and -never re-exported directly: the public types in `src/types.ts` are -hand-reviewed aliases, and `codegen.manifest.ts` maps spec operations to the -generated surface — referencing a renamed/removed spec operation fails -`pnpm generate` loudly. +specs by `api-codegen` (repo root; see its README for the snapshot/generate +pipeline). `src/generated/` is committed, machine-owned, and never +re-exported directly: the public types in `src/types.ts` are hand-reviewed +aliases, and `codegen.manifest.ts` maps spec operations to the generated +surface — referencing a renamed/removed spec operation fails `pnpm generate` +loudly. ## Releasing diff --git a/packages/data-apis/package.json b/packages/data-apis/package.json index 0efce3e6ae..1a45bb3b3d 100644 --- a/packages/data-apis/package.json +++ b/packages/data-apis/package.json @@ -1,7 +1,7 @@ { "name": "@alchemy/data-apis", "version": "5.0.3-alpha.0", - "description": "Alchemy Data APIs SDK: Portfolio, Prices, NFT, Token, and Transfers \u2014 typed, dependency-free actions", + "description": "Alchemy Data APIs SDK: Portfolio, Prices, NFT, Token, and Transfers — typed, dependency-free actions", "keywords": [ "alchemy", "nft", diff --git a/packages/data-apis/src/decorator.ts b/packages/data-apis/src/decorator.ts index 746d9877c5..2f5edc9d40 100644 --- a/packages/data-apis/src/decorator.ts +++ b/packages/data-apis/src/decorator.ts @@ -60,7 +60,7 @@ type PagesAction = ( options?: PaginateOptions, ) => AsyncGenerator; -/** The namespaced Data API actions attached by the {@link dataActions} decorator. */ +/** The namespaced Data API actions available on a Data API client. */ export type DataActions = { portfolio: { getTokensByAddress: Action< From 29593806ba1fde70bcc0f2257a89ccd13ab8ba55 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Fri, 12 Jun 2026 11:45:05 -0400 Subject: [PATCH 18/20] feat(common): viem-free /core entry point; data-apis imports it exclusively MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The root barrel eagerly loads the viem-based transport, so importing '@alchemy/common' requires viem at runtime. The new '@alchemy/common/core' subpath exports exactly the viem-free surface (AlchemyError family, REST + JSON-RPC runtime, network registry, chain registry data, utils). The data- apis core imports only /core; the codegen emitter targets it for generated schema imports. Root barrel unchanged — wallet-apis unaffected. Verified: packed both tarballs into a clean project with NO viem installed — node_modules contains no viem, the package imports, and clients construct (with and without a network). Co-Authored-By: Claude Fable 5 --- api-codegen/src/rest/schemaEmitter.ts | 2 +- packages/common/package.json | 5 +++ packages/common/src/core.ts | 44 +++++++++++++++++++ .../portfolio/getNftContractsByAddress.ts | 2 +- .../src/actions/portfolio/getNftsByAddress.ts | 2 +- .../portfolio/getTokenBalancesByAddress.ts | 2 +- .../actions/portfolio/getTokensByAddress.ts | 2 +- .../prices/getHistoricalTokenPrices.ts | 2 +- .../actions/prices/getTokenPricesByAddress.ts | 2 +- packages/data-apis/src/client.ts | 2 +- .../src/generated/rest/nft.schema.ts | 2 +- .../src/generated/rest/portfolio.schema.ts | 2 +- .../src/generated/rest/prices.schema.ts | 2 +- .../data-apis/src/internal/clientHelpers.ts | 2 +- packages/data-apis/src/internal/paginate.ts | 2 +- packages/data-apis/src/types.ts | 2 +- packages/data-apis/src/viem/dataActions.ts | 7 +-- 17 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 packages/common/src/core.ts diff --git a/api-codegen/src/rest/schemaEmitter.ts b/api-codegen/src/rest/schemaEmitter.ts index d31df2d799..3a16f0e411 100644 --- a/api-codegen/src/rest/schemaEmitter.ts +++ b/api-codegen/src/rest/schemaEmitter.ts @@ -230,7 +230,7 @@ export function emitRestSchema( } return [ - `import type { RestRequestSchema } from "@alchemy/common";`, + `import type { RestRequestSchema } from "@alchemy/common/core";`, `import type { operations } from "./${config.spec}.types.js";`, ``, ...aliasBlocks.map((block) => block + "\n"), diff --git a/packages/common/package.json b/packages/common/package.json index f1de6edc8b..84b417ff9b 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -27,6 +27,11 @@ "import": "./dist/esm/index.js", "default": "./dist/esm/index.js" }, + "./core": { + "types": "./dist/types/core.d.ts", + "import": "./dist/esm/core.js", + "default": "./dist/esm/core.js" + }, "./chains": { "types": "./dist/types/chains.d.ts", "import": "./dist/esm/chains.js", diff --git a/packages/common/src/core.ts b/packages/common/src/core.ts new file mode 100644 index 0000000000..b315f20da2 --- /dev/null +++ b/packages/common/src/core.ts @@ -0,0 +1,44 @@ +// The viem-free entry point ("@alchemy/common/core"): everything here is +// importable without viem installed. The root barrel ("@alchemy/common") +// additionally exposes the viem-based transport, chains, and BaseError for +// wallet-facing packages. Dependency-free consumers (the Data APIs core) +// import from this entry only — adding a viem-dependent export here breaks +// their install, and the data-apis CI proof will catch it. + +// errors (AlchemyError-rooted family — no viem) +export { AlchemyError } from "./errors/AlchemyError.js"; +export type { AlchemyApiErrorDetails } from "./errors/AlchemyApiError.js"; +export { AlchemyApiError } from "./errors/AlchemyApiError.js"; +export { FetchError } from "./errors/FetchError.js"; +export { ServerError } from "./errors/ServerError.js"; + +// REST + JSON-RPC runtime +export type * from "./rest/restClient.js"; +export type * from "./rest/types.js"; +export { AlchemyRestClient } from "./rest/restClient.js"; +export type { + AlchemyJsonRpcClientParams, + JsonRpcRequestFn, + JsonRpcSchema, +} from "./rest/jsonRpcClient.js"; +export { AlchemyJsonRpcClient } from "./rest/jsonRpcClient.js"; + +// network identity (slug / CAIP-2 resolution; viem Chain accepted type-only) +export type { + AlchemyNetwork, + KnownAlchemyNetwork, + NetworkInput, + ResolvedNetwork, +} from "./networks/networkRegistry.js"; +export { resolveNetwork } from "./networks/networkRegistry.js"; + +// chain registry data (generated; no viem) +export { + getAlchemyRpcUrl, + getSupportedChainIds, + isChainSupported, +} from "./transport/chainRegistry.js"; + +// utils +export { composeSignals, sleep } from "./utils/signals.js"; +export { redactUrlCredentials } from "./utils/redact.js"; diff --git a/packages/data-apis/src/actions/portfolio/getNftContractsByAddress.ts b/packages/data-apis/src/actions/portfolio/getNftContractsByAddress.ts index 1440eca92f..c4dffc9820 100644 --- a/packages/data-apis/src/actions/portfolio/getNftContractsByAddress.ts +++ b/packages/data-apis/src/actions/portfolio/getNftContractsByAddress.ts @@ -1,4 +1,4 @@ -import { resolveNetwork } from "@alchemy/common"; +import { resolveNetwork } from "@alchemy/common/core"; import { DATA_API_URL } from "../../internal/endpoints.js"; import { getRestClient, diff --git a/packages/data-apis/src/actions/portfolio/getNftsByAddress.ts b/packages/data-apis/src/actions/portfolio/getNftsByAddress.ts index 38c6c8cc52..bf2f203636 100644 --- a/packages/data-apis/src/actions/portfolio/getNftsByAddress.ts +++ b/packages/data-apis/src/actions/portfolio/getNftsByAddress.ts @@ -1,4 +1,4 @@ -import { resolveNetwork } from "@alchemy/common"; +import { resolveNetwork } from "@alchemy/common/core"; import { DATA_API_URL } from "../../internal/endpoints.js"; import { getRestClient, diff --git a/packages/data-apis/src/actions/portfolio/getTokenBalancesByAddress.ts b/packages/data-apis/src/actions/portfolio/getTokenBalancesByAddress.ts index 8c8acb89cc..ed58a0f083 100644 --- a/packages/data-apis/src/actions/portfolio/getTokenBalancesByAddress.ts +++ b/packages/data-apis/src/actions/portfolio/getTokenBalancesByAddress.ts @@ -1,4 +1,4 @@ -import { resolveNetwork } from "@alchemy/common"; +import { resolveNetwork } from "@alchemy/common/core"; import { DATA_API_URL } from "../../internal/endpoints.js"; import { getRestClient, diff --git a/packages/data-apis/src/actions/portfolio/getTokensByAddress.ts b/packages/data-apis/src/actions/portfolio/getTokensByAddress.ts index c18bc31dc5..929c0bc3cc 100644 --- a/packages/data-apis/src/actions/portfolio/getTokensByAddress.ts +++ b/packages/data-apis/src/actions/portfolio/getTokensByAddress.ts @@ -1,4 +1,4 @@ -import { resolveNetwork } from "@alchemy/common"; +import { resolveNetwork } from "@alchemy/common/core"; import { DATA_API_URL } from "../../internal/endpoints.js"; import { getRestClient, diff --git a/packages/data-apis/src/actions/prices/getHistoricalTokenPrices.ts b/packages/data-apis/src/actions/prices/getHistoricalTokenPrices.ts index dbba35e97e..7ae3e9f646 100644 --- a/packages/data-apis/src/actions/prices/getHistoricalTokenPrices.ts +++ b/packages/data-apis/src/actions/prices/getHistoricalTokenPrices.ts @@ -1,4 +1,4 @@ -import { resolveNetwork } from "@alchemy/common"; +import { resolveNetwork } from "@alchemy/common/core"; import { PRICES_API_URL } from "../../internal/endpoints.js"; import { getRestClient, diff --git a/packages/data-apis/src/actions/prices/getTokenPricesByAddress.ts b/packages/data-apis/src/actions/prices/getTokenPricesByAddress.ts index 47430f495d..9b911c449b 100644 --- a/packages/data-apis/src/actions/prices/getTokenPricesByAddress.ts +++ b/packages/data-apis/src/actions/prices/getTokenPricesByAddress.ts @@ -1,4 +1,4 @@ -import { resolveNetwork } from "@alchemy/common"; +import { resolveNetwork } from "@alchemy/common/core"; import { PRICES_API_URL } from "../../internal/endpoints.js"; import { getRestClient, diff --git a/packages/data-apis/src/client.ts b/packages/data-apis/src/client.ts index 173b54f89e..4f5a1aa34c 100644 --- a/packages/data-apis/src/client.ts +++ b/packages/data-apis/src/client.ts @@ -2,7 +2,7 @@ import { resolveNetwork, type AlchemyNetwork, type AlchemyRestClientParams, -} from "@alchemy/common"; +} from "@alchemy/common/core"; import { dataActions, type DataActions } from "./decorator.js"; import type { DataClient } from "./internal/clientHelpers.js"; diff --git a/packages/data-apis/src/generated/rest/nft.schema.ts b/packages/data-apis/src/generated/rest/nft.schema.ts index a93909c478..eff2c6f114 100644 --- a/packages/data-apis/src/generated/rest/nft.schema.ts +++ b/packages/data-apis/src/generated/rest/nft.schema.ts @@ -3,7 +3,7 @@ // Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) // is recorded in api-codegen/specs/specs.lock.json. -import type { RestRequestSchema } from "@alchemy/common"; +import type { RestRequestSchema } from "@alchemy/common/core"; import type { operations } from "./nft.types.js"; /** 200 response for getNFTsForOwner-v3. */ diff --git a/packages/data-apis/src/generated/rest/portfolio.schema.ts b/packages/data-apis/src/generated/rest/portfolio.schema.ts index 15a1e5edbe..d78145df9d 100644 --- a/packages/data-apis/src/generated/rest/portfolio.schema.ts +++ b/packages/data-apis/src/generated/rest/portfolio.schema.ts @@ -3,7 +3,7 @@ // Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) // is recorded in api-codegen/specs/specs.lock.json. -import type { RestRequestSchema } from "@alchemy/common"; +import type { RestRequestSchema } from "@alchemy/common/core"; import type { operations } from "./portfolio.types.js"; /** Request body for get-tokens-by-address. */ diff --git a/packages/data-apis/src/generated/rest/prices.schema.ts b/packages/data-apis/src/generated/rest/prices.schema.ts index 8be28582ea..71fbeeecd9 100644 --- a/packages/data-apis/src/generated/rest/prices.schema.ts +++ b/packages/data-apis/src/generated/rest/prices.schema.ts @@ -3,7 +3,7 @@ // Regenerate with `pnpm generate`. Spec provenance (docs repo commit + checksums) // is recorded in api-codegen/specs/specs.lock.json. -import type { RestRequestSchema } from "@alchemy/common"; +import type { RestRequestSchema } from "@alchemy/common/core"; import type { operations } from "./prices.types.js"; /** 200 response for get-token-prices-by-symbol. */ diff --git a/packages/data-apis/src/internal/clientHelpers.ts b/packages/data-apis/src/internal/clientHelpers.ts index 74e28cc83d..365e077757 100644 --- a/packages/data-apis/src/internal/clientHelpers.ts +++ b/packages/data-apis/src/internal/clientHelpers.ts @@ -7,7 +7,7 @@ import { type AlchemyRestClientParams, type ResolvedNetwork, type RestRequestSchema, -} from "@alchemy/common"; +} from "@alchemy/common/core"; import { getRpcUrl } from "./endpoints.js"; import type { DataRpcSchema } from "../schema/rpc.js"; diff --git a/packages/data-apis/src/internal/paginate.ts b/packages/data-apis/src/internal/paginate.ts index 80f018fc0c..aaf177b1a6 100644 --- a/packages/data-apis/src/internal/paginate.ts +++ b/packages/data-apis/src/internal/paginate.ts @@ -1,4 +1,4 @@ -import { AlchemyError } from "@alchemy/common"; +import { AlchemyError } from "@alchemy/common/core"; /** Options accepted by the `*Pages` async-iterator actions. */ export type PaginateOptions = { diff --git a/packages/data-apis/src/types.ts b/packages/data-apis/src/types.ts index aa4e88c9f4..547750cd17 100644 --- a/packages/data-apis/src/types.ts +++ b/packages/data-apis/src/types.ts @@ -1,4 +1,4 @@ -import type { AlchemyNetwork } from "@alchemy/common"; +import type { AlchemyNetwork } from "@alchemy/common/core"; import type { ComputeRarityQuery, ComputeRarityResponse, diff --git a/packages/data-apis/src/viem/dataActions.ts b/packages/data-apis/src/viem/dataActions.ts index 4f4f0884d7..dacc01f8db 100644 --- a/packages/data-apis/src/viem/dataActions.ts +++ b/packages/data-apis/src/viem/dataActions.ts @@ -3,11 +3,8 @@ // warrants (post-v1, per the approved standalone-core direction). Kept here // with tests so it stays working against the core. -import { - resolveNetwork, - type AlchemyTransport, - type AlchemyTransportConfig, -} from "@alchemy/common"; +import { resolveNetwork } from "@alchemy/common/core"; +import type { AlchemyTransport, AlchemyTransportConfig } from "@alchemy/common"; import type { Chain, Client } from "viem"; import { dataActions as coreDataActions, From 2949fdfb8190e68251b9be41af1be59034935215 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Fri, 12 Jun 2026 14:53:44 -0400 Subject: [PATCH 19/20] docs: regenerate sdk reference after v5.0.4 merge from main --- .../common/src/classes/AlchemyApiError.mdx | 2 +- .../common/src/classes/AlchemyError.mdx | 2 +- .../common/src/classes/FetchError.mdx | 2 +- .../common/src/classes/ServerError.mdx | 2 +- .../data-apis-sdk-dependency-model.md | 148 ++++++++++++++++++ 5 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 docs/solutions/data-apis-sdk-dependency-model.md diff --git a/docs/pages/reference/common/src/classes/AlchemyApiError.mdx b/docs/pages/reference/common/src/classes/AlchemyApiError.mdx index de0d4016b6..24613e5ca8 100644 --- a/docs/pages/reference/common/src/classes/AlchemyApiError.mdx +++ b/docs/pages/reference/common/src/classes/AlchemyApiError.mdx @@ -275,7 +275,7 @@ Creates a normalized API error. - `"5.0.3"` + `"5.0.4"` diff --git a/docs/pages/reference/common/src/classes/AlchemyError.mdx b/docs/pages/reference/common/src/classes/AlchemyError.mdx index 946405faba..e516fa6a73 100644 --- a/docs/pages/reference/common/src/classes/AlchemyError.mdx +++ b/docs/pages/reference/common/src/classes/AlchemyError.mdx @@ -179,7 +179,7 @@ Error.constructor; - `"5.0.3"` + `"5.0.4"` diff --git a/docs/pages/reference/common/src/classes/FetchError.mdx b/docs/pages/reference/common/src/classes/FetchError.mdx index 582bd65e95..5e7f01e595 100644 --- a/docs/pages/reference/common/src/classes/FetchError.mdx +++ b/docs/pages/reference/common/src/classes/FetchError.mdx @@ -291,7 +291,7 @@ Initializes a new fetch error for a request that failed before a response. - `"5.0.3"` + `"5.0.4"` diff --git a/docs/pages/reference/common/src/classes/ServerError.mdx b/docs/pages/reference/common/src/classes/ServerError.mdx index e850afa827..8cf3d87038 100644 --- a/docs/pages/reference/common/src/classes/ServerError.mdx +++ b/docs/pages/reference/common/src/classes/ServerError.mdx @@ -291,7 +291,7 @@ Initializes a new server error for a failed HTTP response. - `"5.0.3"` + `"5.0.4"` diff --git a/docs/solutions/data-apis-sdk-dependency-model.md b/docs/solutions/data-apis-sdk-dependency-model.md new file mode 100644 index 0000000000..d549d57be5 --- /dev/null +++ b/docs/solutions/data-apis-sdk-dependency-model.md @@ -0,0 +1,148 @@ +--- +title: Data APIs SDK Dependency Model +date: 2026-06-11 +tags: + - data-apis + - sdk + - viem +area: data-apis +--- + +# Data APIs SDK Dependency Model + +## TL;DR + +Recommend a dependency-light `@alchemy/data-apis` core, not viem as the +required foundation. + +Keep viem support as an optional adapter for existing EVM and aa-sdk users. +Use `@alchemy/common` for shared viem-free REST/RPC/auth/error/network +primitives. Keep Data API product logic in `@alchemy/data-apis`. + +Do not use Stainless for v1. Run a POC after spec ownership and quality are +settled. + +## Current Branch + +The branch implements `createDataClient` as a viem wrapper: +`createClient({ chain, transport }).extend(dataActions)`. + +`viem` is a peer dependency. That avoids duplicate-version conflicts, but users +still have to install an Ethereum SDK for non-EVM data use cases. + +The current network model is good: it accepts Alchemy slugs, CAIP-2 ids, and +viem chains. The problem is making viem the root abstraction. The code has to +fabricate viem `Chain` objects for slug, CAIP-2, and Solana inputs, which is a +sign the abstraction is serving viem instead of Data APIs. + +As of this review, npm reports `viem@2.52.2` at 9,616 files and about 24 MB +unpacked. In this repo, `viem@2.46.3` takes about 48 MB per pnpm peer variant. +This is a real install-footprint and product-positioning cost for Solana and +Hyperliquid developers. + +## Competitors + +Pattern: cross-chain data SDKs are HTTP/data clients, not viem wrappers. +QuickNode's official SDK has no viem, ethers, or wagmi dependency or source +reference. Using QuickNode RPC with viem is endpoint compatibility, not evidence +that `@quicknode/sdk` is viem-based. + +| Provider | SDK shape | Takeaway | +| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| QuickNode | `@quicknode/sdk`. Rust/NAPI core with Node, Python, Rust, and Ruby wrappers. No viem. Not browser-compatible. Direct chain calls should use the user's preferred chain library. | Product-client namespaces are good. Native bindings are a poor fit for our browser and dependency-light goals. | +| Moralis | `moralis`. Data modules for EVM, Solana, Aptos, Bitcoin, Streams, Auth. No viem. | Strong missing competitor. Validates chain-specific modules over an EVM-root abstraction. | +| Covalent / GoldRush | `@covalenthq/client-sdk`. Service namespaces over REST/GraphQL. Uses chain names like `eth-mainnet`. No viem. | Strong missing competitor. Data-native chain ids are normal. Their Hyperliquid and Solana docs also make non-EVM first-class. | +| Allium | REST-first Realtime APIs across 20+ chains, including Solana. Auth is a plain `X-API-KEY` header. No official public JS data SDK found. | A lightweight HTTP-first data API is a credible market shape. | +| Dune / Sim | Sim APIs are REST-first with `X-Sim-Api-Key`, EVM and Solana route families, `chain_ids` query params/tags, and cursor pagination. Dune's separate analytics SDK is a lightweight query client. | Data APIs do not need to depend on an EVM client library. | +| Helius | `helius-sdk`. Solana SDK with Solana ecosystem peer deps. No viem. | For Solana, native ecosystem deps are acceptable; EVM deps are not. | +| Ankr | `@ankr.com/ankr.js`. Compact API client with axios. No viem. | Another infra/data SDK using plain HTTP client style. | +| Reservoir | `@reservoir0x/reservoir-sdk`. NFT liquidity SDK with viem as a peer dep. | Useful counterexample, but not like-for-like. EVM liquidity SDKs can justify viem; cross-chain data SDKs should not require it. | + +## Stainless + +Stainless is worth evaluating, but not for immediate v1. + +Pros: + +- Generates SDKs, docs, CLI, and MCP from OpenAPI. +- Handles auth, retries, pagination helpers, publishing, and release flow. +- TypeScript SDKs use native `fetch` and support Node, Deno, Bun, Workers, and + Edge runtimes. +- Avoids runtime request validation, which helps code size and forward + compatibility. + +Cons: + +- Requires high-quality OpenAPI. Our review already found spec-source risk. +- Generated SDK shape will not naturally match aa-sdk viem actions. +- Custom aa-sdk ergonomics would need custom code or a separate adapter. +- Adds vendor and release-workflow dependency. + +Recommendation: POC Stainless on Portfolio/Prices after specs are reliable. +Do not block v1 on it. + +## Options + +| Option | Pros | Cons | +| --------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| Keep viem in core | Fastest from current branch. Best EVM ergonomics. Matches aa-sdk actions. | Weak non-EVM story. Required Ethereum SDK. Wrong default for H2 Solana/Hyperliquid focus. | +| Dependency-light core plus viem adapter | Best non-EVM story. Preserves viem UX for EVM users. Matches competitor REST-first shape. | Requires small package split/refactor. | +| Stainless-generated SDK | Best long-term multi-language path. Strong generated docs/MCP story. | Spec/process risk. Less control over SDK shape. Not a v1 unblocker. | + +## Recommendation + +Ship option 2. + +- `@alchemy/data-apis`: core SDK, no viem dependency. +- `@alchemy/data-apis/viem` or `@alchemy/data-apis-viem`: optional viem adapter, + peer dependency on viem, exports `dataActions`. +- Public config uses `chain`, not `network`. +- Multi-chain methods take `chains`. +- Core accepts Alchemy slugs and CAIP-2 ids. +- Viem adapter additionally accepts viem `Chain`. + +Use `@alchemy/common` for shared internals, but split the entrypoints: + +- viem-free common: + - REST client + - generic JSON-RPC fetch client + - auth/header handling + - retry, timeout, abort handling + - API errors and key redaction + - slug, CAIP-2, and numeric chain normalization +- viem common: + - `alchemyTransport` + - viem `Chain` support + - viem error wrapping + +Keep these in `@alchemy/data-apis`: + +- generated Data API schemas/types +- operation manifests +- public namespaces and method names +- request/response mappers +- Data API pagination helpers + +## Sources + +- QuickNode SDK docs: https://www.quicknode.com/docs/quicknode-sdk +- QuickNode SDK repo: https://github.com/quicknode/sdk +- QuickNode SDK npm: https://www.npmjs.com/package/@quicknode/sdk +- Moralis Data API docs: https://docs.moralis.com/data-api/overview +- Moralis npm: https://www.npmjs.com/package/moralis +- GoldRush docs: https://goldrush.dev/docs/ +- Covalent SDK npm: https://www.npmjs.com/package/@covalenthq/client-sdk +- Allium API docs: https://docs.allium.so/api/overview +- Allium Realtime API docs: https://docs.allium.so/api/developer/overview +- Dune Sim docs: https://docs.sim.dune.com/ +- Dune Sim balances: https://docs.sim.dune.com/evm/balances +- Dune SDK docs: https://docs.dune.com/api-reference/sdks/typescript +- Helius docs: https://www.helius.dev/docs +- Helius SDK npm: https://www.npmjs.com/package/helius-sdk +- Ankr SDK npm: https://www.npmjs.com/package/@ankr.com/ankr.js +- Reservoir SDK npm: https://www.npmjs.com/package/@reservoir0x/reservoir-sdk +- Stainless docs: https://www.stainless.com/docs/ +- Stainless TypeScript runtime support: + https://www.stainless.com/docs/sdks/typescript/ +- Stainless runtime validation rationale: + https://www.stainless.com/docs/design/runtime-request-validation/ From acb94993e0da98ffc3002f594c4cb4d8fa77b463 Mon Sep 17 00:00:00 2001 From: blake duncan Date: Fri, 12 Jun 2026 15:31:45 -0400 Subject: [PATCH 20/20] test(common): deflake profiled-function timing assertion setTimeout(10) can measure as 9ms when both timing endpoints are millisecond-truncated; failed Build and Test on CI. Allow 1ms tolerance. Co-Authored-By: Claude Fable 5 --- packages/common/tests/logging/createLogger.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/common/tests/logging/createLogger.test.ts b/packages/common/tests/logging/createLogger.test.ts index 0504f1550d..8b66c4067c 100644 --- a/packages/common/tests/logging/createLogger.test.ts +++ b/packages/common/tests/logging/createLogger.test.ts @@ -339,7 +339,9 @@ describe("createLogger", () => { ); const entry = mockSink.mock.calls[0][0] as LogEntry; - expect(entry.data?.executionTimeMs).toBeGreaterThanOrEqual(10); + // setTimeout(10) can measure as 9ms due to millisecond truncation at + // both timing endpoints — allow 1ms of tolerance to deflake CI. + expect(entry.data?.executionTimeMs).toBeGreaterThanOrEqual(9); }); it("should track failed profiled functions", async () => {