diff --git a/package.json b/package.json index 447cef64aad..6a0bed90438 100644 --- a/package.json +++ b/package.json @@ -55,16 +55,16 @@ "build": "turbo run build --filter=./packages/*", "build:release": "turbo run build --filter=./packages/* --force", "changeset": "changeset", - "dashboard": "turbo run dev --filter=./apps/dashboard --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/vault-sdk --filter=./packages/engine --filter=./packages/nebula", + "dashboard": "turbo run dev --filter=./apps/dashboard --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/vault-sdk --filter=./packages/engine --filter=./packages/api --filter=./packages/nebula", "dashboard:build": "turbo run build --filter=./apps/dashboard", "e2e": "turbo run e2e --filter=./packages/*", "fix": "turbo run fix", "hotlink-init": "node ./scripts/hotlink/hotlink-init.mjs", "hotlink-revert": "node ./scripts/hotlink/hotlink-revert.mjs", "lint": "pnpm dlx sherif@latest -i remark-gfm -i eslint && turbo run lint", - "playground": "turbo run dev --filter=./apps/playground-web --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine --filter=./packages/nebula", + "playground": "turbo run dev --filter=./apps/playground-web --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine --filter=./packages/api --filter=./packages/nebula", "playground:build": "turbo run build --filter=./apps/playground-web", - "portal": "turbo run dev --filter=./apps/portal --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine --filter=./packages/nebula", + "portal": "turbo run dev --filter=./apps/portal --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine --filter=./packages/api --filter=./packages/nebula", "portal:build": "turbo run build --filter=./apps/portal", "prefix": "pnpm dlx sherif@latest -i remark-gfm -i eslint --fix", "preinstall": "npx only-allow pnpm", @@ -75,8 +75,8 @@ "typedoc": "turbo run typedoc", "version-packages": "changeset version", "version-packages:nightly": "node scripts/pre-nightly.mjs && changeset version --snapshot nightly", - "wallet-ui": "turbo run dev --filter=./apps/wallet-ui --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine --filter=./packages/nebula", - "wallet-ui:build": "turbo run build --filter=./apps/wallet-ui --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine --filter=./packages/nebula" + "wallet-ui": "turbo run dev --filter=./apps/wallet-ui --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine --filter=./packages/api --filter=./packages/nebula", + "wallet-ui:build": "turbo run build --filter=./apps/wallet-ui --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine --filter=./packages/api --filter=./packages/nebula" }, "version": "1.0.0" } diff --git a/packages/api/README.md b/packages/api/README.md new file mode 100644 index 00000000000..42df880bc5b --- /dev/null +++ b/packages/api/README.md @@ -0,0 +1,39 @@ +# thirdweb API TypeScript wrapper + +This package is a thin OpenAPI wrapper for the thirdweb API + +## Configuration + +```ts +import { configure } from "@thirdweb-dev/api"; + +// call this once at the startup of your application +configure({ + secretKey: "", +}); +``` + +## Example Usage + +```ts +import { writeContract } from "@thirdweb-dev/api"; + +const result = await sendTransaction({ + body: { + from: "0x1234567891234567891234567891234567891234", + chainId: "1", + calls: [ + { + contractAddress: "0x1234567890123456789012345678901234567890", + method: "function transfer(address to, uint256 amount)", + params: [ + "0x1234567890123456789012345678901234567890", + "1000000000000000000", + ], + }, + ], + }, +}); +``` + +This package was autogenerated from the [thirdweb openAPI spec](https://api.thirdweb.com/reference) using [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) diff --git a/packages/api/biome.json b/packages/api/biome.json new file mode 100644 index 00000000000..2548ec8ca6d --- /dev/null +++ b/packages/api/biome.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.0.6/schema.json", + "linter": { + "rules": { + "correctness": { + "useImportExtensions": { + "fix": "safe", + "level": "error", + "options": { + "forceJsExtensions": true + } + } + } + } + }, + "overrides": [ + { + "assist": { + "actions": { + "source": { + "useSortedKeys": "off" + } + } + }, + "includes": ["package.json"] + } + ] +} diff --git a/packages/api/openapi-ts.config.ts b/packages/api/openapi-ts.config.ts new file mode 100644 index 00000000000..73479e330a3 --- /dev/null +++ b/packages/api/openapi-ts.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from "@hey-api/openapi-ts"; + +export default defineConfig({ + input: "https://api.thirdweb-dev.com/openapi.json", + output: { format: "biome", lint: "biome", path: "src/client" }, +}); diff --git a/packages/api/package.json b/packages/api/package.json new file mode 100644 index 00000000000..986d1f9e2bb --- /dev/null +++ b/packages/api/package.json @@ -0,0 +1,61 @@ +{ + "name": "@thirdweb-dev/api", + "version": "0.0.1", + "repository": { + "type": "git", + "url": "git+https://github.com/thirdweb-dev/js.git#main" + }, + "author": "thirdweb eng ", + "type": "module", + "main": "./dist/cjs/exports/thirdweb.js", + "module": "./dist/esm/exports/thirdweb.js", + "types": "./dist/types/exports/thirdweb.d.ts", + "typings": "./dist/types/exports/thirdweb.d.ts", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/thirdweb-dev/js/issues" + }, + "dependencies": { + "@hey-api/client-fetch": "0.10.0" + }, + "engines": { + "node": ">=18" + }, + "exports": { + ".": { + "types": "./dist/types/exports/thirdweb.d.ts", + "import": "./dist/esm/exports/thirdweb.js", + "default": "./dist/cjs/exports/thirdweb.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist/*", + "src/*" + ], + "devDependencies": { + "@biomejs/biome": "2.0.6", + "@hey-api/openapi-ts": "0.76.0", + "rimraf": "6.0.1", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + }, + "scripts": { + "build": "pnpm clean && pnpm build:cjs && pnpm build:esm && pnpm build:types", + "build:cjs": "tsc --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json", + "build:esm": "tsc --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json", + "build:generate": "openapi-ts && pnpm format && pnpm fix", + "build:types": "tsc --project ./tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", + "clean": "rimraf dist", + "fix": "biome check --write ./src", + "format": "biome format --write ./src", + "lint": "biome check ./src" + } +} diff --git a/packages/api/src/client/client.gen.ts b/packages/api/src/client/client.gen.ts new file mode 100644 index 00000000000..0c30251d565 --- /dev/null +++ b/packages/api/src/client/client.gen.ts @@ -0,0 +1,28 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { + type Config, + createClient, + createConfig, + type ClientOptions as DefaultClientOptions, +} from "./client/index.js"; +import type { ClientOptions } from "./types.gen.js"; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = + ( + override?: Config, + ) => Config & T>; + +export const client = createClient( + createConfig({ + baseUrl: "https://api.thirdweb-dev.com", + }), +); diff --git a/packages/api/src/client/client/client.ts b/packages/api/src/client/client/client.ts new file mode 100644 index 00000000000..0b528190390 --- /dev/null +++ b/packages/api/src/client/client/client.ts @@ -0,0 +1,189 @@ +import type { Client, Config, RequestOptions } from "./types.js"; +import { + buildUrl, + createConfig, + createInterceptors, + getParseAs, + mergeConfigs, + mergeHeaders, + setAuthParams, +} from "./utils.js"; + +type ReqInit = Omit & { + body?: any; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + RequestOptions + >(); + + const request: Client["request"] = async (options) => { + const opts = { + ..._config, + ...options, + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, + headers: mergeHeaders(_config.headers, options.headers), + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.body && opts.bodySerializer) { + opts.body = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.body === "") { + opts.headers.delete("Content-Type"); + } + + const url = buildUrl(opts); + const requestInit: ReqInit = { + redirect: "follow", + ...opts, + }; + + let request = new Request(url, requestInit); + + for (const fn of interceptors.request._fns) { + if (fn) { + request = await fn(request, opts); + } + } + + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = opts.fetch!; + let response = await _fetch(request); + + for (const fn of interceptors.response._fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { + request, + response, + }; + + if (response.ok) { + if ( + response.status === 204 || + response.headers.get("Content-Length") === "0" + ) { + return opts.responseStyle === "data" + ? {} + : { + data: {}, + ...result, + }; + } + + const parseAs = + (opts.parseAs === "auto" + ? getParseAs(response.headers.get("Content-Type")) + : opts.parseAs) ?? "json"; + + let data: any; + switch (parseAs) { + case "arrayBuffer": + case "blob": + case "formData": + case "json": + case "text": + data = await response[parseAs](); + break; + case "stream": + return opts.responseStyle === "data" + ? response.body + : { + data: response.body, + ...result, + }; + } + + if (parseAs === "json") { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return opts.responseStyle === "data" + ? data + : { + data, + ...result, + }; + } + + let error = await response.text(); + + try { + error = JSON.parse(error); + } catch { + // noop + } + + let finalError = error; + + for (const fn of interceptors.error._fns) { + if (fn) { + finalError = (await fn(error, response, request, opts)) as string; + } + } + + finalError = finalError || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + // TODO: we probably want to return error and improve types + return opts.responseStyle === "data" + ? undefined + : { + error: finalError, + ...result, + }; + }; + + return { + buildUrl, + connect: (options) => request({ ...options, method: "CONNECT" }), + delete: (options) => request({ ...options, method: "DELETE" }), + get: (options) => request({ ...options, method: "GET" }), + getConfig, + head: (options) => request({ ...options, method: "HEAD" }), + interceptors, + options: (options) => request({ ...options, method: "OPTIONS" }), + patch: (options) => request({ ...options, method: "PATCH" }), + post: (options) => request({ ...options, method: "POST" }), + put: (options) => request({ ...options, method: "PUT" }), + request, + setConfig, + trace: (options) => request({ ...options, method: "TRACE" }), + }; +}; diff --git a/packages/api/src/client/client/index.ts b/packages/api/src/client/client/index.ts new file mode 100644 index 00000000000..c0343af4092 --- /dev/null +++ b/packages/api/src/client/client/index.ts @@ -0,0 +1,22 @@ +export type { Auth } from "../core/auth.js"; +export type { QuerySerializerOptions } from "../core/bodySerializer.js"; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from "../core/bodySerializer.js"; +export { buildClientParams } from "../core/params.js"; +export { createClient } from "./client.js"; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResponseStyle, + TDataShape, +} from "./types.js"; +export { createConfig, mergeHeaders } from "./utils.js"; diff --git a/packages/api/src/client/client/types.ts b/packages/api/src/client/client/types.ts new file mode 100644 index 00000000000..ef3d74fd17d --- /dev/null +++ b/packages/api/src/client/client/types.ts @@ -0,0 +1,222 @@ +import type { Auth } from "../core/auth.js"; +import type { + Client as CoreClient, + Config as CoreConfig, +} from "../core/types.js"; +import type { Middleware } from "./utils.js"; + +export type ResponseStyle = "data" | "fields"; + +export interface Config + extends Omit, + CoreConfig { + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T["baseUrl"]; + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: (request: Request) => ReturnType; + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | "arrayBuffer" + | "auto" + | "blob" + | "formData" + | "json" + | "stream" + | "text"; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T["throwOnError"]; +} + +export interface RequestOptions< + TResponseStyle extends ResponseStyle = "fields", + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }> { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = "fields", +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends "data" + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends "data" + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = "fields", +>( + options: Omit, "method">, +) => RequestResult; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = "fields", +>( + options: Omit, "method"> & + Pick>, "method">, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = "fields", +> = OmitKeys< + RequestOptions, + "body" | "path" | "query" | "url" +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = "fields", +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + "body" | "headers" | "url" + > & + TData + : OmitKeys, "body" | "url"> & + TData & + Pick, "headers"> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + "headers" | "url" + > & + TData & + Pick, "body"> + : OmitKeys, "url"> & TData; diff --git a/packages/api/src/client/client/utils.ts b/packages/api/src/client/client/utils.ts new file mode 100644 index 00000000000..2b5bb778117 --- /dev/null +++ b/packages/api/src/client/client/utils.ts @@ -0,0 +1,417 @@ +import { getAuthToken } from "../core/auth.js"; +import type { + QuerySerializer, + QuerySerializerOptions, +} from "../core/bodySerializer.js"; +import { jsonBodySerializer } from "../core/bodySerializer.js"; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from "../core/pathSerializer.js"; +import type { Client, ClientOptions, Config, RequestOptions } from "./types.js"; + +interface PathSerializer { + path: Record; + url: string; +} + +const PATH_PARAM_RE = /\{[^{}]+\}/g; + +type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited"; +type MatrixStyle = "label" | "matrix" | "simple"; +type ArraySeparatorStyle = ArrayStyle | MatrixStyle; + +const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = "simple"; + + if (name.endsWith("*")) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith(".")) { + name = name.substring(1); + style = "label"; + } else if (name.startsWith(";")) { + name = name.substring(1); + style = "matrix"; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === "object") { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === "matrix") { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === "label" ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === "object") { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: "form", + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === "object") { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: "deepObject", + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join("&"); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return "stream"; + } + + const cleanContent = contentType.split(";")[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith("application/json") || + cleanContent.endsWith("+json") + ) { + return "json"; + } + + if (cleanContent === "multipart/form-data") { + return "formData"; + } + + if ( + ["application/", "audio/", "image/", "video/"].some((type) => + cleanContent.startsWith(type), + ) + ) { + return "blob"; + } + + if (cleanContent.startsWith("text/")) { + return "text"; + } + + return; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, "security"> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? "Authorization"; + + switch (auth.in) { + case "query": + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case "cookie": + options.headers.append("Cookie", `${name}=${token}`); + break; + case "header": + default: + options.headers.set(name, token); + break; + } + + return; + } +}; + +export const buildUrl: Client["buildUrl"] = (options) => { + const url = getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === "function" + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith("/") ? _url : `/${_url}`; + let url = (baseUrl ?? "") + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ""; + if (search.startsWith("?")) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith("/")) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +export const mergeHeaders = ( + ...headers: Array["headers"] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header || typeof header !== "object") { + continue; + } + + const iterator = + header instanceof Headers ? header.entries() : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === "object" ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + _fns: (Interceptor | null)[]; + + constructor() { + this._fns = []; + } + + clear() { + this._fns = []; + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === "number") { + return this._fns[id] ? id : -1; + } else { + return this._fns.indexOf(id); + } + } + exists(id: number | Interceptor) { + const index = this.getInterceptorIndex(id); + return !!this._fns[index]; + } + + eject(id: number | Interceptor) { + const index = this.getInterceptorIndex(id); + if (this._fns[index]) { + this._fns[index] = null; + } + } + + update(id: number | Interceptor, fn: Interceptor) { + const index = this.getInterceptorIndex(id); + if (this._fns[index]) { + this._fns[index] = fn; + return id; + } else { + return false; + } + } + + use(fn: Interceptor) { + this._fns = [...this._fns, fn]; + return this._fns.length - 1; + } +} + +// `createInterceptors()` response, meant for external use as it does not +// expose internals +export interface Middleware { + error: Pick< + Interceptors>, + "eject" | "use" + >; + request: Pick>, "eject" | "use">; + response: Pick< + Interceptors>, + "eject" | "use" + >; +} + +// do not add `Middleware` as return type so we can use _fns internally +export const createInterceptors = () => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: "form", + }, + object: { + explode: true, + style: "deepObject", + }, +}); + +const defaultHeaders = { + "Content-Type": "application/json", +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: "auto", + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/api/src/client/core/auth.ts b/packages/api/src/client/core/auth.ts new file mode 100644 index 00000000000..f9012011fc2 --- /dev/null +++ b/packages/api/src/client/core/auth.ts @@ -0,0 +1,40 @@ +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: "header" | "query" | "cookie"; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: "basic" | "bearer"; + type: "apiKey" | "http"; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === "function" ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === "bearer") { + return `Bearer ${token}`; + } + + if (auth.scheme === "basic") { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/api/src/client/core/bodySerializer.ts b/packages/api/src/client/core/bodySerializer.ts new file mode 100644 index 00000000000..7d254a96ebe --- /dev/null +++ b/packages/api/src/client/core/bodySerializer.ts @@ -0,0 +1,84 @@ +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from "./pathSerializer.js"; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = (data: FormData, key: string, value: unknown) => { + if (typeof value === "string" || value instanceof Blob) { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +) => { + if (typeof value === "string") { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ) => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T) => + JSON.stringify(body, (_key, value) => + typeof value === "bigint" ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ) => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/api/src/client/core/params.ts b/packages/api/src/client/core/params.ts new file mode 100644 index 00000000000..2b3490369bc --- /dev/null +++ b/packages/api/src/client/core/params.ts @@ -0,0 +1,141 @@ +type Slot = "body" | "headers" | "path" | "query"; + +export type Field = + | { + in: Exclude; + key: string; + map?: string; + } + | { + in: Extract; + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: "body", + $headers_: "headers", + $path_: "path", + $query_: "query", +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ("in" in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === "object" && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ("in" in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/api/src/client/core/pathSerializer.ts b/packages/api/src/client/core/pathSerializer.ts new file mode 100644 index 00000000000..2f5d1bf4148 --- /dev/null +++ b/packages/api/src/client/core/pathSerializer.ts @@ -0,0 +1,179 @@ +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited"; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = "label" | "matrix" | "simple"; +export type ObjectStyle = "form" | "deepObject"; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case "label": + return "."; + case "matrix": + return ";"; + case "simple": + return ","; + default: + return "&"; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case "form": + return ","; + case "pipeDelimited": + return "|"; + case "spaceDelimited": + return "%20"; + default: + return ","; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case "label": + return "."; + case "matrix": + return ";"; + case "simple": + return ","; + default: + return "&"; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case "label": + return `.${joinedValues}`; + case "matrix": + return `;${name}=${joinedValues}`; + case "simple": + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === "label" || style === "simple") { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === "label" || style === "matrix" + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ""; + } + + if (typeof value === "object") { + throw new Error( + "Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.", + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== "deepObject" && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(","); + switch (style) { + case "form": + return `${name}=${joinedValues}`; + case "label": + return `.${joinedValues}`; + case "matrix": + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === "deepObject" ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === "label" || style === "matrix" + ? separator + joinedValues + : joinedValues; +}; diff --git a/packages/api/src/client/core/types.ts b/packages/api/src/client/core/types.ts new file mode 100644 index 00000000000..e5ee754b86e --- /dev/null +++ b/packages/api/src/client/core/types.ts @@ -0,0 +1,98 @@ +import type { Auth, AuthToken } from "./auth.js"; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from "./bodySerializer.js"; + +export interface Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, +> { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + connect: MethodFn; + delete: MethodFn; + get: MethodFn; + getConfig: () => Config; + head: MethodFn; + options: MethodFn; + patch: MethodFn; + post: MethodFn; + put: MethodFn; + request: RequestFn; + setConfig: (config: Config) => Config; + trace: MethodFn; +} + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit["headers"] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: + | "CONNECT" + | "DELETE" + | "GET" + | "HEAD" + | "OPTIONS" + | "PATCH" + | "POST" + | "PUT" + | "TRACE"; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} diff --git a/packages/api/src/client/index.ts b/packages/api/src/client/index.ts new file mode 100644 index 00000000000..58ea430d932 --- /dev/null +++ b/packages/api/src/client/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from "./sdk.gen.js"; +export * from "./types.gen.js"; diff --git a/packages/api/src/client/sdk.gen.ts b/packages/api/src/client/sdk.gen.ts new file mode 100644 index 00000000000..76f519d66ae --- /dev/null +++ b/packages/api/src/client/sdk.gen.ts @@ -0,0 +1,901 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + Client, + Options as ClientOptions, + TDataShape, +} from "./client/index.js"; +import { client as _heyApiClient } from "./client.gen.js"; +import type { + BuyTokenWithUsdData, + BuyTokenWithUsdErrors, + BuyTokenWithUsdResponses, + CreateWalletData, + CreateWalletErrors, + CreateWalletResponses, + GetContractEventsData, + GetContractEventsErrors, + GetContractEventsResponses, + GetContractTransactionsData, + GetContractTransactionsErrors, + GetContractTransactionsResponses, + GetTransactionByIdData, + GetTransactionByIdErrors, + GetTransactionByIdResponses, + GetV1ContractsData, + GetV1ContractsErrors, + GetV1ContractsResponses, + GetV1WalletsData, + GetV1WalletsErrors, + GetV1WalletsResponses, + GetWalletBalanceData, + GetWalletBalanceErrors, + GetWalletBalanceResponses, + GetWalletDetailsData, + GetWalletDetailsErrors, + GetWalletDetailsResponses, + GetWalletNftsData, + GetWalletNftsErrors, + GetWalletNftsResponses, + GetWalletTokensData, + GetWalletTokensErrors, + GetWalletTokensResponses, + GetWalletTransactionsData, + GetWalletTransactionsErrors, + GetWalletTransactionsResponses, + ListTransactionsData, + ListTransactionsErrors, + ListTransactionsResponses, + PostV1ContractsData, + PostV1ContractsErrors, + PostV1ContractsResponses, + ReadContractData, + ReadContractErrors, + ReadContractResponses, + SellTokenForUsdData, + SellTokenForUsdErrors, + SellTokenForUsdResponses, + SendOtpData, + SendOtpErrors, + SendOtpResponses, + SendTransactionsData, + SendTransactionsErrors, + SendTransactionsResponses, + SignMessageData, + SignMessageErrors, + SignMessageResponses, + SignTypedDataData, + SignTypedDataErrors, + SignTypedDataResponses, + TransferTokenWithUsdData, + TransferTokenWithUsdErrors, + TransferTokenWithUsdResponses, + VerifyOtpData, + VerifyOtpErrors, + VerifyOtpResponses, + WriteContractData, + WriteContractErrors, + WriteContractResponses, +} from "./types.gen.js"; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, +> = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +/** + * List Contracts + * Retrieves a list of all smart contracts imported by the authenticated client on the thirdweb dashboard. This endpoint provides access to contracts that have been added to your dashboard for management and interaction. Results include contract metadata, deployment information, and import timestamps. + * + * **Authentication**: This endpoint requires backend authentication using the `x-secret-key` header. The secret key should never be exposed publicly. + * + * **ABI Enrichment**: When `includeAbi=true`, the endpoint will fetch the contract ABI (Application Binary Interface) from the thirdweb contract service for each contract. The ABI contains function signatures, event definitions, and interface specifications required for contract interaction. + * + * **Metadata Enrichment**: When `includeMetadata=true`, the endpoint will fetch additional metadata from the thirdweb contract metadata service for each contract. This includes information like contract name, description, compilation details, and more. The metadata is returned in an optional `metadata` object within each contract. + */ +export const getV1Contracts = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).get< + GetV1ContractsResponses, + GetV1ContractsErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/contracts", + ...options, + }); +}; + +/** + * Deploy Contract + * Deploy a new smart contract to a blockchain network. This endpoint allows you to deploy contracts by providing the contract source URL, target chain, constructor parameters, and optional salt for deterministic deployment. + * + * **Authentication**: This endpoint requires backend authentication using the `x-secret-key` header. The secret key should never be exposed publicly. + */ +export const postV1Contracts = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + PostV1ContractsResponses, + PostV1ContractsErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/contracts", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Read Contract Methods + * Executes multiple read-only contract method calls in a single batch request. This endpoint allows efficient batch reading from multiple contracts on the same chain, significantly reducing the number of HTTP requests needed. Each call specifies the contract address, method signature, and optional parameters. Results are returned in the same order as the input calls, with individual success/failure status for each operation. + * + * **Authentication**: Pass `x-client-id` header for frontend usage from allowlisted origins or `x-secret-key` for backend usage. + */ +export const readContract = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + ReadContractResponses, + ReadContractErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/contracts/read", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Get Contract Transactions + * Retrieves transactions for a specific smart contract address across one or more blockchain networks. This endpoint provides comprehensive transaction data including block information, gas details, transaction status, and function calls. Results can be filtered, paginated, and sorted to meet specific requirements. + * + * **Authentication**: Pass `x-client-id` header for frontend usage from allowlisted origins or `x-secret-key` for backend usage. + */ +export const getContractTransactions = ( + options: Options, +) => { + return (options.client ?? _heyApiClient).get< + GetContractTransactionsResponses, + GetContractTransactionsErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/contracts/{address}/transactions", + ...options, + }); +}; + +/** + * Get Contract Events + * Retrieves events emitted by a specific smart contract address across one or more blockchain networks. This endpoint provides comprehensive event data including block information, transaction details, event topics, and optional ABI decoding. Results can be filtered, paginated, and sorted to meet specific requirements. + * + * **Authentication**: Pass `x-client-id` header for frontend usage from allowlisted origins or `x-secret-key` for backend usage. + */ +export const getContractEvents = ( + options: Options, +) => { + return (options.client ?? _heyApiClient).get< + GetContractEventsResponses, + GetContractEventsErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/contracts/{address}/events", + ...options, + }); +}; + +/** + * Write Contract Methods + * Executes write operations (transactions) on smart contracts. This is a convenience endpoint that simplifies contract interaction by accepting method signatures and parameters directly, without requiring manual transaction encoding. All calls are executed against the same contract address and chain, making it ideal for batch operations. + * + * **Authentication**: This endpoint requires project authentication and wallet authentication. For backend usage, use `x-secret-key` header. For frontend usage, use `x-client-id` + `Authorization: Bearer ` headers. + */ +export const writeContract = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + WriteContractResponses, + WriteContractErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/contracts/write", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Send OTP + * Send OTP for login authentication + */ +export const sendOtp = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + SendOtpResponses, + SendOtpErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/login/send-otp", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Verify OTP + * Verify OTP for login authentication + */ +export const verifyOtp = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + VerifyOtpResponses, + VerifyOtpErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/login/verify-otp", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * List Wallets + * Get all wallet details with pagination for your project. + * + * **Authentication**: This endpoint requires backend authentication using the `x-secret-key` header. The secret key should never be exposed publicly. + */ +export const getV1Wallets = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).get< + GetV1WalletsResponses, + GetV1WalletsErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/wallets", + ...options, + }); +}; + +/** + * Create Wallet + * Creates a server wallet from a unique identifier. If the wallet already exists, it will return the existing wallet. Requires project authentication with `x-secret-key` header. + */ +export const createWallet = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + CreateWalletResponses, + CreateWalletErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/wallets", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Get Wallet Native Balance + * Get native token balance for a wallet address across multiple blockchain networks. This endpoint retrieves native token balances (ETH, MATIC, BNB, etc.) for a given wallet address on multiple chains simultaneously, making it efficient for cross-chain native balance checking. + * + * **Authentication**: Pass `x-client-id` header for frontend usage from allowlisted origins or `x-secret-key` for backend usage. + */ +export const getWalletBalance = ( + options: Options, +) => { + return (options.client ?? _heyApiClient).get< + GetWalletBalanceResponses, + GetWalletBalanceErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/wallets/{address}/balance", + ...options, + }); +}; + +/** + * Get Wallet Transactions + * Retrieves transactions for a specific wallet address across one or more blockchain networks. This endpoint provides comprehensive transaction data including both incoming and outgoing transactions, with block information, gas details, transaction status, and function calls. Results can be filtered, paginated, and sorted to meet specific requirements. + * + * **Authentication**: Pass `x-client-id` header for frontend usage from allowlisted origins or `x-secret-key` for backend usage. + */ +export const getWalletTransactions = ( + options: Options, +) => { + return (options.client ?? _heyApiClient).get< + GetWalletTransactionsResponses, + GetWalletTransactionsErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/wallets/{address}/transactions", + ...options, + }); +}; + +/** + * Get Wallet Tokens + * Retrieves token balances for a specific wallet address across one or more blockchain networks. This endpoint provides comprehensive token data including ERC-20 tokens with their balances, metadata, and price information. Results can be filtered by chain and paginated to meet specific requirements. + * + * **Authentication**: Pass `x-client-id` header for frontend usage from allowlisted origins or `x-secret-key` for backend usage. + */ +export const getWalletTokens = ( + options: Options, +) => { + return (options.client ?? _heyApiClient).get< + GetWalletTokensResponses, + GetWalletTokensErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/wallets/{address}/tokens", + ...options, + }); +}; + +/** + * Get Wallet NFTs + * Retrieves NFTs for a specific wallet address across one or more blockchain networks. This endpoint provides comprehensive NFT data including metadata, attributes, and collection information. Results can be filtered by chain and paginated to meet specific requirements. + * + * **Authentication**: Pass `x-client-id` header for frontend usage from allowlisted origins or `x-secret-key` for backend usage. + */ +export const getWalletNfts = ( + options: Options, +) => { + return (options.client ?? _heyApiClient).get< + GetWalletNftsResponses, + GetWalletNftsErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/wallets/{address}/nfts", + ...options, + }); +}; + +/** + * Get Wallet Details + * Retrieves detailed user information for a previously created wallet. This endpoint fetches user data including authentication details and linked accounts. + * + * **Authentication**: This endpoint requires backend authentication using the `x-secret-key` header. The secret key should never be exposed publicly. + */ +export const getWalletDetails = ( + options: Options, +) => { + return (options.client ?? _heyApiClient).get< + GetWalletDetailsResponses, + GetWalletDetailsErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/wallets/{address}", + ...options, + }); +}; + +/** + * List Transactions + * Retrieves a paginated list of transactions associated with the authenticated client. Results are sorted by creation date in descending order (most recent first). Supports filtering by wallet address and pagination controls. + * + * **Authentication**: Pass `x-client-id` header for frontend usage from allowlisted origins or `x-secret-key` for backend usage. + */ +export const listTransactions = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).get< + ListTransactionsResponses, + ListTransactionsErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/transactions", + ...options, + }); +}; + +/** + * Send Transactions + * Submits a blockchain transaction. Supports three types of transactions: native token transfers, encoded transactions with custom data, and smart contract method calls. The transaction type is determined by the 'type' field in the request body. + * + * **Authentication**: This endpoint requires project authentication and wallet authentication. For backend usage, use `x-secret-key` header. For frontend usage, use `x-client-id` + `Authorization: Bearer ` headers. + */ +export const sendTransactions = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + SendTransactionsResponses, + SendTransactionsErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/transactions", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Get Transaction by ID + * Retrieves detailed information about a specific transaction using its unique identifier. Returns comprehensive transaction data including execution status, blockchain details, and any associated metadata. + * + * **Authentication**: Pass `x-client-id` header for frontend usage from allowlisted origins or `x-secret-key` for backend usage. + */ +export const getTransactionById = ( + options: Options, +) => { + return (options.client ?? _heyApiClient).get< + GetTransactionByIdResponses, + GetTransactionByIdErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/transactions/{transactionId}", + ...options, + }); +}; + +/** + * Sign Message + * Signs an arbitrary message using a connected wallet. This endpoint supports both text and hexadecimal message formats. The signing is performed using thirdweb Engine with smart account support for gasless transactions. + * + * **Authentication**: This endpoint requires project authentication and wallet authentication. For backend usage, use `x-secret-key` header. For frontend usage, use `x-client-id` + `Authorization: Bearer ` headers. + */ +export const signMessage = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + SignMessageResponses, + SignMessageErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/sign/message", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Sign Typed Data + * Signs structured data according to the EIP-712 standard. This is commonly used for secure message signing in DeFi protocols, NFT marketplaces, and other dApps that require structured data verification. The typed data includes domain separation and type definitions for enhanced security. + * + * **Authentication**: This endpoint requires project authentication and wallet authentication. For backend usage, use `x-secret-key` header. For frontend usage, use `x-client-id` + `Authorization: Bearer ` headers. + */ +export const signTypedData = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + SignTypedDataResponses, + SignTypedDataErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/sign/typed-data", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Buy Token + * Purchase tokens using a USD amount. The system will automatically handle wallet creation if needed, check your wallet balance, and execute the optimal cross-chain route to get the tokens you want. + * + * **Authentication**: This endpoint requires project authentication and wallet authentication. For backend usage, use `x-secret-key` header. For frontend usage, use `x-client-id` + `Authorization: Bearer ` headers. + */ +export const buyTokenWithUsd = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + BuyTokenWithUsdResponses, + BuyTokenWithUsdErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/tokens/buy", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Sell Token + * Sell tokens for a USD amount. The system will automatically handle the optimal cross-chain route to convert your tokens to USD. + * + * **Authentication**: This endpoint requires project authentication and wallet authentication. For backend usage, use `x-secret-key` header. For frontend usage, use `x-client-id` + `Authorization: Bearer ` headers. + */ +export const sellTokenForUsd = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + SellTokenForUsdResponses, + SellTokenForUsdErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/tokens/sell", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Transfer Token + * Transfer tokens worth a USD amount to another wallet address. The system will calculate the required token amount based on current prices and execute the transfer. + * + * **Authentication**: This endpoint requires project authentication and wallet authentication. For backend usage, use `x-secret-key` header. For frontend usage, use `x-client-id` + `Authorization: Bearer ` headers. + */ +export const transferTokenWithUsd = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + TransferTokenWithUsdResponses, + TransferTokenWithUsdErrors, + ThrowOnError + >({ + security: [ + { + name: "x-client-id", + type: "apiKey", + }, + { + name: "x-secret-key", + type: "apiKey", + }, + { + scheme: "bearer", + type: "http", + }, + ], + url: "/v1/tokens/transfer", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; diff --git a/packages/api/src/client/types.gen.ts b/packages/api/src/client/types.gen.ts new file mode 100644 index 00000000000..2a682f113d3 --- /dev/null +++ b/packages/api/src/client/types.gen.ts @@ -0,0 +1,2721 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type GetV1ContractsData = { + body?: never; + path?: never; + query?: { + /** + * Whether to include the contract ABI (Application Binary Interface) for each contract. When true, fetches the ABI from the thirdweb contract service and returns it in an optional 'abi' array within each contract. + */ + includeAbi?: boolean; + /** + * Whether to include contract metadata from the thirdweb contract metadata service. When true, fetches additional metadata for each contract and returns it in an optional 'metadata' object within each contract. + */ + includeMetadata?: boolean; + /** + * The number of contracts to return (default: 20, max: 100). + */ + limit?: number; + /** + * The page number for pagination (default: 1). + */ + page?: number; + }; + url: "/v1/contracts"; +}; + +export type GetV1ContractsErrors = { + /** + * Invalid request parameters + */ + 400: unknown; + /** + * Authentication required. The request must include a valid `x-secret-key` header for backend authentication. + */ + 401: unknown; + /** + * Rate limit exceeded + */ + 429: unknown; + /** + * Internal server error + */ + 500: unknown; +}; + +export type GetV1ContractsResponses = { + /** + * Successfully retrieved list of contracts + */ + 200: { + result: { + /** + * Array of contracts imported by the client. + */ + contracts: Array<{ + /** + * The contract ABI (Application Binary Interface) as an array of objects. Contains function definitions, event signatures, constructor parameters, error definitions, and other contract interface specifications. Only present when includeAbi=true. + */ + abi?: Array<{ + [key: string]: unknown; + }>; + /** + * The contract address. + */ + address: string; + /** + * The chain ID where the contract is deployed. + */ + chainId: number; + /** + * The date when the contract was deployed. + */ + deployedAt?: string; + /** + * The contract ID. + */ + id?: string; + /** + * The date when the contract was imported to the dashboard. + */ + importedAt: string; + /** + * Additional contract metadata from the thirdweb contract metadata service. Only present when includeMetadata=true. Contains compilation details, ABI information, and Solidity metadata. + */ + metadata?: { + /** + * The chain ID where the contract is deployed. + */ + chainId: number; + /** + * Array of compilation target paths. + */ + compilationTarget?: Array; + /** + * The contract address. + */ + contractAddress: string; + /** + * The implementation address for proxy contracts. + */ + implementationAddress?: string; + /** + * Whether the ABI is a composite of multiple contracts. + */ + isCompositeAbi?: boolean; + /** + * Whether the ABI is partial or complete. + */ + isPartialAbi?: boolean; + /** + * Solidity metadata object as defined in the Solidity documentation. Contains compiler settings, sources, and other compilation metadata. + */ + metadata?: { + [key: string]: unknown; + }; + /** + * The strategy used to retrieve the metadata. + */ + strategy?: string; + }; + /** + * The contract name, if available. + */ + name?: string; + /** + * The contract symbol, if available. + */ + symbol?: string; + /** + * The contract type (e.g., ERC20, ERC721, etc.). + */ + type?: string; + }>; + pagination: { + /** + * Whether there are more items available + */ + hasMore?: boolean; + /** + * Number of items per page + */ + limit: number; + /** + * Current page number + */ + page: number; + /** + * Total number of items available + */ + totalCount?: number; + }; + }; + }; +}; + +export type GetV1ContractsResponse = + GetV1ContractsResponses[keyof GetV1ContractsResponses]; + +export type PostV1ContractsData = { + body?: { + /** + * The blockchain network identifier. Common values include: 1 (Ethereum), 8453 (Base), 137 (Polygon), 56 (BSC), 43114 (Avalanche), 42161 (Arbitrum), 10 (Optimism). + */ + chainId: number; + /** + * Object containing constructor parameters for the contract deployment (e.g., { param1: 'value1', param2: 123 }). + */ + constructorParams?: { + [key: string]: unknown; + }; + /** + * The URL to the contract source from thirdweb.com (e.g., https://thirdweb.com/thirdweb.eth/TokenERC20). Version suffixes will be automatically stripped. + */ + contractUrl: string; + /** + * The wallet address that will deploy the contract. + */ + from: string; + /** + * Optional salt value for deterministic contract deployment. + */ + salt?: string; + }; + path?: never; + query?: never; + url: "/v1/contracts"; +}; + +export type PostV1ContractsErrors = { + /** + * Invalid request parameters + */ + 400: unknown; + /** + * Authentication required. The request must include a valid `x-secret-key` header for backend authentication. + */ + 401: unknown; + /** + * Rate limit exceeded + */ + 429: unknown; + /** + * Internal server error + */ + 500: unknown; +}; + +export type PostV1ContractsResponses = { + /** + * Contract deployed successfully + */ + 200: { + result: { + /** + * The deployed contract address. + */ + address: string; + /** + * The chain ID where the contract was deployed. + */ + chainId: number; + /** + * The unique identifier for the transaction that deployed the contract. Will not be returned if the contract was already deployed at the predicted address. + */ + transactionId?: string; + }; + }; +}; + +export type PostV1ContractsResponse = + PostV1ContractsResponses[keyof PostV1ContractsResponses]; + +export type ReadContractData = { + body?: { + /** + * Array of contract method calls to execute. Each call specifies a contract address, method signature, and optional parameters. + */ + calls: Array<{ + /** + * The smart contract address. Must be a valid Ethereum-compatible address (42 characters, starting with 0x). + */ + contractAddress: string; + /** + * The contract function signature to call (e.g., 'function approve(address spender, uint256 amount)' or `function balanceOf(address)`). Must start with 'function' followed by the function name and parameters as defined in the contract ABI. + */ + method: string; + /** + * Array of parameters to pass to the contract method, in the correct order and format. + */ + params?: Array; + /** + * Amount of native token to send with the transaction in wei. Required for payable methods. + */ + value?: string; + }>; + /** + * The blockchain network identifier. Common values include: 1 (Ethereum), 8453 (Base), 137 (Polygon), 56 (BSC), 43114 (Avalanche), 42161 (Arbitrum), 10 (Optimism). + */ + chainId: number; + }; + path?: never; + query?: never; + url: "/v1/contracts/read"; +}; + +export type ReadContractErrors = { + /** + * Invalid request parameters. This occurs when the chainId is not supported, contract addresses are invalid, function signatures are malformed, or the calls array is empty. + */ + 400: unknown; + /** + * Authentication required. The request must include a valid `x-client-id` header for frontend usage or x-secret-key for backend usage. + */ + 401: unknown; + /** + * Internal server error. This may occur due to engine connectivity issues, RPC node unavailability, or unexpected server errors. + */ + 500: unknown; +}; + +export type ReadContractResponses = { + /** + * Contract read operations completed successfully. Returns an array of results corresponding to each input call, including both successful and failed operations. + */ + 200: { + /** + * Array of results corresponding to each contract read call. Results are returned in the same order as the input calls. + */ + result: Array<{ + /** + * The result of the contract read operation. The type and format depend on the method's return value as defined in the contract ABI. + */ + data?: unknown; + /** + * Error message if the contract read operation failed. + */ + error?: string; + /** + * Indicates whether the contract read operation was successful. + */ + success: boolean; + }>; + }; +}; + +export type ReadContractResponse = + ReadContractResponses[keyof ReadContractResponses]; + +export type GetContractTransactionsData = { + body?: never; + path: { + /** + * The smart contract address. Must be a valid Ethereum-compatible address (42 characters, starting with 0x). + */ + address: string; + }; + query: { + /** + * Chain ID(s) to request transaction data for. You can specify multiple chain IDs by repeating the parameter, up to a maximum of 50. Example: ?chainId=1&chainId=137 + */ + chainId: number | Array; + /** + * Whether to enable ABI decoding of transaction data. When true, returns decoded function calls if ABI is available. + */ + decode?: boolean; + /** + * The number of transactions to return per chain (default: 20, max: 500). + */ + limit?: number; + /** + * The page number for pagination (default: 0, max: 20). + */ + page?: number; + /** + * Sort order - ascending or descending (default: desc). + */ + sortOrder?: "asc" | "desc"; + /** + * Start time for filtering transactions (Unix timestamp). Default is 3 months ago. + */ + startTime?: number; + }; + url: "/v1/contracts/{address}/transactions"; +}; + +export type GetContractTransactionsErrors = { + /** + * Invalid request parameters. This occurs when the contract address format is invalid, chainId array is empty or exceeds the maximum limit of 50, or pagination parameters are out of range. + */ + 400: unknown; + /** + * Authentication required. The request must include a valid `x-client-id` header for frontend usage or x-secret-key for backend usage. + */ + 401: unknown; + /** + * Contract not found or no transactions available for the specified contract address on the given blockchain networks. + */ + 404: unknown; + /** + * Internal server error. This may occur due to network connectivity issues, external service unavailability, or unexpected server errors. + */ + 500: unknown; +}; + +export type GetContractTransactionsResponses = { + /** + * Contract transactions retrieved successfully. Returns transaction data with metadata including pagination information and chain details. When decode=true, includes decoded function calls if ABI is available. + */ + 200: { + result: { + /** + * Array of contract transactions. + */ + data: Array<{ + /** + * The hash of the block containing this transaction. + */ + blockHash: string; + /** + * The block number containing this transaction. + */ + blockNumber: number; + /** + * The timestamp of the block (Unix timestamp). + */ + blockTimestamp: number; + /** + * The chain ID where the transaction occurred. + */ + chainId: string; + /** + * Contract address created if this was a contract creation transaction. + */ + contractAddress?: string; + /** + * Total gas used by all transactions in this block up to and including this one. + */ + cumulativeGasUsed?: number; + /** + * The transaction input data. + */ + data: string; + /** + * Decoded transaction data (only present when decode=true and ABI is available). + */ + decoded?: { + /** + * Object containing decoded function parameters. + */ + inputs: { + [key: string]: unknown; + }; + /** + * The function name. + */ + name: string; + /** + * The function signature. + */ + signature: string; + }; + /** + * The effective gas price paid (in wei as string). + */ + effectiveGasPrice?: string; + /** + * The address that initiated the transaction. + */ + fromAddress: string; + /** + * The function selector (first 4 bytes of the transaction data). + */ + functionSelector: string; + /** + * The gas limit for the transaction. + */ + gas: number; + /** + * The gas price used for the transaction (in wei as string). + */ + gasPrice: string; + /** + * The amount of gas used by the transaction. + */ + gasUsed?: number; + /** + * The transaction hash. + */ + hash: string; + /** + * Maximum fee per gas (EIP-1559). + */ + maxFeePerGas?: string; + /** + * Maximum priority fee per gas (EIP-1559). + */ + maxPriorityFeePerGas?: string; + /** + * The transaction nonce. + */ + nonce: number; + /** + * The transaction status (1 for success, 0 for failure). + */ + status: number; + /** + * The address that received the transaction. + */ + toAddress: string; + /** + * The index of the transaction within the block. + */ + transactionIndex: number; + /** + * The transaction type (0=legacy, 1=EIP-2930, 2=EIP-1559). + */ + transactionType?: number; + /** + * The value transferred in the transaction (in wei as string). + */ + value: string; + }>; + pagination: { + /** + * Whether there are more items available + */ + hasMore?: boolean; + /** + * Number of items per page + */ + limit: number; + /** + * Current page number + */ + page: number; + /** + * Total number of items available + */ + totalCount?: number; + }; + }; + }; +}; + +export type GetContractTransactionsResponse = + GetContractTransactionsResponses[keyof GetContractTransactionsResponses]; + +export type GetContractEventsData = { + body?: never; + path: { + /** + * The smart contract address. Must be a valid Ethereum-compatible address (42 characters, starting with 0x). + */ + address: string; + }; + query: { + /** + * Chain ID(s) to request event data for. You can specify multiple chain IDs by repeating the parameter, up to a maximum of 50. Example: ?chainId=1&chainId=137 + */ + chainId: number | Array; + /** + * Whether to enable ABI decoding of event data. When true, returns decoded event parameters if ABI is available. + */ + decode?: boolean; + /** + * Maximum number of events to return per chain. Must be between 1 and 500. Default is 20. + */ + limit?: number; + /** + * Page number for pagination (0-based). Must be between 0 and 20. Default is 0. + */ + page?: number; + /** + * Sort order for the results. Always sorts by block_number. 'desc' for newest first, 'asc' for oldest first. Default is 'desc'. + */ + sortOrder?: "asc" | "desc"; + /** + * Start time for filtering events (Unix timestamp). Default is 3 months ago. + */ + startTime?: number; + }; + url: "/v1/contracts/{address}/events"; +}; + +export type GetContractEventsErrors = { + /** + * Invalid request parameters. This occurs when the contract address format is invalid, chainId array is empty or exceeds the maximum limit of 50, or pagination parameters are out of range. + */ + 400: unknown; + /** + * Authentication required. The request must include a valid `x-client-id` header for frontend usage or x-secret-key for backend usage. + */ + 401: unknown; + /** + * Contract not found or no events available for the specified contract address on the given blockchain networks. + */ + 404: unknown; + /** + * Internal server error. This may occur due to network connectivity issues, external service unavailability, or unexpected server errors. + */ + 500: unknown; +}; + +export type GetContractEventsResponses = { + /** + * Contract events retrieved successfully. Returns event data with metadata including pagination information and chain details. When decode=true, includes decoded event parameters if ABI is available. + */ + 200: { + result: { + /** + * Array of contract events. + */ + events: Array<{ + /** + * The contract address that emitted the event. + */ + address: string; + /** + * The hash of the block containing this event. + */ + blockHash: string; + /** + * The block number where the event was emitted. + */ + blockNumber: number; + /** + * The timestamp of the block (Unix timestamp). + */ + blockTimestamp: number; + /** + * The chain ID where the event occurred. + */ + chainId: string; + /** + * The non-indexed event data as a hex string. + */ + data: string; + /** + * Decoded event data (only present when decode=true and ABI is available). + */ + decoded?: { + /** + * The event name. + */ + name: string; + /** + * Object containing decoded parameters. + */ + params: { + [key: string]: unknown; + }; + /** + * The event signature. + */ + signature: string; + }; + /** + * The index of the log within the transaction. + */ + logIndex: number; + /** + * Array of indexed event topics (including event signature). + */ + topics: Array; + /** + * The hash of the transaction containing this event. + */ + transactionHash: string; + /** + * The index of the transaction within the block. + */ + transactionIndex: number; + }>; + pagination: { + /** + * Whether there are more items available + */ + hasMore?: boolean; + /** + * Number of items per page + */ + limit: number; + /** + * Current page number + */ + page: number; + /** + * Total number of items available + */ + totalCount?: number; + }; + }; + }; +}; + +export type GetContractEventsResponse = + GetContractEventsResponses[keyof GetContractEventsResponses]; + +export type WriteContractData = { + body?: { + /** + * Array of contract method calls to execute. Each call specifies a contract address, method signature, and optional parameters. + */ + calls: Array<{ + /** + * The smart contract address. Must be a valid Ethereum-compatible address (42 characters, starting with 0x). + */ + contractAddress: string; + /** + * The contract function signature to call (e.g., 'function approve(address spender, uint256 amount)' or `function balanceOf(address)`). Must start with 'function' followed by the function name and parameters as defined in the contract ABI. + */ + method: string; + /** + * Array of parameters to pass to the contract method, in the correct order and format. + */ + params?: Array; + /** + * Amount of native token to send with the transaction in wei. Required for payable methods. + */ + value?: string; + }>; + /** + * The blockchain network identifier. Common values include: 1 (Ethereum), 8453 (Base), 137 (Polygon), 56 (BSC), 43114 (Avalanche), 42161 (Arbitrum), 10 (Optimism). + */ + chainId: number; + /** + * The wallet address that will send the transaction. + */ + from: string; + }; + path?: never; + query?: never; + url: "/v1/contracts/write"; +}; + +export type WriteContractErrors = { + /** + * Invalid request parameters. This occurs when contract parameters are malformed, method signatures are invalid, insufficient balance, or unsupported contract methods. + */ + 400: unknown; + /** + * Authentication required. For backend usage, include `x-secret-key` header. For frontend usage, include `x-client-id` + `Authorization: Bearer ` headers. + */ + 401: unknown; + /** + * Contract not found. The specified contract address does not exist on the given blockchain network or is not accessible. + */ + 404: unknown; + /** + * Internal server error. This may occur due to blockchain connectivity issues, gas estimation failures, contract execution errors, or unexpected server errors. + */ + 500: unknown; +}; + +export type WriteContractResponses = { + /** + * Contract write operations submitted successfully. Returns transaction IDs for tracking and monitoring. + */ + 200: { + result: { + /** + * Array of unique identifiers for the submitted transactions. Use these to track transaction status. + */ + transactionIds: Array; + }; + }; +}; + +export type WriteContractResponse = + WriteContractResponses[keyof WriteContractResponses]; + +export type SendOtpData = { + body?: { + email?: string; + phone?: string; + }; + path?: never; + query?: never; + url: "/v1/login/send-otp"; +}; + +export type SendOtpErrors = { + /** + * Invalid request parameters + */ + 400: unknown; + /** + * Internal server error + */ + 500: unknown; +}; + +export type SendOtpResponses = { + /** + * OTP sent successfully + */ + 200: { + email?: string; + phone?: string; + }; +}; + +export type SendOtpResponse = SendOtpResponses[keyof SendOtpResponses]; + +export type VerifyOtpData = { + body?: { + code: string; + email?: string; + phone?: string; + }; + path?: never; + query?: never; + url: "/v1/login/verify-otp"; +}; + +export type VerifyOtpErrors = { + /** + * Invalid OTP or request parameters + */ + 400: unknown; + /** + * Internal server error + */ + 500: unknown; +}; + +export type VerifyOtpResponses = { + /** + * OTP verified successfully + */ + 200: { + isNewUser: boolean; + token: string; + type: string; + }; +}; + +export type VerifyOtpResponse = VerifyOtpResponses[keyof VerifyOtpResponses]; + +export type GetV1WalletsData = { + body?: never; + path?: never; + query?: { + limit?: number; + page?: number; + /** + * Type of wallet to fetch, default is user + */ + type?: "user" | "server"; + }; + url: "/v1/wallets"; +}; + +export type GetV1WalletsErrors = { + /** + * Authentication required. The request must include a valid `x-secret-key` header for backend authentication. + */ + 401: unknown; + /** + * Internal server error. This may occur due to service unavailability or unexpected server errors. + */ + 500: unknown; +}; + +export type GetV1WalletsResponses = { + /** + * Returns a list of wallet addresses, smart wallet addresses, and auth details. + */ + 200: { + result: { + /** + * Pagination information + */ + pagination: { + /** + * Whether there are more items available + */ + hasMore?: boolean; + /** + * Number of items per page + */ + limit: number; + /** + * Current page number + */ + page: number; + /** + * Total number of items available + */ + totalCount?: number; + }; + /** + * Array of user or server wallets + */ + wallets: Array<{ + /** + * The EOA (Externally Owned Account) address of the wallet. This is the traditional wallet address. + */ + address: string; + /** + * The date and time the wallet was created + */ + createdAt: string; + /** + * The profiles linked to the wallet, can be email, phone, google etc, or backend for developer created wallets + */ + profiles: Array< + | { + email: string; + emailVerified: boolean; + familyName?: string; + givenName?: string; + hd: string; + id: string; + locale: string; + name?: string; + picture: string; + type: "google"; + } + | { + email?: string; + firstName?: string; + id: string; + lastName?: string; + name?: string; + picture?: string; + type: "facebook"; + } + | { + email?: string; + emailVerified: boolean; + id: string; + isPrivateEmail: boolean; + type: "apple"; + } + | { + avatar?: string; + id: string; + name?: string; + type: "github"; + username: string; + } + | { + avatar: string; + email?: string; + emailVerified: boolean; + id: string; + type: "discord"; + username: string; + } + | { + avatar?: string; + id: string; + name: string; + type: "coinbase"; + } + | { + id: string; + name: string; + type: "x"; + username: string; + } + | { + avatar?: string; + id: string; + metadata: { + avatar: { + large?: string; + medium?: string; + small?: string; + }; + personaname?: string; + profileurl?: string; + realname?: string; + }; + type: "steam"; + username?: string; + } + | { + firstName?: string; + id: string; + lastName?: string; + picture?: string; + type: "telegram"; + username?: string; + } + | { + avatar?: string; + description?: string; + email?: string; + id: string; + type: "twitch"; + username: string; + } + | { + avatar?: string; + id: string; + type: "line"; + username?: string; + } + | { + fid: string; + id: string; + type: "farcaster"; + walletAddress?: string; + } + | { + algorithm: string; + credentialId: string; + publicKey: string; + type: "passkey"; + } + | { + email: string; + id: string; + type: "email"; + } + | { + id: string; + pregeneratedIdentifier: string; + type: "pre_generation"; + } + | { + id: string; + phone: string; + type: "phone"; + } + | { + id: string; + type: "siwe"; + walletAddress: string; + } + | { + id: string; + type: "guest"; + } + | { + id: string; + type: "backend"; + } + | { + identifier: string; + type: "server"; + } + | { + authProviderId?: string; + email?: string; + id: string; + phone?: string; + type: "custom_jwt"; + walletAddress?: string; + } + | { + authProviderId?: string; + email?: string; + id: string; + phone?: string; + type: "custom_auth_endpoint"; + walletAddress?: string; + } + >; + /** + * The smart wallet address with EIP-4337 support. This address enables gasless transactions and advanced account features. + */ + smartWalletAddress?: string; + }>; + }; + }; +}; + +export type GetV1WalletsResponse = + GetV1WalletsResponses[keyof GetV1WalletsResponses]; + +export type CreateWalletData = { + body?: { + /** + * Unique identifier for wallet creation or retrieval. Can be user ID, email, or any unique string. The same identifier will always return the same wallet. + */ + identifier: string; + }; + path?: never; + query?: never; + url: "/v1/wallets"; +}; + +export type CreateWalletErrors = { + /** + * Invalid request parameters. This occurs when the identifier format is invalid or required parameters are missing. + */ + 400: unknown; + /** + * Authentication required. For backend usage, include `x-secret-key` header. + */ + 401: unknown; + /** + * Internal server error. This may occur due to wallet service unavailability, smart account deployment issues, or unexpected server errors. + */ + 500: unknown; +}; + +export type CreateWalletResponses = { + /** + * Wallet created or connected successfully. Returns wallet addresses for subsequent operations. + */ + 200: { + result: { + /** + * The EOA (Externally Owned Account) address of the wallet. This is the traditional wallet address. + */ + address: string; + /** + * The date and time the wallet was created + */ + createdAt: string; + /** + * The profiles linked to the wallet, can be email, phone, google etc, or backend for developer created wallets + */ + profiles: Array< + | { + email: string; + emailVerified: boolean; + familyName?: string; + givenName?: string; + hd: string; + id: string; + locale: string; + name?: string; + picture: string; + type: "google"; + } + | { + email?: string; + firstName?: string; + id: string; + lastName?: string; + name?: string; + picture?: string; + type: "facebook"; + } + | { + email?: string; + emailVerified: boolean; + id: string; + isPrivateEmail: boolean; + type: "apple"; + } + | { + avatar?: string; + id: string; + name?: string; + type: "github"; + username: string; + } + | { + avatar: string; + email?: string; + emailVerified: boolean; + id: string; + type: "discord"; + username: string; + } + | { + avatar?: string; + id: string; + name: string; + type: "coinbase"; + } + | { + id: string; + name: string; + type: "x"; + username: string; + } + | { + avatar?: string; + id: string; + metadata: { + avatar: { + large?: string; + medium?: string; + small?: string; + }; + personaname?: string; + profileurl?: string; + realname?: string; + }; + type: "steam"; + username?: string; + } + | { + firstName?: string; + id: string; + lastName?: string; + picture?: string; + type: "telegram"; + username?: string; + } + | { + avatar?: string; + description?: string; + email?: string; + id: string; + type: "twitch"; + username: string; + } + | { + avatar?: string; + id: string; + type: "line"; + username?: string; + } + | { + fid: string; + id: string; + type: "farcaster"; + walletAddress?: string; + } + | { + algorithm: string; + credentialId: string; + publicKey: string; + type: "passkey"; + } + | { + email: string; + id: string; + type: "email"; + } + | { + id: string; + pregeneratedIdentifier: string; + type: "pre_generation"; + } + | { + id: string; + phone: string; + type: "phone"; + } + | { + id: string; + type: "siwe"; + walletAddress: string; + } + | { + id: string; + type: "guest"; + } + | { + id: string; + type: "backend"; + } + | { + identifier: string; + type: "server"; + } + | { + authProviderId?: string; + email?: string; + id: string; + phone?: string; + type: "custom_jwt"; + walletAddress?: string; + } + | { + authProviderId?: string; + email?: string; + id: string; + phone?: string; + type: "custom_auth_endpoint"; + walletAddress?: string; + } + >; + /** + * The smart wallet address with EIP-4337 support. This address enables gasless transactions and advanced account features. + */ + smartWalletAddress?: string; + }; + }; +}; + +export type CreateWalletResponse = + CreateWalletResponses[keyof CreateWalletResponses]; + +export type GetWalletBalanceData = { + body?: never; + path: { + /** + * A valid Ethereum address, which is a 40-character hexadecimal string (0x prefixed) representing an account on the Ethereum blockchain. + */ + address: string; + }; + query: { + /** + * Chain ID(s) to request balance data for. You can specify multiple chain IDs by repeating the parameter, up to a maximum of 50. Example: ?chainId=1&chainId=137 + */ + chainId: number | Array; + }; + url: "/v1/wallets/{address}/balance"; +}; + +export type GetWalletBalanceErrors = { + /** + * Invalid request parameters. This occurs when the wallet address format is invalid, chainId array is empty or exceeds the maximum limit of 50, or chain IDs are invalid. + */ + 400: unknown; + /** + * Authentication required. The request must include a valid `x-client-id` header for frontend usage or x-secret-key for backend usage. + */ + 401: unknown; + /** + * Internal server error. This may occur due to blockchain connectivity issues, RPC service unavailability, or unexpected server errors. + */ + 500: unknown; +}; + +export type GetWalletBalanceResponses = { + /** + * Wallet native balances retrieved successfully. Returns detailed native token balance information for each chain including token metadata and formatted values. + */ + 200: { + result: Array<{ + /** + * The blockchain network ID + */ + chainId: number; + /** + * Number of decimal places for the token + */ + decimals: number; + /** + * Human-readable balance formatted with appropriate decimal places + */ + displayValue: string; + /** + * The token name (e.g., 'Ether', 'USD Coin') + */ + name: string; + /** + * The token symbol (e.g., 'ETH', 'USDC') + */ + symbol: string; + /** + * The token contract address. Returns zero address (0x0...0) for native tokens. + */ + tokenAddress: string; + /** + * Raw balance value as string in smallest unit (wei for ETH, etc.) + */ + value: string; + }>; + }; +}; + +export type GetWalletBalanceResponse = + GetWalletBalanceResponses[keyof GetWalletBalanceResponses]; + +export type GetWalletTransactionsData = { + body?: never; + path: { + /** + * A valid Ethereum address, which is a 40-character hexadecimal string (0x prefixed) representing an account on the Ethereum blockchain. + */ + address: string; + }; + query: { + /** + * Chain ID(s) to request transaction data for. You can specify multiple chain IDs by repeating the parameter, up to a maximum of 50. Example: ?chainId=1&chainId=137 + */ + chainId: number | Array; + /** + * Whether to enable ABI decoding of transaction data. When true, returns decoded function calls if ABI is available. + */ + decode?: boolean; + /** + * The number of transactions to return per chain (default: 20, max: 500). + */ + limit?: number; + /** + * The page number for pagination (default: 0, max: 20). + */ + page?: number; + /** + * Sort order - ascending or descending (default: desc). + */ + sortOrder?: "asc" | "desc"; + /** + * Start time for filtering transactions (Unix timestamp). Default is 3 months ago. + */ + startTime?: number; + }; + url: "/v1/wallets/{address}/transactions"; +}; + +export type GetWalletTransactionsErrors = { + /** + * Invalid request parameters. This occurs when the wallet address format is invalid, chainId array is empty or exceeds the maximum limit of 50, or pagination parameters are out of range. + */ + 400: unknown; + /** + * Authentication required. The request must include a valid `x-client-id` header for frontend usage or x-secret-key for backend usage. + */ + 401: unknown; + /** + * Wallet not found or no transactions available for the specified wallet address on the given blockchain networks. + */ + 404: unknown; + /** + * Internal server error. This may occur due to network connectivity issues, external service unavailability, or unexpected server errors. + */ + 500: unknown; +}; + +export type GetWalletTransactionsResponses = { + /** + * Wallet transactions retrieved successfully. Returns transaction data with metadata including pagination information and chain details. When decode=true, includes decoded function calls if ABI is available. + */ + 200: { + result: { + pagination: { + /** + * Whether there are more items available + */ + hasMore?: boolean; + /** + * Number of items per page + */ + limit: number; + /** + * Current page number + */ + page: number; + /** + * Total number of items available + */ + totalCount?: number; + }; + /** + * Array of wallet transactions. + */ + transactions: Array<{ + /** + * The hash of the block containing this transaction. + */ + blockHash: string; + /** + * The block number containing this transaction. + */ + blockNumber: number; + /** + * The timestamp of the block (Unix timestamp). + */ + blockTimestamp: number; + /** + * The chain ID where the transaction occurred. + */ + chainId: string; + /** + * Contract address created if this was a contract creation transaction. + */ + contractAddress?: string; + /** + * Total gas used by all transactions in this block up to and including this one. + */ + cumulativeGasUsed?: number; + /** + * The transaction input data. + */ + data: string; + /** + * Decoded transaction data (only present when decode=true and ABI is available). + */ + decoded?: { + /** + * Object containing decoded function parameters. + */ + inputs: { + [key: string]: unknown; + }; + /** + * The function name. + */ + name: string; + /** + * The function signature. + */ + signature: string; + }; + /** + * The effective gas price paid (in wei as string). + */ + effectiveGasPrice?: string; + /** + * The address that initiated the transaction. + */ + fromAddress: string; + /** + * The function selector (first 4 bytes of the transaction data). + */ + functionSelector: string; + /** + * The gas limit for the transaction. + */ + gas: number; + /** + * The gas price used for the transaction (in wei as string). + */ + gasPrice: string; + /** + * The amount of gas used by the transaction. + */ + gasUsed?: number; + /** + * The transaction hash. + */ + hash: string; + /** + * Maximum fee per gas (EIP-1559). + */ + maxFeePerGas?: string; + /** + * Maximum priority fee per gas (EIP-1559). + */ + maxPriorityFeePerGas?: string; + /** + * The transaction nonce. + */ + nonce: number; + /** + * The transaction status (1 for success, 0 for failure). + */ + status: number; + /** + * The address that received the transaction. + */ + toAddress: string; + /** + * The index of the transaction within the block. + */ + transactionIndex: number; + /** + * The transaction type (0=legacy, 1=EIP-2930, 2=EIP-1559). + */ + transactionType?: number; + /** + * The value transferred in the transaction (in wei as string). + */ + value: string; + }>; + }; + }; +}; + +export type GetWalletTransactionsResponse = + GetWalletTransactionsResponses[keyof GetWalletTransactionsResponses]; + +export type GetWalletTokensData = { + body?: never; + path: { + /** + * A valid Ethereum address, which is a 40-character hexadecimal string (0x prefixed) representing an account on the Ethereum blockchain. + */ + address: string; + }; + query: { + /** + * Chain ID(s) to request token data for. You can specify multiple chain IDs by repeating the parameter, up to a maximum of 50. Example: ?chainId=1&chainId=137 + */ + chainId: number | Array; + /** + * The number of tokens to return per chain (default: 20, max: 500). + */ + limit?: number; + /** + * The page number for pagination (default: 0, max: 20). + */ + page?: number; + }; + url: "/v1/wallets/{address}/tokens"; +}; + +export type GetWalletTokensErrors = { + /** + * Invalid request parameters. This occurs when the wallet address format is invalid, chainId array is empty or exceeds the maximum limit of 50, or pagination parameters are out of range. + */ + 400: unknown; + /** + * Authentication required. The request must include a valid `x-client-id` header for frontend usage or x-secret-key for backend usage. + */ + 401: unknown; + /** + * Wallet not found or no tokens available for the specified wallet address on the given blockchain networks. + */ + 404: unknown; + /** + * Internal server error. This may occur due to network connectivity issues, external service unavailability, or unexpected server errors. + */ + 500: unknown; +}; + +export type GetWalletTokensResponses = { + /** + * Wallet tokens retrieved successfully. Returns token data with metadata including pagination information and chain details. Includes token balances, metadata, and price information when available. + */ + 200: { + result: { + pagination: { + /** + * Whether there are more items available + */ + hasMore?: boolean; + /** + * Number of items per page + */ + limit: number; + /** + * Current page number + */ + page: number; + /** + * Total number of items available + */ + totalCount?: number; + }; + /** + * Array of wallet tokens. + */ + tokens: Array<{ + /** + * The token balance as a string + */ + balance: string; + /** + * The chain ID of the token + */ + chain_id: number; + /** + * The number of decimal places + */ + decimals?: number; + /** + * The token name + */ + name?: string; + /** + * Price data for the token + */ + price_data?: { + /** + * The circulating supply of the token + */ + circulating_supply?: number; + /** + * The market cap of the token in USD + */ + market_cap_usd?: number; + /** + * The percentage change of the token in the last 24 hours + */ + percent_change_24h?: number; + /** + * The timestamp of the latest price update + */ + price_timestamp?: string; + /** + * The price of the token in USD + */ + price_usd?: number; + /** + * The total supply of the token + */ + total_supply?: number; + /** + * The volume of the token in USD + */ + volume_24h_usd?: number; + }; + /** + * The token symbol + */ + symbol?: string; + /** + * The contract address of the token + */ + token_address: string; + }>; + }; + }; +}; + +export type GetWalletTokensResponse = + GetWalletTokensResponses[keyof GetWalletTokensResponses]; + +export type GetWalletNftsData = { + body?: never; + path: { + /** + * A valid Ethereum address, which is a 40-character hexadecimal string (0x prefixed) representing an account on the Ethereum blockchain. + */ + address: string; + }; + query: { + /** + * Chain ID(s) to request NFT data for. You can specify multiple chain IDs by repeating the parameter, up to a maximum of 50. Example: ?chainId=1&chainId=137 + */ + chainId: number | Array; + /** + * The number of NFTs to return per chain (default: 20, max: 500). + */ + limit?: number; + /** + * The page number for pagination (default: 0, max: 20). + */ + page?: number; + }; + url: "/v1/wallets/{address}/nfts"; +}; + +export type GetWalletNftsErrors = { + /** + * Invalid request parameters. This occurs when the wallet address format is invalid, chainId array is empty or exceeds the maximum limit of 50, or pagination parameters are out of range. + */ + 400: unknown; + /** + * Authentication required. The request must include a valid `x-client-id` header for frontend usage or x-secret-key for backend usage. + */ + 401: unknown; + /** + * Wallet not found or no NFTs available for the specified wallet address on the given blockchain networks. + */ + 404: unknown; + /** + * Internal server error. This may occur due to network connectivity issues, external service unavailability, or unexpected server errors. + */ + 500: unknown; +}; + +export type GetWalletNftsResponses = { + /** + * Wallet NFTs retrieved successfully. Returns NFT data with metadata including pagination information and chain details. Includes NFT metadata, attributes, and collection information when available. + */ + 200: { + result: { + /** + * Array of wallet NFTs. + */ + nfts: Array<{ + /** + * The animation URL of the NFT + */ + animation_url?: string; + /** + * The attributes/traits of the NFT + */ + attributes?: Array<{ + /** + * The display type + */ + display_type?: string; + /** + * The trait type + */ + trait_type?: string; + /** + * The trait value + */ + value?: string | number; + }>; + /** + * The chain ID of the NFT + */ + chain_id: number; + /** + * Collection information + */ + collection?: { + /** + * The collection description + */ + description?: string; + /** + * The collection external URL + */ + external_url?: string; + /** + * The collection image URL + */ + image?: string; + /** + * The collection name + */ + name?: string; + }; + /** + * The description of the NFT + */ + description?: string; + /** + * The external URL of the NFT + */ + external_url?: string; + /** + * The image URL of the NFT + */ + image_url?: string; + /** + * Additional metadata for the NFT + */ + metadata?: { + [key: string]: unknown; + }; + /** + * The name of the NFT + */ + name?: string; + /** + * The contract address of the NFT collection + */ + token_address: string; + /** + * The token ID of the NFT + */ + token_id: string; + }>; + pagination: { + /** + * Whether there are more items available + */ + hasMore?: boolean; + /** + * Number of items per page + */ + limit: number; + /** + * Current page number + */ + page: number; + /** + * Total number of items available + */ + totalCount?: number; + }; + }; + }; +}; + +export type GetWalletNftsResponse = + GetWalletNftsResponses[keyof GetWalletNftsResponses]; + +export type GetWalletDetailsData = { + body?: never; + path: { + /** + * A valid Ethereum address, which is a 40-character hexadecimal string (0x prefixed) representing an account on the Ethereum blockchain. + */ + address: string; + }; + query?: never; + url: "/v1/wallets/{address}"; +}; + +export type GetWalletDetailsErrors = { + /** + * Invalid request parameters. This occurs when the wallet address format is invalid or malformed. + */ + 400: unknown; + /** + * Authentication required. The request must include a valid `x-secret-key` header for backend authentication. + */ + 401: unknown; + /** + * Wallet not found. The specified wallet address does not exist or is not associated with any user in the system. + */ + 404: unknown; + /** + * Internal server error. This may occur due to service unavailability, network connectivity issues, or unexpected server errors. + */ + 500: unknown; +}; + +export type GetWalletDetailsResponses = { + /** + * Wallet details retrieved successfully. Returns comprehensive user information including authentication details and linked accounts. + */ + 200: { + result: { + /** + * The EOA (Externally Owned Account) address of the wallet. This is the traditional wallet address. + */ + address: string; + /** + * The date and time the wallet was created + */ + createdAt: string; + /** + * The profiles linked to the wallet, can be email, phone, google etc, or backend for developer created wallets + */ + profiles: Array< + | { + email: string; + emailVerified: boolean; + familyName?: string; + givenName?: string; + hd: string; + id: string; + locale: string; + name?: string; + picture: string; + type: "google"; + } + | { + email?: string; + firstName?: string; + id: string; + lastName?: string; + name?: string; + picture?: string; + type: "facebook"; + } + | { + email?: string; + emailVerified: boolean; + id: string; + isPrivateEmail: boolean; + type: "apple"; + } + | { + avatar?: string; + id: string; + name?: string; + type: "github"; + username: string; + } + | { + avatar: string; + email?: string; + emailVerified: boolean; + id: string; + type: "discord"; + username: string; + } + | { + avatar?: string; + id: string; + name: string; + type: "coinbase"; + } + | { + id: string; + name: string; + type: "x"; + username: string; + } + | { + avatar?: string; + id: string; + metadata: { + avatar: { + large?: string; + medium?: string; + small?: string; + }; + personaname?: string; + profileurl?: string; + realname?: string; + }; + type: "steam"; + username?: string; + } + | { + firstName?: string; + id: string; + lastName?: string; + picture?: string; + type: "telegram"; + username?: string; + } + | { + avatar?: string; + description?: string; + email?: string; + id: string; + type: "twitch"; + username: string; + } + | { + avatar?: string; + id: string; + type: "line"; + username?: string; + } + | { + fid: string; + id: string; + type: "farcaster"; + walletAddress?: string; + } + | { + algorithm: string; + credentialId: string; + publicKey: string; + type: "passkey"; + } + | { + email: string; + id: string; + type: "email"; + } + | { + id: string; + pregeneratedIdentifier: string; + type: "pre_generation"; + } + | { + id: string; + phone: string; + type: "phone"; + } + | { + id: string; + type: "siwe"; + walletAddress: string; + } + | { + id: string; + type: "guest"; + } + | { + id: string; + type: "backend"; + } + | { + identifier: string; + type: "server"; + } + | { + authProviderId?: string; + email?: string; + id: string; + phone?: string; + type: "custom_jwt"; + walletAddress?: string; + } + | { + authProviderId?: string; + email?: string; + id: string; + phone?: string; + type: "custom_auth_endpoint"; + walletAddress?: string; + } + >; + /** + * The smart wallet address with EIP-4337 support. This address enables gasless transactions and advanced account features. + */ + smartWalletAddress?: string; + }; + }; +}; + +export type GetWalletDetailsResponse = + GetWalletDetailsResponses[keyof GetWalletDetailsResponses]; + +export type ListTransactionsData = { + body?: never; + path?: never; + query?: { + /** + * Filter transactions by sender wallet address. + */ + from?: string; + /** + * Number of transactions to return per page (1-100). + */ + limit?: number; + /** + * Page number for pagination, starting from 1. + */ + page?: number; + }; + url: "/v1/transactions"; +}; + +export type ListTransactionsErrors = { + /** + * Invalid request parameters. This occurs when pagination parameters are out of range or wallet address format is invalid. + */ + 400: unknown; + /** + * Authentication required. The request must include a valid `x-client-id` header for frontend usage or x-secret-key for backend usage. + */ + 401: unknown; + /** + * Internal server error. This may occur due to engine connectivity issues, database unavailability, or unexpected server errors. + */ + 500: unknown; +}; + +export type ListTransactionsResponses = { + /** + * Transactions retrieved successfully. Returns a paginated list of transactions with metadata including creation and confirmation timestamps. + */ + 200: { + result: { + pagination: { + /** + * Whether there are more items available + */ + hasMore?: boolean; + /** + * Number of items per page + */ + limit: number; + /** + * Current page number + */ + page: number; + /** + * Total number of items available + */ + totalCount?: number; + }; + transactions: Array<{ + /** + * Index within transaction batch + */ + batchIndex: number; + /** + * ISO timestamp when transaction was cancelled, if applicable + */ + cancelledAt: string; + /** + * Blockchain network identifier as string + */ + chainId: string; + /** + * Client identifier that initiated the transaction + */ + clientId: string; + /** + * ISO timestamp when transaction was confirmed on-chain + */ + confirmedAt: string; + /** + * Block number where transaction was confirmed + */ + confirmedAtBlockNumber: string; + /** + * ISO timestamp when transaction was created + */ + createdAt: string; + /** + * Additional metadata and enriched transaction information + */ + enrichedData?: unknown; + /** + * Error message if transaction failed + */ + errorMessage: string; + /** + * Parameters used for transaction execution + */ + executionParams?: unknown; + /** + * Result data from transaction execution + */ + executionResult?: unknown; + /** + * Sender wallet address + */ + from: string; + /** + * Unique transaction identifier + */ + id: string; + /** + * On-chain transaction hash once confirmed + */ + transactionHash: string; + /** + * Original transaction parameters and data + */ + transactionParams?: unknown; + }>; + }; + }; +}; + +export type ListTransactionsResponse = + ListTransactionsResponses[keyof ListTransactionsResponses]; + +export type SendTransactionsData = { + /** + * Transaction Request + * Request object containing an array of blockchain transactions to execute. All transactions must use the same from address and chainId. Supports batching multiple transactions of different types in a single request. + */ + body?: { + /** + * The blockchain network identifier where all transactions will be executed. + */ + chainId: number; + /** + * The wallet address that will send the transaction. + */ + from: string; + /** + * Transaction + * A blockchain transaction of one of three supported types: contract call, encoded transaction, or native token transfer. + */ + transactions: Array< + | { + /** + * The smart contract address to interact with. + */ + contractAddress: string; + /** + * The contract function signature to call (e.g., 'function approve(address spender, uint256 amount)'). Must start with 'function' followed by the function name and parameters as defined in the contract ABI. + */ + method: string; + /** + * Array of parameters to pass to the contract method, in the correct order and format. + */ + params?: Array; + /** + * Transaction type for smart contract method calls + */ + type: "contract-call"; + /** + * Amount of native token to send with the transaction in wei. Required for payable methods. + */ + value?: string; + } + | { + /** + * Transaction data in hexadecimal format for contract interactions or custom payloads. + */ + data: string; + /** + * The target address for the encoded transaction. + */ + to: string; + /** + * Transaction type for pre-encoded transaction data + */ + type: "encoded"; + /** + * Amount of native token to send in wei (smallest unit). Use '0' or omit for non-value transactions. + */ + value?: string; + } + | { + /** + * The recipient wallet address. + */ + to: string; + /** + * Transaction type for native token transfers + */ + type: "native-transfer"; + /** + * Amount of native token to send in wei (smallest unit). + */ + value: string; + } + >; + }; + path?: never; + query?: never; + url: "/v1/transactions"; +}; + +export type SendTransactionsErrors = { + /** + * Invalid request parameters. This occurs when transaction parameters are malformed, insufficient balance, invalid contract data, or unsupported transaction type. + */ + 400: unknown; + /** + * Authentication required. For backend usage, include `x-secret-key` header. For frontend usage, include `x-client-id` + `Authorization: Bearer ` headers. + */ + 401: unknown; + /** + * Internal server error. This may occur due to blockchain connectivity issues, gas estimation failures, contract execution errors, or unexpected server errors. + */ + 500: unknown; +}; + +export type SendTransactionsResponses = { + /** + * Transaction submitted successfully. Returns the transaction ID for tracking and monitoring. + */ + 200: { + result: { + /** + * Array of unique identifiers for the submitted transactions. Use these to track transaction status. + */ + transactionIds: Array; + }; + }; +}; + +export type SendTransactionsResponse = + SendTransactionsResponses[keyof SendTransactionsResponses]; + +export type GetTransactionByIdData = { + body?: never; + path: { + /** + * Unique identifier of the transaction to retrieve. + */ + transactionId: string; + }; + query?: never; + url: "/v1/transactions/{transactionId}"; +}; + +export type GetTransactionByIdErrors = { + /** + * Invalid request parameters. This occurs when the transaction ID format is invalid or malformed. + */ + 400: unknown; + /** + * Authentication required. The request must include a valid `x-client-id` header for frontend usage or x-secret-key for backend usage. + */ + 401: unknown; + /** + * Transaction not found. The specified transaction ID does not exist or is not associated with the authenticated client. + */ + 404: unknown; + /** + * Internal server error. This may occur due to engine connectivity issues, database unavailability, or unexpected server errors. + */ + 500: unknown; +}; + +export type GetTransactionByIdResponses = { + /** + * Transaction details retrieved successfully. Returns comprehensive transaction information including status, blockchain details, and execution metadata. + */ + 200: { + result: { + /** + * Index within transaction batch + */ + batchIndex: number; + /** + * ISO timestamp when transaction was cancelled, if applicable + */ + cancelledAt: string; + /** + * Blockchain network identifier as string + */ + chainId: string; + /** + * Client identifier that initiated the transaction + */ + clientId: string; + /** + * ISO timestamp when transaction was confirmed on-chain + */ + confirmedAt: string; + /** + * Block number where transaction was confirmed + */ + confirmedAtBlockNumber: string; + /** + * ISO timestamp when transaction was created + */ + createdAt: string; + /** + * Additional metadata and enriched transaction information + */ + enrichedData?: unknown; + /** + * Error message if transaction failed + */ + errorMessage: string; + /** + * Parameters used for transaction execution + */ + executionParams?: unknown; + /** + * Result data from transaction execution + */ + executionResult?: unknown; + /** + * Sender wallet address + */ + from: string; + /** + * Unique transaction identifier + */ + id: string; + /** + * On-chain transaction hash once confirmed + */ + transactionHash: string; + /** + * Original transaction parameters and data + */ + transactionParams?: unknown; + }; + }; +}; + +export type GetTransactionByIdResponse = + GetTransactionByIdResponses[keyof GetTransactionByIdResponses]; + +export type SignMessageData = { + body?: { + /** + * The blockchain network identifier where the signing will occur. Common values include: 1 (Ethereum), 137 (Polygon), 56 (BSC). + */ + chainId: number; + /** + * The wallet address that will sign the message. + */ + from: string; + /** + * The message to be signed. Can be plain text or hexadecimal format (starting with 0x). The format is automatically detected. + */ + message: string; + }; + path?: never; + query?: never; + url: "/v1/sign/message"; +}; + +export type SignMessageErrors = { + /** + * Invalid request parameters. This occurs when the wallet address format is invalid, chainId is not supported, or the message format is incorrect. + */ + 400: unknown; + /** + * Authentication required. For backend usage, include `x-secret-key` header. For frontend usage, include `x-client-id` + `Authorization: Bearer ` headers. + */ + 401: unknown; + /** + * Internal server error. This may occur due to wallet connectivity issues, signing service unavailability, or unexpected server errors. + */ + 500: unknown; +}; + +export type SignMessageResponses = { + /** + * Message signed successfully. Returns the cryptographic signature that can be used for verification. + */ + 200: { + result: { + /** + * The cryptographic signature in hexadecimal format. This can be used for verification and authentication purposes. + */ + signature: string; + }; + }; +}; + +export type SignMessageResponse = + SignMessageResponses[keyof SignMessageResponses]; + +export type SignTypedDataData = { + body?: { + /** + * The blockchain network identifier for EIP-712 domain separation. + */ + chainId: number; + /** + * EIP-712 domain separator containing contract and chain information for signature verification. + */ + domain: { + /** + * Chain ID as string for domain separation + */ + chainId?: string; + /** + * The domain name (e.g., token name) + */ + name?: string; + /** + * Optional salt for additional entropy + */ + salt?: string; + /** + * The contract address that will verify this signature + */ + verifyingContract?: string; + /** + * Domain version for signature compatibility + */ + version?: string; + }; + /** + * The wallet address that will sign the typed data. + */ + from: string; + /** + * The structured data to be signed, matching the defined types schema. + */ + message: { + [key: string]: unknown; + }; + /** + * The primary type name from the types object that defines the main structure being signed. + */ + primaryType: string; + /** + * Type definitions for the structured data, following EIP-712 specifications. + */ + types: { + [key: string]: Array<{ + /** + * The field name + */ + name: string; + /** + * The Solidity type (e.g., 'address', 'uint256') + */ + type: string; + }>; + }; + }; + path?: never; + query?: never; + url: "/v1/sign/typed-data"; +}; + +export type SignTypedDataErrors = { + /** + * Invalid request parameters. This occurs when the typed data structure is malformed, domain parameters are incorrect, or wallet address format is invalid. + */ + 400: unknown; + /** + * Authentication required. The request must include valid `x-wallet-access-token` headers for accessing the wallet, as well as a x-client-id (frontend) or x-secret-key (backend) for project authentication. + */ + 401: unknown; + /** + * Internal server error. This may occur due to wallet connectivity issues, signing service unavailability, or unexpected server errors. + */ + 500: unknown; +}; + +export type SignTypedDataResponses = { + /** + * Typed data signed successfully. Returns the EIP-712 compliant signature that can be used for on-chain verification. + */ + 200: { + result: { + /** + * The cryptographic signature in hexadecimal format. This can be used for verification and authentication purposes. + */ + signature: string; + }; + }; +}; + +export type SignTypedDataResponse = + SignTypedDataResponses[keyof SignTypedDataResponses]; + +export type BuyTokenWithUsdData = { + /** + * Buy Token Request + * Request to buy tokens using a USD amount. The system will automatically use your wallet balance to purchase the specified tokens. + */ + body?: { + /** + * The USD amount to spend on the token purchase (as string to support decimals) + */ + amountUsd: string; + /** + * The blockchain network where the token will be received + */ + chainId: number; + /** + * The wallet address that will buy the tokens. + */ + from: string; + /** + * The token address to purchase (use 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for native token) + */ + tokenAddress: string; + }; + path?: never; + query?: never; + url: "/v1/tokens/buy"; +}; + +export type BuyTokenWithUsdErrors = { + /** + * Invalid request parameters. + */ + 400: unknown; + /** + * Authentication required. For backend usage, include `x-secret-key` header. For frontend usage, include `x-client-id` + `Authorization: Bearer ` headers. + */ + 401: unknown; + /** + * Payment required. Insufficient wallet balance to complete the purchase. + */ + 402: unknown; + /** + * Internal server error. This may occur due to network connectivity issues, wallet creation failures, or transaction execution failures. + */ + 500: unknown; +}; + +export type BuyTokenWithUsdResponses = { + /** + * Buy Token Response + * Successful token purchase response containing executed transaction IDs + */ + 200: { + result: { + /** + * Transaction IDs that were executed for your token purchase + */ + transactionIds: Array; + }; + }; +}; + +export type BuyTokenWithUsdResponse = + BuyTokenWithUsdResponses[keyof BuyTokenWithUsdResponses]; + +export type SellTokenForUsdData = { + /** + * Sell Token Request + * Request to sell tokens for USD value. The system will automatically convert your tokens to USDC on Arbitrum. + */ + body?: { + /** + * The USD amount worth of tokens to sell (as string to support decimals) + */ + amountUsd: string; + /** + * The blockchain network where the token is located + */ + chainId: number; + /** + * The wallet address that will send the transaction. + */ + from: string; + /** + * The token address to sell (use 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for native token) + */ + tokenAddress: string; + }; + path?: never; + query?: never; + url: "/v1/tokens/sell"; +}; + +export type SellTokenForUsdErrors = { + /** + * Invalid request parameters. + */ + 400: unknown; + /** + * Authentication required. For backend usage, include `x-secret-key` header. For frontend usage, include `x-client-id` + `Authorization: Bearer ` headers. + */ + 401: unknown; + /** + * Payment required. Insufficient token balance to complete the sale. + */ + 402: unknown; + /** + * Internal server error. This may occur due to network connectivity issues, wallet creation failures, or transaction execution failures. + */ + 500: unknown; +}; + +export type SellTokenForUsdResponses = { + /** + * Sell Token Response + * Successful token sale response containing executed transaction IDs + */ + 200: { + result: { + /** + * Transaction IDs that were executed for your token sale + */ + transactionIds: Array; + }; + }; +}; + +export type SellTokenForUsdResponse = + SellTokenForUsdResponses[keyof SellTokenForUsdResponses]; + +export type TransferTokenWithUsdData = { + /** + * Transfer Token Request + * Request to transfer tokens worth a USD amount to another wallet address. The system will calculate the token amount based on current prices and transfer the tokens. + */ + body?: { + /** + * The USD amount worth of tokens to transfer (as string to support decimals) + */ + amountUsd: string; + /** + * The blockchain network where the token is located + */ + chainId: number; + /** + * The wallet address that will send the transaction. + */ + from: string; + /** + * The wallet address that will receive the transferred tokens + */ + to: string; + /** + * The token address to transfer (use 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for native token) + */ + tokenAddress: string; + }; + path?: never; + query?: never; + url: "/v1/tokens/transfer"; +}; + +export type TransferTokenWithUsdErrors = { + /** + * Invalid request parameters or insufficient token balance. + */ + 400: unknown; + /** + * Authentication required. For backend usage, include `x-secret-key` header. For frontend usage, include `x-client-id` + `Authorization: Bearer ` headers. + */ + 401: unknown; + /** + * Payment required. Insufficient wallet balance to complete the transfer. + */ + 402: unknown; + /** + * Internal server error. This may occur due to network connectivity issues, wallet creation failures, or transaction execution failures. + */ + 500: unknown; +}; + +export type TransferTokenWithUsdResponses = { + /** + * Transfer Token Response + * Successful token transfer response containing executed transaction IDs + */ + 200: { + result: { + /** + * Transaction IDs that were executed for your token transfer + */ + transactionIds: Array; + }; + }; +}; + +export type TransferTokenWithUsdResponse = + TransferTokenWithUsdResponses[keyof TransferTokenWithUsdResponses]; + +export type ClientOptions = { + baseUrl: + | "https://api.thirdweb-dev.com" + | "http://localhost:3030" + | (string & {}); +}; diff --git a/packages/api/src/configure.ts b/packages/api/src/configure.ts new file mode 100644 index 00000000000..031669957d1 --- /dev/null +++ b/packages/api/src/configure.ts @@ -0,0 +1,52 @@ +import type { Config } from "./client/client/index.js"; +import { client } from "./client/client.gen.js"; + +export type EngineClientOptions = { + readonly clientId?: string; + readonly secretKey?: string; +}; + +export function configure( + options: EngineClientOptions & { override?: Config }, +) { + client.setConfig({ + bodySerializer: stringify, + headers: { + ...(options.clientId && { "x-client-id": options.clientId }), + ...(options.secretKey && { "x-secret-key": options.secretKey }), + }, + ...(options.override ?? {}), + }); +} + +function stringify( + // biome-ignore lint/suspicious/noExplicitAny: JSON.stringify signature + value: any, + // biome-ignore lint/suspicious/noExplicitAny: JSON.stringify signature + replacer?: ((this: any, key: string, value: any) => any) | null, + space?: string | number, +) { + const res = JSON.stringify( + value, + (key, value_) => { + const value__ = typeof value_ === "bigint" ? value_.toString() : value_; + return typeof replacer === "function" ? replacer(key, value__) : value__; + }, + space, + ); + return res; +} + +export type MaybeErrorResponse = { result: D } | { error: E }; + +export function isErrorResponse( + res: MaybeErrorResponse, +): res is { error: E } { + return "error" in res; +} + +export function isSuccessResponse( + res: MaybeErrorResponse, +): res is { result: D } { + return "result" in res; +} diff --git a/packages/api/src/exports/thirdweb.ts b/packages/api/src/exports/thirdweb.ts new file mode 100644 index 00000000000..8da457075e5 --- /dev/null +++ b/packages/api/src/exports/thirdweb.ts @@ -0,0 +1,8 @@ +export type { CreateClientConfig } from "../client/client.gen.js"; +export * from "../client/index.js"; +export { + configure, + type EngineClientOptions, + isErrorResponse, + isSuccessResponse, +} from "../configure.js"; diff --git a/packages/api/tsconfig.base.json b/packages/api/tsconfig.base.json new file mode 100644 index 00000000000..f0e556fd3c6 --- /dev/null +++ b/packages/api/tsconfig.base.json @@ -0,0 +1,48 @@ +{ + // This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config. + "compilerOptions": { + // Incremental builds + // NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes. + "allowJs": false, + "allowSyntheticDefaultImports": true, + "checkJs": false, + + // Interop constraints + "esModuleInterop": false, + "exactOptionalPropertyTypes": false, + "forceConsistentCasingInFileNames": true, + "importHelpers": true, + // Incremental builds + // NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes. + "incremental": false, + + // jsx for "/react" portion + "jsx": "react-jsx", + "lib": [ + "ES2022", // By using ES2022 we get access to the `.cause` property on `Error` instances. + "DOM", // We are adding `DOM` here to get the `fetch`, etc. types. This should be removed once these types are available via DefinitelyTyped. + "DOM.Iterable" // Adding `DOM.Iterable` for the Headers.entries() method. + ], + "module": "NodeNext", + + // Language and environment + "moduleResolution": "NodeNext", + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + + // Skip type checking for node modules + "skipLibCheck": true, + + // Type checking + "strict": true, + "target": "ES2021", + "useDefineForClassFields": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true + }, + "include": [] +} diff --git a/packages/api/tsconfig.build.json b/packages/api/tsconfig.build.json new file mode 100644 index 00000000000..03fd851f536 --- /dev/null +++ b/packages/api/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "rootDir": "./src", + "sourceMap": true + }, + "exclude": [ + "src/**/*.test.ts", + "src/**/*.test.tsx", + "src/**/*.test-d.ts", + "src/**/*.bench.ts", + "src/**/*.macro.ts" + ], + "extends": "./tsconfig.base.json", + "include": ["src"] +} diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json new file mode 100644 index 00000000000..567a39cb1ce --- /dev/null +++ b/packages/api/tsconfig.json @@ -0,0 +1,12 @@ +{ + // This configuration is used for local development and type checking. + "compilerOptions": { + "baseUrl": ".", + "paths": { + "~test/*": ["./test/src/*"] + } + }, + "exclude": [], + "extends": "./tsconfig.base.json", + "include": ["src", "test"] +} diff --git a/packages/thirdweb/knip.json b/packages/thirdweb/knip.json index d038587413d..a163c284a77 100644 --- a/packages/thirdweb/knip.json +++ b/packages/thirdweb/knip.json @@ -12,7 +12,8 @@ "@testing-library/jest-dom", "tslib", "@thirdweb-dev/insight", - "@thirdweb-dev/engine" + "@thirdweb-dev/engine", + "@thirdweb-dev/api" ], "project": ["src/**/*.{ts,tsx}", "scripts/**/*.mjs"], "rules": { diff --git a/packages/thirdweb/package.json b/packages/thirdweb/package.json index 6de14d705bc..6ab0c7d7040 100644 --- a/packages/thirdweb/package.json +++ b/packages/thirdweb/package.json @@ -23,6 +23,7 @@ "@radix-ui/react-tooltip": "1.2.7", "@storybook/react": "9.0.15", "@tanstack/react-query": "5.81.5", + "@thirdweb-dev/api": "workspace:*", "@thirdweb-dev/engine": "workspace:*", "@thirdweb-dev/insight": "workspace:*", "@walletconnect/sign-client": "2.20.1", diff --git a/packages/thirdweb/src/exports/thirdweb.ts b/packages/thirdweb/src/exports/thirdweb.ts index c3f3dd3cd13..6460d4e88ae 100644 --- a/packages/thirdweb/src/exports/thirdweb.ts +++ b/packages/thirdweb/src/exports/thirdweb.ts @@ -297,4 +297,8 @@ export { export { keccak256 } from "../utils/hashing/keccak256.js"; // sha256 export { sha256 } from "../utils/hashing/sha256.js"; +export * as Contracts from "../v2/contracts/index.js"; + +// namespaced API +export * as Wallets from "../v2/wallets/index.js"; export { deploySmartAccount } from "../wallets/smart/lib/signing.js"; diff --git a/packages/thirdweb/src/utils/domains.ts b/packages/thirdweb/src/utils/domains.ts index 9e345046ee9..bdac469d10f 100644 --- a/packages/thirdweb/src/utils/domains.ts +++ b/packages/thirdweb/src/utils/domains.ts @@ -49,6 +49,12 @@ type DomainOverrides = { * @default "bridge.thirdweb.com" */ bridge?: string; + + /** + * The base URL for the API service. + * @default "api.thirdweb.com" + */ + api?: string; }; export const DEFAULT_RPC_URL = "rpc.thirdweb.com"; @@ -61,6 +67,7 @@ const DEFAULT_ANALYTICS_URL = "c.thirdweb.com"; const DEFAULT_INSIGHT_URL = "insight.thirdweb.com"; const DEFAULT_ENGINE_CLOUD_URL = "engine.thirdweb.com"; const DEFAULT_BRIDGE_URL = "bridge.thirdweb.com"; +const DEFAULT_API_URL = "api.thirdweb.com"; let domains: { [k in keyof DomainOverrides]-?: string } = { analytics: DEFAULT_ANALYTICS_URL, @@ -73,6 +80,7 @@ let domains: { [k in keyof DomainOverrides]-?: string } = { rpc: DEFAULT_RPC_URL, social: DEFAULT_SOCIAL_URL, storage: DEFAULT_STORAGE_URL, + api: DEFAULT_API_URL, }; export const setThirdwebDomains = (DomainOverrides: DomainOverrides) => { @@ -87,6 +95,7 @@ export const setThirdwebDomains = (DomainOverrides: DomainOverrides) => { rpc: DomainOverrides.rpc ?? DEFAULT_RPC_URL, social: DomainOverrides.social ?? DEFAULT_SOCIAL_URL, storage: DomainOverrides.storage ?? DEFAULT_STORAGE_URL, + api: DomainOverrides.api ?? DEFAULT_API_URL, }; }; diff --git a/packages/thirdweb/src/v2/client/init.ts b/packages/thirdweb/src/v2/client/init.ts new file mode 100644 index 00000000000..f4eef128b13 --- /dev/null +++ b/packages/thirdweb/src/v2/client/init.ts @@ -0,0 +1,41 @@ +import { createThirdwebClient } from "../../client/client.js"; + +/** + * Initializes a new Thirdweb client using your client ID or secret key. + * @param options - Options for initializing the client + * @param options.clientId - The client ID for your Thirdweb project + * @param options.secretKey - The secret key for your Thirdweb project + * @returns A new Thirdweb client instance to be used with other thirdweb functions + * + * @example + * ## Initialize client-side + * ```typescript + * import { Client } from "thirdweb/v2"; + * + * const thirdwebClient = Client.init({ + * clientId: "YOUR_CLIENT_ID", + * }); + * ``` + * + * ## Initialize server-side + * ```typescript + * import { createThirdwebClient } from "thirdweb/v2"; + * + * const thirdwebClient = createThirdwebClient({ + * secretKey: "YOUR_CLIENT_ID", + * }); + * ``` + */ +export function init(options: init.Options) { + return createThirdwebClient(options); +} + +export declare namespace init { + type Options = { + clientId: string; + secretKey?: string; + } | { + clientId?: string; + secretKey: string; + }; +} diff --git a/packages/thirdweb/src/v2/contracts/index.ts b/packages/thirdweb/src/v2/contracts/index.ts new file mode 100644 index 00000000000..85f625bfeb5 --- /dev/null +++ b/packages/thirdweb/src/v2/contracts/index.ts @@ -0,0 +1,3 @@ +export { type ReadOptions, read } from "./read.js"; +export type { ContractCall } from "./types.js"; +export { type WriteOptions, write } from "./write.js"; diff --git a/packages/thirdweb/src/v2/contracts/read.ts b/packages/thirdweb/src/v2/contracts/read.ts new file mode 100644 index 00000000000..816a471cabd --- /dev/null +++ b/packages/thirdweb/src/v2/contracts/read.ts @@ -0,0 +1,43 @@ +import { readContract } from "@thirdweb-dev/engine"; +import type { ContractCall } from "./types.js"; + +export type ReadOptions = { + contractAddress: string; + chainId: number; + calls: ContractCall[]; +}; + +/** + * Read from a contract + * @param options - Options including the contract address, chain id, and calls + * @returns Promise that resolves to the result + * @example + * ```typescript + * const result = await Contracts.read({ + * contractAddress: "0x...", + * chainId: 1, + * calls: [{ method: "function balanceOf(address)", params: ["0x..."] }], + * }); + * ``` + */ +export async function read(options: ReadOptions) { + const result = await readContract({ + body: { + readOptions: { + chainId: options.chainId.toString(), + }, + params: options.calls.map((call) => ({ + contractAddress: call.contractAddress, + method: call.method, + params: call.params, + value: call.value, + })), + }, + }); + if (result.error) { + throw new Error( + `Failed to write contract: ${result.response.status} - ${result.error}`, + ); + } + return result.data?.result; +} diff --git a/packages/thirdweb/src/v2/contracts/types.ts b/packages/thirdweb/src/v2/contracts/types.ts new file mode 100644 index 00000000000..1e9b1c3000c --- /dev/null +++ b/packages/thirdweb/src/v2/contracts/types.ts @@ -0,0 +1,6 @@ +export type ContractCall = { + contractAddress: string; + method: string; + params: unknown[]; + value?: string; +}; diff --git a/packages/thirdweb/src/v2/contracts/write.ts b/packages/thirdweb/src/v2/contracts/write.ts new file mode 100644 index 00000000000..0cbe3db7089 --- /dev/null +++ b/packages/thirdweb/src/v2/contracts/write.ts @@ -0,0 +1,56 @@ +import { writeContract } from "@thirdweb-dev/api"; +import { getThirdwebBaseUrl } from "../../utils/domains.js"; +import { getClientFetch } from "../../utils/fetch.js"; +import { stringify } from "../../utils/json.js"; +import type { UserWallet } from "../wallets/types.js"; +import type { ContractCall } from "./types.js"; + +export type WriteOptions = { + wallet: UserWallet; + chainId: number; + calls: ContractCall[]; +}; + +/** + * Write to a contract + * @param options - Options including the wallet, contract address, chain id, and calls + * @returns Promise that resolves to the transaction id + * @example + * ```typescript + * const transactionId = await Contracts.write({ + * wallet: userWallet, + * contractAddress: "0x...", + * chainId: 1, + * calls: [{ method: "function transfer(address,uint256)", params: ["0x...", "100"] }], + * }); + * ``` + */ +export async function write(options: WriteOptions) { + const result = await writeContract({ + baseUrl: getThirdwebBaseUrl("api"), + fetch: getClientFetch(options.wallet.client), + headers: { + Authorization: `Bearer ${options.wallet.authToken}`, + }, + body: { + chainId: options.chainId, + from: options.wallet.address, + calls: options.calls.map((call) => ({ + contractAddress: call.contractAddress, + method: call.method, + params: call.params, + value: call.value, + })), + }, + }); + if (result.error) { + throw new Error( + `Failed to write contract: ${result.response.status} - ${stringify(result.error)}`, + ); + } + const transactionId = result.data?.result?.transactionIds?.[0]; + if (!transactionId) { + throw new Error("Failed to write contract: no transaction id"); + } + return transactionId; +} diff --git a/packages/thirdweb/src/v2/transactions/get.ts b/packages/thirdweb/src/v2/transactions/get.ts new file mode 100644 index 00000000000..4e37b3e0217 --- /dev/null +++ b/packages/thirdweb/src/v2/transactions/get.ts @@ -0,0 +1,70 @@ +import { getTransactionById } from "@thirdweb-dev/api"; +import type { ThirdwebClient } from "../../client/client.js"; +import { getThirdwebBaseUrl } from "../../utils/domains.js"; +import { getClientFetch } from "../../utils/fetch.js"; +import type { Transaction } from "./types.js"; +import type { Address } from "../../utils/address.js"; +import type { Hex } from "../../utils/encoding/hex.js"; + +/** + * Retrieves a transaction by ID. + * + * @param options - Options including the transaction ID + * @param options.transactionId - The ID of the transaction to retrieve + * @returns Promise that resolves to the transaction + * @example + * + * ## Get a transaction by ID + * ```typescript + * import { Client, Transactions } from "thirdweb/v2"; + * + * const client = Client.init({ + * clientId: "YOUR_CLIENT_ID", + * }); + * + * const transaction = await Transactions.get({ + * transactionId: "...", + * client, + * }); + * ``` + */ +export async function get(options: get.Options): Promise { + const result = await getTransactionById({ + baseUrl: getThirdwebBaseUrl("api"), + fetch: getClientFetch(options.client), + path: { + transactionId: options.transactionId, + } + }); + + if (result.error) { + throw new Error( + `Failed to get transaction: ${result.response.status} - ${result.error}`, + ); + } + const transaction = result.data?.result; + if (!transaction) { + throw new Error("Failed to get transaction: no transaction"); + } + + return { + id: transaction.id, + transactionHash: transaction.transactionHash as Hex, + from: transaction.from as Address, + chainId: transaction.chainId, + createdAt: transaction.createdAt, + confirmedAt: transaction.confirmedAt, + confirmedAtBlockNumber: transaction.confirmedAtBlockNumber, + cancelledAt: transaction.cancelledAt, + errorMessage: transaction.errorMessage, + }; +} + +export declare namespace get { + type Options = { + transactionId: string; + client: ThirdwebClient; + }; + + type Result = Transaction; +} diff --git a/packages/thirdweb/src/v2/transactions/list.ts b/packages/thirdweb/src/v2/transactions/list.ts new file mode 100644 index 00000000000..0047eeffaed --- /dev/null +++ b/packages/thirdweb/src/v2/transactions/list.ts @@ -0,0 +1,77 @@ +import { listTransactions } from "@thirdweb-dev/api"; +import type { Transaction } from "./types.js"; +import { getThirdwebBaseUrl } from "../../utils/domains.js"; +import { getClientFetch } from "../../utils/fetch.js"; +import type { Address } from "../../utils/address.js"; +import type { ThirdwebClient } from "../../client/client.js"; +import type { Hex } from "../../utils/encoding/hex.js"; + + +/** + * Retrieves a paginated list of transactions associated with the provided sender address. + * + * @param options - Options including the sender address, chain ID, and pagination + * @param options.client - The Thirdweb client instance + * @param options.sender - The sender address to retrieve transactions for + * @param options.limit - The maximum number of transactions to retrieve (default: 20, max: 100) + * @param options.page - The page number for pagination (default: 1, min: 1) + * @returns Promise that resolves to the transactions + * @example + * + * ## List transactions + * ```typescript + * import { Client, Transactions } from "thirdweb/v2"; + * + * const client = Client.init({ + * clientId: "YOUR_CLIENT_ID", + * }); + * + * const transactions = await Transactions.list({ + * sender: "0x...", + * client, + * }); + * ``` + */ +export async function list(options: list.Options): Promise { + const result = await listTransactions({ + baseUrl: getThirdwebBaseUrl("api"), + fetch: getClientFetch(options.client), + query: { + from: options.sender, + limit: options.limit, + page: options.page, + } + }); + + if (result.error) { + throw new Error( + `Failed to list transactions: ${result.response.status} - ${result.error}`, + ); + } + const transactions = result.data?.result?.transactions; + if (!transactions) { + throw new Error("Failed to list transactions: no transactions"); + } + return transactions.map((transaction) => ({ + id: transaction.id, + transactionHash: transaction.transactionHash as Hex, + from: transaction.from as Address, + chainId: transaction.chainId, + createdAt: transaction.createdAt, + confirmedAt: transaction.confirmedAt, + confirmedAtBlockNumber: transaction.confirmedAtBlockNumber, + cancelledAt: transaction.cancelledAt, + errorMessage: transaction.errorMessage, + })); +} + +export declare namespace list { + type Options = { + client: ThirdwebClient; + sender: Address; + limit?: number; + page?: number; + }; + + type Result = Array; +} diff --git a/packages/thirdweb/src/v2/transactions/send.ts b/packages/thirdweb/src/v2/transactions/send.ts new file mode 100644 index 00000000000..ac3f437238d --- /dev/null +++ b/packages/thirdweb/src/v2/transactions/send.ts @@ -0,0 +1,141 @@ +import { sendTransactions } from "@thirdweb-dev/api"; +import type { UserWallet } from "../wallets/types.js"; +import type { TransactionRequest } from "./types.js"; +import { getThirdwebBaseUrl } from "../../utils/domains.js"; +import { getClientFetch } from "../../utils/fetch.js"; + +/** + * Sends a series of transactions for execution. Each transaction can be a human-readable contract call, native transfer, or encoded transaction. + * + * @param options - Options including the wallet, chain id, and transactions + * @param options.wallet - The wallet to use for signing transactions + * @param options.chainId - The chain ID to use for the transactions + * @param options.transactions - An array of transactions to send + * @returns The sent transaction IDs + * @example + * + * ## Call a contract + * ```typescript + * import { Client, Transactions, Wallets } from "thirdweb/v2"; + * + * const userWallet = await Wallets.loginWithOauth({ + * client: thirdwebClient, + * provider: "google", + * }); + * + * const transactionIds = await Transactions.send({ + * wallet: userWallet, + * chainId: 1, + * transactions: [ + * { + * contractAddress: "0x...", + * method: "function transfer(address,uint256)", + * params: ["0x...", 100n], + * } + * ], + * }); + * ``` + * + * ## Send native currency + * ```typescript + * import { Client, Transactions, Wallets } from "thirdweb/v2"; + * + * const userWallet = await Wallets.loginAsGuest({ + * client: thirdwebClient, + * }); + * + * const transactionIds = await Transactions.send({ + * wallet: userWallet, + * chainId: 1, + * transactions: [ + * { + * to: "0x...", + * value: 100n, + * } + * ], + * }); + * ``` + * + * ## Send an encoded transaction + * ```typescript + * import { Client, Transactions, Wallets } from "thirdweb/v2"; + * + * const thirdwebClient = Client.init({ + * clientId: "YOUR_CLIENT_ID", + * }); + * + * const userWallet = await Wallets.loginWithOauth({ + * client: thirdwebClient, + * provider: "github", + * }); + * + * const transactionIds = await Transactions.send({ + * wallet: userWallet, + * chainId: 1, + * transactions: [ + * { + * to: "0x...", + * data: "0x...", + * } + * ], + * }); + */ +export async function send(options: send.Options): Promise> { + const transactions = options.transactions.map((transaction) => { + if ("contractAddress" in transaction) { + return { + contractAddress: transaction.contractAddress, + method: transaction.method, + params: transaction.params, + value: transaction.value?.toString() ?? "0", + type: "contract-call" as const, + } as const; + } else if ("data" in transaction) { + return { + to: transaction.to, + data: transaction.data, + value: transaction.value?.toString() ?? "0", + type: "encoded" as const, + } as const; + } else { + return { + to: transaction.to, + value: transaction.value?.toString() ?? "0", + type: "native-transfer" as const, + } as const; + } + }); + + const result = await sendTransactions({ + baseUrl: getThirdwebBaseUrl("api"), + fetch: getClientFetch(options.wallet.client), + headers: { + Authorization: `Bearer ${options.wallet.authToken}`, + }, + body: { + chainId: options.chainId, + from: options.wallet.address, + transactions + } + }); + + if (result.error) { + throw new Error( + `Failed to send transactions: ${result.response.status} - ${result.error}`, + ); + } + const transactionIds = result.data?.result?.transactionIds; + if (!transactionIds) { + throw new Error("Failed to send transactions: no transaction ids"); + } + return transactionIds; +}; + +export namespace send { + export type Options = { + wallet: UserWallet; + chainId: number; + transactions: Array; + }; +} + diff --git a/packages/thirdweb/src/v2/transactions/types.ts b/packages/thirdweb/src/v2/transactions/types.ts new file mode 100644 index 00000000000..785a2da66ca --- /dev/null +++ b/packages/thirdweb/src/v2/transactions/types.ts @@ -0,0 +1,29 @@ +import type { Address } from "../../utils/address.js"; +import type { Hex } from "../../utils/encoding/hex.js"; + +export type TransactionRequest = { + contractAddress: Address; + method: `function ${string}`; + params: Array<(string | bigint | number | boolean | object)>; + value?: bigint; +} | { + to: Address; + data: Hex; + value?: bigint; +} | { + to: Address; + value: bigint; +} + +export type Transaction = { + id: string; + transactionHash: Hex; + from: Address; + chainId: string; + createdAt: string; + confirmedAt?: string; + confirmedAtBlockNumber?: string; + cancelledAt?: string; + errorMessage: string | null; +} + diff --git a/packages/thirdweb/src/v2/types.ts b/packages/thirdweb/src/v2/types.ts new file mode 100644 index 00000000000..6f5907f0fb2 --- /dev/null +++ b/packages/thirdweb/src/v2/types.ts @@ -0,0 +1 @@ +export type EthereumAddress = `0x${string}`; // Joaquim do you think we should use the types from the existing SDK or create entirely new ones so we don't cross over at all? diff --git a/packages/thirdweb/src/v2/wallets/external.ts b/packages/thirdweb/src/v2/wallets/external.ts new file mode 100644 index 00000000000..704d33a92a3 --- /dev/null +++ b/packages/thirdweb/src/v2/wallets/external.ts @@ -0,0 +1,44 @@ +import { defineChain } from "../../chains/utils.js"; +import type { ThirdwebClient } from "../../client/client.js"; +import { injectedProvider } from "../../wallets/injected/mipdStore.js"; +import type { WalletId } from "../../wallets/wallet-types.js"; + +/** + * Connect an external wallet like Metamask, Coinbase, etc. + * @param options - Options including the client, wallet id, and chain id + * @returns Promise that resolves to the wallet instance + * @example + * ```typescript + * import { Wallets } from "thirdweb/v2"; + * + * const wallet = await Wallets.connectExternalWallet({ + * client: thirdwebClient, + * walletId: "io.metamask", + * chainId: 1, + * }); + * ``` + * @wallet + */ +export async function connectExternalWallet(options: { + client: ThirdwebClient; + walletId: WalletId; + chainId: number; +}) { + const { createWallet } = await import("../../wallets/create-wallet.js"); + const w = createWallet(options.walletId); + if (injectedProvider(options.walletId)) { + await w.connect({ + client: options.client, + chain: defineChain(options.chainId), + }); + } else { + await w.connect({ + client: options.client, + chain: defineChain(options.chainId), + walletConnect: { + showQrModal: true, + }, + }); + } + return w; +} diff --git a/packages/thirdweb/src/v2/wallets/guest.ts b/packages/thirdweb/src/v2/wallets/guest.ts new file mode 100644 index 00000000000..d6296a8e481 --- /dev/null +++ b/packages/thirdweb/src/v2/wallets/guest.ts @@ -0,0 +1,39 @@ +import { webLocalStorage } from "../../utils/storage/webStorage.js"; +import { ClientScopedStorage } from "../../wallets/in-app/core/authentication/client-scoped-storage.js"; +import type { BaseLoginOptions } from "./types.js"; +import { createUserWallet } from "./user.js"; + +/** + * Login as a guest + * @param options - Options including the client and ecosystem + * @returns Promise that resolves to the user wallet + * @example + * ```typescript + * import { Wallets } from "thirdweb/v2"; + * + * const userWallet = await Wallets.loginAsGuest({ + * client: thirdwebClient, + * }); + * ``` + */ +export async function loginAsGuest(options: BaseLoginOptions) { + const { guestAuthenticate } = await import( + "../../wallets/in-app/core/authentication/guest.js" + ); + const storage = new ClientScopedStorage({ + clientId: options.client.clientId, + ecosystem: options.ecosystem, + storage: webLocalStorage, + }); + const authResult = await guestAuthenticate({ + client: options.client, + storage, + ecosystem: options.ecosystem, + }); + + return createUserWallet({ + client: options.client, + ecosystem: options.ecosystem, + authResult, + }); +} diff --git a/packages/thirdweb/src/v2/wallets/index.ts b/packages/thirdweb/src/v2/wallets/index.ts new file mode 100644 index 00000000000..ab6d353739b --- /dev/null +++ b/packages/thirdweb/src/v2/wallets/index.ts @@ -0,0 +1,18 @@ +export type { connectExternalWallet } from "./external.js"; +export { loginAsGuest } from "./guest.js"; +export { + handleOauthRedirect, + type LoginWithOauthOptions, + type LoginWithOauthRedirectOptions, + loginWithOauth, + loginWithOauthRedirect, +} from "./oauth.js"; +export { + verifyLoginCode, + type SendLoginCodeOptions, + sendLoginCode, + type VerifyLoginCodeOptions, +} from "./otp.js"; +export { type LoginWithPasskeyOptions, loginWithPasskey } from "./passkey.js"; +export { type LoginWithWalletOptions, loginWithWallet } from "./siwe.js"; +export type { BaseLoginOptions, UserWallet } from "./types.js"; diff --git a/packages/thirdweb/src/v2/wallets/login.test.ts b/packages/thirdweb/src/v2/wallets/login.test.ts new file mode 100644 index 00000000000..6c4e45f4a86 --- /dev/null +++ b/packages/thirdweb/src/v2/wallets/login.test.ts @@ -0,0 +1,94 @@ +import { beforeAll, describe, expect, it } from "vitest"; +import { TEST_CLIENT } from "../../../test/src/test-clients.js"; +import { createThirdwebClient } from "../../client/client.js"; +import { setThirdwebDomains } from "../../utils/domains.js"; +import * as Contracts from "../contracts/index.js"; +import * as Wallets from "./index.js"; + +describe.skip("login tests", () => { + beforeAll(() => { + setThirdwebDomains({ + inAppWallet: "embedded-wallet.thirdweb-dev.com", + api: "api.thirdweb-dev.com", + }); + }); + + it.skip("login with OTP", { retry: 0 }, async () => { + const client = createThirdwebClient({ + clientId: "...", + }); + + await Wallets.sendLoginCode({ + client, + type: "phone", + phoneNumber: "+1111111111", + }); + + const wallet = await Wallets.verifyLoginCode({ + client, + type: "phone", + phoneNumber: "+1111111111", + otp: "000000", + }); + + const address = wallet.address; + const profiles = wallet.getProfiles(); + const balance = await wallet.getBalance({ chainId: 1 }); + + console.log({ address, profiles, balance }); + + // rest of the sdk maps to HTTP API as close as possible: + + // Contract.prepareCall() + // Contract.write({ wallet, contractAddress, chainId, calls }) + // Contract.read({ contractAddress, chainId, calls }) + + // Transactions.send({ wallet, chainId, transaction }) + // Transactions.get(id) + + expect(address).toBeDefined(); + expect(profiles).toBeDefined(); + expect(balance).toBeDefined(); + }); + + it("login as guest", async () => { + const client = TEST_CLIENT; + + const wallet = await Wallets.loginAsGuest({ + client, + }); + + // const serverWalletAddress = await Wallets.createServerWallet({ + // client, + // identifier: "test", + // }) + // const serverWallet = Wallets.serverWallet({ + // client, + // serverWalletAddress, + // }); + + const address = wallet.address; + const profiles = await wallet.getProfiles(); + + console.log({ address, profiles }); + + const transactionId = await Contracts.write({ + wallet, + chainId: 84532, + calls: [ + { + contractAddress: "0xe352Cf5f74e3ACfd2d59EcCee6373d2Aa996086e", + method: "function approve(address,uint256)", + params: ["0x4fA9230f4E8978462cE7Bf8e6b5a2588da5F4264", "100"], + }, + ], + }); + + console.log({ transactionId }); + + // poll for status + // Transactions.get(transactionId) + }); + + // const { loginWithCode, sendCode, loginWithOauth, wallet, state } = useWallets(); +}); diff --git a/packages/thirdweb/src/v2/wallets/oauth.ts b/packages/thirdweb/src/v2/wallets/oauth.ts new file mode 100644 index 00000000000..eb77ff1d2ad --- /dev/null +++ b/packages/thirdweb/src/v2/wallets/oauth.ts @@ -0,0 +1,108 @@ +import type { Prettify } from "../../utils/type-utils.js"; +import type { SocialAuthOption } from "../../wallets/types.js"; +import type { BaseLoginOptions, UserWallet } from "./types.js"; +import { createUserWallet } from "./user.js"; + +export type LoginWithOauthOptions = Prettify< + BaseLoginOptions & { + provider: SocialAuthOption; + } +>; + +/** + * Login with OAuth provider + * Authenticate a user using OAuth providers like Google, Facebook, Apple, etc. + * @param options - Login options including provider and client configuration + * @returns Promise that resolves to UserWallet instance + * @example + * ```typescript + * const userWallet = await Wallets.loginWithOauth({ + * client: thirdwebClient, + * provider: "google", + * }); + * ``` + * @wallet + */ +export async function loginWithOauth( + options: LoginWithOauthOptions, +): Promise { + const { loginWithOauth } = await import( + "../../wallets/in-app/web/lib/auth/oauth.js" + ); + const authResult = await loginWithOauth({ + authOption: options.provider, + client: options.client, + ecosystem: options.ecosystem, + }); + return createUserWallet({ + client: options.client, + ecosystem: options.ecosystem, + authResult, + }); +} + +export type LoginWithOauthRedirectOptions = Prettify< + LoginWithOauthOptions & { + redirectUrl?: string; + } +>; + +/** + * Initiate OAuth login with redirect + * Redirects the user to the OAuth provider's login page for authentication. + * @param options - Login options including provider, client, and optional redirect URL + * @returns Promise that resolves when redirect is initiated + * @example + * ```typescript + * await Wallets.loginWithOauthRedirect({ + * client: thirdwebClient, + * provider: "google", + * redirectUrl: "https://myapp.com/callback", + * }); + * ``` + * @wallet + */ +export async function loginWithOauthRedirect( + options: LoginWithOauthRedirectOptions, +): Promise { + const { loginWithOauthRedirect } = await import( + "../../wallets/in-app/web/lib/auth/oauth.js" + ); + return loginWithOauthRedirect({ + authOption: options.provider, + client: options.client, + redirectUrl: options.redirectUrl, + ecosystem: options.ecosystem, + }); +} + +/** + * Handle OAuth redirect callback + * Processes the OAuth redirect response and creates a user wallet from the authentication token. + * @param options - Base login options with client and ecosystem configuration + * @returns Promise that resolves to UserWallet instance + * @example + * ```typescript + * // After OAuth redirect callback + * const userWallet = await Wallets.handleOauthRedirect({ + * client: thirdwebClient, + * }); + * ``` + * @wallet + */ +export async function handleOauthRedirect( + options: BaseLoginOptions, +): Promise { + const { getUrlToken } = await import( + "../../wallets/in-app/web/lib/get-url-token.js" + ); + const urlToken = getUrlToken(); + if (!urlToken?.authResult) { + throw new Error("No auth token found in URL"); + } + return createUserWallet({ + client: options.client, + ecosystem: options.ecosystem, + authResult: urlToken.authResult, + }); +} diff --git a/packages/thirdweb/src/v2/wallets/otp.ts b/packages/thirdweb/src/v2/wallets/otp.ts new file mode 100644 index 00000000000..fe541f6f14d --- /dev/null +++ b/packages/thirdweb/src/v2/wallets/otp.ts @@ -0,0 +1,137 @@ +import type { Prettify } from "../../utils/type-utils.js"; +import type { BaseLoginOptions, UserWallet } from "./types.js"; +import { createUserWallet } from "./user.js"; + +export type SendLoginCodeOptions = Prettify< + BaseLoginOptions & + ( + | { + type: "phone"; + phoneNumber: string; + } + | { + type: "email"; + email: string; + } + ) +>; + +/** + * Send OTP verification code + * Sends a one-time password (OTP) to the user's email or phone number for authentication. + * @param options - Options specifying the delivery method (email or phone) and recipient + * @returns Promise that resolves when OTP is sent + * @example + * ```typescript + * // Send OTP via email + * await Wallets.sendLoginCode({ + * client: thirdwebClient, + * type: "email", + * email: "user@example.com", + * }); + * + * // Send OTP via phone + * await Wallets.sendLoginCode({ + * client: thirdwebClient, + * type: "phone", + * phoneNumber: "+1234567890", + * }); + * ``` + * @wallet + */ +export async function sendLoginCode(options: SendLoginCodeOptions) { + const { sendOtp } = await import("../../wallets/in-app/web/lib/auth/otp.js"); + switch (options.type) { + case "email": + return sendOtp({ + client: options.client, + strategy: "email", + email: options.email, + ecosystem: options.ecosystem, + }); + case "phone": + return sendOtp({ + client: options.client, + strategy: "phone", + phoneNumber: options.phoneNumber, + ecosystem: options.ecosystem, + }); + } +} + +export type VerifyLoginCodeOptions = Prettify< + BaseLoginOptions & + ( + | { + type: "phone"; + phoneNumber: string; + otp: string; + } + | { + type: "email"; + email: string; + otp: string; + } + ) +>; + +/** + * Login with OTP verification + * Verifies the OTP code and authenticates the user to create a wallet. + * @param options - Options including the OTP code and recipient details (email or phone) + * @returns Promise that resolves to UserWallet instance + * @example + * ```typescript + * import { Wallets } from "thirdweb/v2"; + * + * // Login with email OTP + * const userWallet = await Wallets.verifyLoginCode({ + * client: thirdwebClient, + * type: "email", + * email: "user@example.com", + * otp: "123456", + * }); + * + * // Login with phone OTP + * const userWallet = await Wallets.verifyLoginCode({ + * client: thirdwebClient, + * type: "phone", + * phoneNumber: "+1234567890", + * otp: "123456", + * }); + * ``` + * @wallet + */ +export async function verifyLoginCode( + options: VerifyLoginCodeOptions +): Promise { + const { verifyOtp } = await import( + "../../wallets/in-app/web/lib/auth/otp.js" + ); + const authResult = await (async () => { + switch (options.type) { + case "email": + return verifyOtp({ + client: options.client, + strategy: "email", + email: options.email, + verificationCode: options.otp, + ecosystem: options.ecosystem, + }); + case "phone": + return verifyOtp({ + client: options.client, + strategy: "phone", + phoneNumber: options.phoneNumber, + verificationCode: options.otp, + ecosystem: options.ecosystem, + }); + } + })(); + + return createUserWallet({ + client: options.client, + ecosystem: options.ecosystem, + authResult, + }); +} diff --git a/packages/thirdweb/src/v2/wallets/passkey.ts b/packages/thirdweb/src/v2/wallets/passkey.ts new file mode 100644 index 00000000000..a65cdc74876 --- /dev/null +++ b/packages/thirdweb/src/v2/wallets/passkey.ts @@ -0,0 +1,86 @@ +import { webLocalStorage } from "../../utils/storage/webStorage.js"; +import type { Prettify } from "../../utils/type-utils.js"; +import { ClientScopedStorage } from "../../wallets/in-app/core/authentication/client-scoped-storage.js"; +import type { BaseLoginOptions } from "./types.js"; +import { createUserWallet } from "./user.js"; + +export type LoginWithPasskeyOptions = Prettify< + BaseLoginOptions & { + action: "sign-in" | "sign-up"; + passkeyDomain?: string; + storeLastUsedPasskey?: boolean; + } +>; + +/** + * Login with passkey authentication + * Authenticates users using WebAuthn passkeys for secure, passwordless login. + * @param options - Options including action type, domain, and storage preferences + * @returns Promise that resolves to UserWallet instance + * @example + * ```typescript + * // Sign in with existing passkey + * const userWallet = await Wallets.loginWithPasskey({ + * client: thirdwebClient, + * action: "sign-in", + * passkeyDomain: "myapp.com", + * storeLastUsedPasskey: true, + * }); + * + * // Sign up with new passkey + * const userWallet = await Wallets.loginWithPasskey({ + * client: thirdwebClient, + * action: "sign-up", + * }); + * ``` + * @wallet + */ +export async function loginWithPasskey(options: LoginWithPasskeyOptions) { + const { PasskeyWebClient } = await import( + "../../wallets/in-app/web/lib/auth/passkeys.js" + ); + const { loginWithPasskey, registerPasskey } = await import( + "../../wallets/in-app/core/authentication/passkeys.js" + ); + + // TODO: react native version of this + const passkeyClient = new PasskeyWebClient(); + const storage = new ClientScopedStorage({ + clientId: options.client.clientId, + ecosystem: options.ecosystem, + storage: webLocalStorage, + }); + + const authResult = await (async () => { + switch (options.action) { + case "sign-in": + return loginWithPasskey({ + client: options.client, + passkeyClient, + rp: { + id: options.passkeyDomain ?? window.location.hostname, + name: options.passkeyDomain ?? window.document.title, + }, + storage: options.storeLastUsedPasskey ? storage : undefined, + ecosystem: options.ecosystem, + }); + case "sign-up": + return registerPasskey({ + client: options.client, + passkeyClient, + rp: { + id: options.passkeyDomain ?? window.location.hostname, + name: options.passkeyDomain ?? window.document.title, + }, + storage: options.storeLastUsedPasskey ? storage : undefined, + ecosystem: options.ecosystem, + }); + } + })(); + + return createUserWallet({ + client: options.client, + ecosystem: options.ecosystem, + authResult, + }); +} diff --git a/packages/thirdweb/src/v2/wallets/siwe.ts b/packages/thirdweb/src/v2/wallets/siwe.ts new file mode 100644 index 00000000000..893cce8c6b4 --- /dev/null +++ b/packages/thirdweb/src/v2/wallets/siwe.ts @@ -0,0 +1,51 @@ +import { defineChain } from "../../chains/utils.js"; +import type { Prettify } from "../../utils/type-utils.js"; +import type { Wallet } from "../../wallets/interfaces/wallet.js"; +import type { BaseLoginOptions } from "./types.js"; +import { createUserWallet } from "./user.js"; + +export type LoginWithWalletOptions = Prettify< + BaseLoginOptions & { + wallet: Wallet; + chainId?: number; + } +>; + +/** + * Login with wallet using SIWE + * Authenticates users using Sign-In with Ethereum (SIWE) protocol with any connected wallet. + * @param options - Options including the wallet instance and optional chain ID + * @returns Promise that resolves to UserWallet instance + * @example + * ```typescript + * import { Wallets } from "thirdweb/v2"; + * + * const wallet = await Wallets.connectExternalWallet({ + * client: thirdwebClient, + * walletId: "io.metamask", + * chainId: 1, + * }); + * + * const userWallet = await Wallets.loginWithWallet({ + * client: thirdwebClient, + * wallet: wallet, + * }); + * ``` + * @wallet + */ +export async function loginWithWallet(options: LoginWithWalletOptions) { + const { siweAuthenticate } = await import( + "../../wallets/in-app/core/authentication/siwe.js" + ); + const authResult = await siweAuthenticate({ + client: options.client, + wallet: options.wallet, + chain: options.wallet.getChain() ?? defineChain(1), + ecosystem: options.ecosystem, + }); + return createUserWallet({ + client: options.client, + ecosystem: options.ecosystem, + authResult, + }); +} diff --git a/packages/thirdweb/src/v2/wallets/types.ts b/packages/thirdweb/src/v2/wallets/types.ts new file mode 100644 index 00000000000..e8215b7ae09 --- /dev/null +++ b/packages/thirdweb/src/v2/wallets/types.ts @@ -0,0 +1,17 @@ +import type { ThirdwebClient } from "../../client/client.js"; +import type { GetBalanceResult } from "../../extensions/erc20/read/getBalance.js"; +import type { Profile } from "../../wallets/in-app/core/authentication/types.js"; +import type { Ecosystem } from "../../wallets/in-app/core/wallet/types.js"; + +export type BaseLoginOptions = { + client: ThirdwebClient; + ecosystem?: Ecosystem; +}; + +export type UserWallet = { + client: ThirdwebClient; + address: string; + authToken: string; + getBalance: (options: { chainId: number }) => Promise; + getProfiles: () => Promise; +}; diff --git a/packages/thirdweb/src/v2/wallets/user.ts b/packages/thirdweb/src/v2/wallets/user.ts new file mode 100644 index 00000000000..edb3c455012 --- /dev/null +++ b/packages/thirdweb/src/v2/wallets/user.ts @@ -0,0 +1,55 @@ +import { defineChain } from "../../chains/utils.js"; +import type { ThirdwebClient } from "../../client/client.js"; +import { generateWallet } from "../../wallets/in-app/core/actions/generate-wallet.enclave.js"; +import type { AuthStoredTokenWithCookieReturnType } from "../../wallets/in-app/core/authentication/types.js"; +import type { Ecosystem } from "../../wallets/in-app/core/wallet/types.js"; +import type { UserWallet } from "./types.js"; + +export async function createUserWallet(args: { + client: ThirdwebClient; + ecosystem: Ecosystem | undefined; + authResult: AuthStoredTokenWithCookieReturnType; +}): Promise { + let address = args.authResult.storedToken.authDetails.walletAddress; + const authToken = args.authResult.storedToken.cookieString; + + if (!authToken) { + throw new Error("Failed to login"); + } + + // TODO: this will be done in the gateway API + if (!address) { + const wallet = await generateWallet({ + client: args.client, + ecosystem: args.ecosystem, + authToken, + }); + address = wallet.address; + } + + return { + client: args.client, + address, + authToken, + getBalance: async (options: { chainId: number }) => { + const { getWalletBalance } = await import( + "../../wallets/utils/getWalletBalance.js" + ); + return getWalletBalance({ + client: args.client, + address, + chain: defineChain(options.chainId), + }); + }, + getProfiles: async () => { + const { fetchUserProfiles } = await import( + "../../wallets/in-app/core/authentication/linkAccount.js" + ); + return fetchUserProfiles({ + client: args.client, + ecosystem: args.ecosystem, + authToken, + }); + }, + }; +} diff --git a/packages/thirdweb/src/wallets/in-app/core/authentication/linkAccount.ts b/packages/thirdweb/src/wallets/in-app/core/authentication/linkAccount.ts index 9519af310a1..b179180ddce 100644 --- a/packages/thirdweb/src/wallets/in-app/core/authentication/linkAccount.ts +++ b/packages/thirdweb/src/wallets/in-app/core/authentication/linkAccount.ts @@ -154,3 +154,34 @@ export async function getLinkedProfilesInternal({ return (linkedAccounts ?? []) satisfies Profile[]; } + +export async function fetchUserProfiles(options: { + client: ThirdwebClient; + ecosystem?: Ecosystem; + authToken: string; +}): Promise { + const clientFetch = getClientFetch(options.client, options.ecosystem); + const IN_APP_URL = getThirdwebBaseUrl("inAppWallet"); + + const headers: Record = { + Authorization: `Bearer iaw-auth-token:${options.authToken}`, + "Content-Type": "application/json", + }; + + const linkedAccountsResp = await clientFetch( + `${IN_APP_URL}/api/2024-05-05/accounts`, + { + headers, + method: "GET", + }, + ); + + if (!linkedAccountsResp.ok) { + const body = await linkedAccountsResp.json(); + throw new Error(body.message || "Failed to get linked accounts."); + } + + const { linkedAccounts } = await linkedAccountsResp.json(); + + return (linkedAccounts ?? []) satisfies Profile[]; +} diff --git a/packages/thirdweb/src/wallets/in-app/core/authentication/types.ts b/packages/thirdweb/src/wallets/in-app/core/authentication/types.ts index 9b078279671..a988fb638b4 100644 --- a/packages/thirdweb/src/wallets/in-app/core/authentication/types.ts +++ b/packages/thirdweb/src/wallets/in-app/core/authentication/types.ts @@ -174,6 +174,7 @@ export type AuthDetails = ( backupRecoveryCodes?: string[]; recoveryShareManagement: RecoveryShareManagement; walletType?: "sharded" | "enclave"; + walletAddress?: string; }; type InitializedUser = { diff --git a/packages/thirdweb/src/wallets/in-app/web/lib/auth/otp.ts b/packages/thirdweb/src/wallets/in-app/web/lib/auth/otp.ts index b14fec04aca..4729ad3c81e 100644 --- a/packages/thirdweb/src/wallets/in-app/web/lib/auth/otp.ts +++ b/packages/thirdweb/src/wallets/in-app/web/lib/auth/otp.ts @@ -21,6 +21,7 @@ export const sendOtp = async (args: PreAuthArgsType): Promise => { const headers: Record = { "Content-Type": "application/json", "x-client-id": client.clientId, + ...(client.secretKey ? { "x-secret-key": client.secretKey } : {}), }; if (ecosystem?.id) { @@ -51,7 +52,10 @@ export const sendOtp = async (args: PreAuthArgsType): Promise => { }); if (!response.ok) { - throw new Error("Failed to send verification code"); + const error = await response.text(); + throw new Error( + `Failed to send verification code: ${response.status} ${response.statusText} ${error}`, + ); } return await response.json(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71d2f329000..ce80dc78093 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1054,6 +1054,28 @@ importers: specifier: 5.8.3 version: 5.8.3 + packages/api: + dependencies: + '@hey-api/client-fetch': + specifier: 0.10.0 + version: 0.10.0(@hey-api/openapi-ts@0.76.0(magicast@0.3.5)(typescript@5.8.3)) + typescript: + specifier: '>=5.0.4' + version: 5.8.3 + devDependencies: + '@biomejs/biome': + specifier: 2.0.6 + version: 2.0.6 + '@hey-api/openapi-ts': + specifier: 0.76.0 + version: 0.76.0(magicast@0.3.5)(typescript@5.8.3) + rimraf: + specifier: 6.0.1 + version: 6.0.1 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + packages/engine: dependencies: '@hey-api/client-fetch': @@ -1250,6 +1272,9 @@ importers: '@tanstack/react-query': specifier: 5.81.5 version: 5.81.5(react@19.1.0) + '@thirdweb-dev/api': + specifier: workspace:* + version: link:../api '@thirdweb-dev/engine': specifier: workspace:* version: link:../engine @@ -16102,10 +16127,10 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.592.0 + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) '@aws-sdk/client-sts': 3.592.0 '@aws-sdk/core': 3.592.0 - '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0(@aws-sdk/client-sts@3.592.0))(@aws-sdk/client-sts@3.592.0) '@aws-sdk/middleware-host-header': 3.577.0 '@aws-sdk/middleware-logger': 3.577.0 '@aws-sdk/middleware-recursion-detection': 3.577.0 @@ -16148,10 +16173,10 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.592.0 + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) '@aws-sdk/client-sts': 3.592.0 '@aws-sdk/core': 3.592.0 - '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0(@aws-sdk/client-sts@3.592.0))(@aws-sdk/client-sts@3.592.0) '@aws-sdk/middleware-host-header': 3.577.0 '@aws-sdk/middleware-logger': 3.577.0 '@aws-sdk/middleware-recursion-detection': 3.577.0 @@ -16194,10 +16219,10 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.592.0 + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) '@aws-sdk/client-sts': 3.592.0 '@aws-sdk/core': 3.592.0 - '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0(@aws-sdk/client-sts@3.592.0))(@aws-sdk/client-sts@3.592.0) '@aws-sdk/middleware-host-header': 3.577.0 '@aws-sdk/middleware-logger': 3.577.0 '@aws-sdk/middleware-recursion-detection': 3.577.0 @@ -16241,7 +16266,7 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.592.0': + '@aws-sdk/client-sso-oidc@3.592.0(@aws-sdk/client-sts@3.592.0)': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 @@ -16284,6 +16309,7 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 transitivePeerDependencies: + - '@aws-sdk/client-sts' - aws-crt '@aws-sdk/client-sso-oidc@3.840.0': @@ -16420,7 +16446,7 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.592.0 + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -16539,6 +16565,24 @@ snapshots: '@smithy/util-stream': 4.2.2 tslib: 2.8.1 + '@aws-sdk/credential-provider-ini@3.592.0(@aws-sdk/client-sso-oidc@3.592.0(@aws-sdk/client-sts@3.592.0))(@aws-sdk/client-sts@3.592.0)': + dependencies: + '@aws-sdk/client-sts': 3.592.0 + '@aws-sdk/credential-provider-env': 3.587.0 + '@aws-sdk/credential-provider-http': 3.587.0 + '@aws-sdk/credential-provider-process': 3.587.0 + '@aws-sdk/credential-provider-sso': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0) + '@aws-sdk/credential-provider-web-identity': 3.587.0(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/types': 3.577.0 + '@smithy/credential-provider-imds': 3.2.8 + '@smithy/property-provider': 3.1.11 + '@smithy/shared-ini-file-loader': 3.1.12 + '@smithy/types': 3.7.2 + tslib: 2.8.1 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + '@aws-sdk/credential-provider-ini@3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0)': dependencies: '@aws-sdk/client-sts': 3.592.0 @@ -16593,6 +16637,25 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-node@3.592.0(@aws-sdk/client-sso-oidc@3.592.0(@aws-sdk/client-sts@3.592.0))(@aws-sdk/client-sts@3.592.0)': + dependencies: + '@aws-sdk/credential-provider-env': 3.587.0 + '@aws-sdk/credential-provider-http': 3.587.0 + '@aws-sdk/credential-provider-ini': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0(@aws-sdk/client-sts@3.592.0))(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/credential-provider-process': 3.587.0 + '@aws-sdk/credential-provider-sso': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0) + '@aws-sdk/credential-provider-web-identity': 3.587.0(@aws-sdk/client-sts@3.592.0) + '@aws-sdk/types': 3.577.0 + '@smithy/credential-provider-imds': 3.2.8 + '@smithy/property-provider': 3.1.11 + '@smithy/shared-ini-file-loader': 3.1.12 + '@smithy/types': 3.7.2 + tslib: 2.8.1 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + '@aws-sdk/credential-provider-node@3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0)': dependencies: '@aws-sdk/credential-provider-env': 3.587.0 @@ -16866,7 +16929,7 @@ snapshots: '@aws-sdk/token-providers@3.587.0(@aws-sdk/client-sso-oidc@3.592.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.592.0 + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.1.11 '@smithy/shared-ini-file-loader': 3.1.12