Skip to content

[SDK] Init new API namespaces #7637

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
}
39 changes: 39 additions & 0 deletions packages/api/README.md
Original file line number Diff line number Diff line change
@@ -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: "<PROJECT_SECRET_KEY>",
});
```

## Example Usage

```ts
import { writeContract } from "@thirdweb-dev/api";

const result = await sendTransaction({
Comment on lines +18 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the import/function call mismatch.

The example imports writeContract but calls sendTransaction, which will cause a runtime error.

Apply this fix:

-import { writeContract } from "@thirdweb-dev/api";
+import { sendTransaction } from "@thirdweb-dev/api";

Or alternatively, if writeContract is the correct function:

-const result = await sendTransaction({
+const result = await writeContract({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```ts
import { writeContract } from "@thirdweb-dev/api";
const result = await sendTransaction({
import { sendTransaction } from "@thirdweb-dev/api";
const result = await sendTransaction({
🤖 Prompt for AI Agents
In packages/api/README.md around lines 18 to 21, the code imports writeContract
but calls sendTransaction, causing a runtime error. Fix this by either changing
the import to import sendTransaction from the correct module or update the
function call to writeContract if that is the intended function. Ensure the
import and function call match to avoid runtime issues.

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)
28 changes: 28 additions & 0 deletions packages/api/biome.json
Original file line number Diff line number Diff line change
@@ -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
}
}
Comment on lines +6 to +12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

forceJsExtensions may break non-relative (bare module specifier) imports

The rule is applied globally, so it will enforce adding .js even on bare-specifier paths such as "react" or "@thirdweb-dev/sdk".
Biome’s own docs warn that the rule is only safe when "ignorePackageImports": true is set (or when limited to relative imports).

Consider narrowing the rule scope or adding

{
  "forceJsExtensions": true,
  "ignorePackageImports": true
}

to avoid turning valid bare imports into invalid ones.

🤖 Prompt for AI Agents
In packages/api/biome.json around lines 6 to 12, the "forceJsExtensions" option
is set to true globally, which incorrectly adds ".js" extensions to bare module
specifiers like "react" or "@thirdweb-dev/sdk". To fix this, add
"ignorePackageImports": true to the options object to ensure the rule only
applies to relative imports and does not break bare imports.

}
}
},
"overrides": [
{
"assist": {
"actions": {
"source": {
"useSortedKeys": "off"
}
}
},
"includes": ["package.json"]
}
]
}
6 changes: 6 additions & 0 deletions packages/api/openapi-ts.config.ts
Original file line number Diff line number Diff line change
@@ -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" },
});
Comment on lines +3 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Pin the OpenAPI schema version to guarantee reproducible builds

Fetching https://api.thirdweb-dev.com/openapi.json on every generation ties build determinism to a live, mutable endpoint.
If the spec changes, all generated client types will drift without code review.

Store a copy in-repo (or pin to a tagged commit URL) and update intentionally.

-  input: "https://api.thirdweb-dev.com/openapi.json",
+  // Prefer a pinned revision for reproducibility
+  input: "./schemas/[email protected]",

🏁 Script executed:

#!/bin/bash
# Search for a local 'schemas' directory
fd --type d --max-depth 3 schemas

# List all JSON files in the repo
fd --extension json

# Find JSON files mentioning 'openapi'
rg -l 'openapi' -g '*.json' || true

Length of output: 13458


Pin OpenAPI schema for reproducible builds

A live URL will produce drifting client types whenever the remote spec changes. To guarantee deterministic generations:

  • Add a copy of the OpenAPI spec under packages/api/schemas/, e.g.
    packages/api/schemas/[email protected]
  • Update the config in packages/api/openapi-ts.config.ts:
 export default defineConfig({
-  input: "https://api.thirdweb-dev.com/openapi.json",
+  // Point to a pinned, in-repo spec for stable builds
+  input: "./schemas/[email protected]",
   output: { format: "biome", lint: "biome", path: "src/client" },
 });

• Create the schemas folder next to openapi-ts.config.ts if it doesn’t exist.
• Commit the downloaded JSON with a clear date-or-version suffix.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default defineConfig({
input: "https://api.thirdweb-dev.com/openapi.json",
output: { format: "biome", lint: "biome", path: "src/client" },
});
export default defineConfig({
// Point to a pinned, in-repo spec for stable builds
input: "./schemas/[email protected]",
output: { format: "biome", lint: "biome", path: "src/client" },
});
🤖 Prompt for AI Agents
In packages/api/openapi-ts.config.ts around lines 3 to 6, the OpenAPI schema is
currently referenced via a live URL, which causes non-reproducible builds due to
changes in the remote spec. To fix this, download the OpenAPI JSON spec and save
it under packages/api/schemas/ with a clear date or version suffix, such as
[email protected]. Then update the input path in the config to
point to this local file instead of the URL. Also, create the schemas folder if
it does not exist and commit the downloaded JSON file to the repo.

61 changes: 61 additions & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>",
"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"
}
}
28 changes: 28 additions & 0 deletions packages/api/src/client/client.gen.ts
Original file line number Diff line number Diff line change
@@ -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<T extends DefaultClientOptions = ClientOptions> =
(
override?: Config<DefaultClientOptions & T>,
) => Config<Required<DefaultClientOptions> & T>;

export const client = createClient(
createConfig<ClientOptions>({
baseUrl: "https://api.thirdweb-dev.com",
}),
);
Comment on lines +24 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the hardcoded base URL configuration.

The client is configured with a hardcoded base URL https://api.thirdweb-dev.com. Ensure this is the correct production endpoint and consider whether this should be configurable for different environments.


🌐 Web query:

What is the current production API base URL for thirdweb services?

💡 Result:

The production API base URL for thirdweb services varies depending on the specific service you are using. Here are the base URLs for some of thirdweb's key services:

Please note that some services may require additional configuration, such as specifying the chain ID in the Bundler API's base URL. For the most accurate and up-to-date information, refer to the official thirdweb documentation or the specific service's API reference.

Citations:


Update hardcoded API base URL and enable configuration

The generated client is currently pointing to the dev endpoint

baseUrl: "https://api.thirdweb-dev.com",

but none of our production services use that host. Per the thirdweb docs, production URLs include:

Please:

  • Confirm which service this generated client is targeting.
  • Update baseUrl to the corresponding production URL.
  • Make baseUrl configurable via an environment variable (e.g. process.env.THIRDWEB_API_BASE_URL) with a sensible default.

Suggested diff in packages/api/src/client/client.gen.ts (lines 24–28):

-export const client = createClient(
-  createConfig<ClientOptions>({
-    baseUrl: "https://api.thirdweb-dev.com",
-  }),
-);
+export const client = createClient(
+  createConfig<ClientOptions>({
+    baseUrl:
+      process.env.THIRDWEB_API_BASE_URL ??
+      "https://contract-api.thirdweb.com/", // update to correct service URL
+  }),
+);
🤖 Prompt for AI Agents
In packages/api/src/client/client.gen.ts around lines 24 to 28, the client is
using a hardcoded development baseUrl "https://api.thirdweb-dev.com" which is
not used in production. First, identify which thirdweb service this client
targets (e.g., Contract API, Nebula API, etc.). Then update the baseUrl to the
corresponding production URL for that service. Finally, make the baseUrl
configurable by reading from an environment variable like
process.env.THIRDWEB_API_BASE_URL, and fallback to the production URL as a
default if the env var is not set.

189 changes: 189 additions & 0 deletions packages/api/src/client/client/client.ts
Original file line number Diff line number Diff line change
@@ -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<RequestInit, "body" | "headers"> & {
body?: any;
headers: ReturnType<typeof mergeHeaders>;
};

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Type inconsistency in error default assignment

The fallback assigns an empty object to finalError which is expected to be a string. Consider using an empty string or a more appropriate default error value.

-finalError = finalError || ({} as string);
+finalError = finalError || "";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
finalError = finalError || ({} as string);
finalError = finalError || "";
🤖 Prompt for AI Agents
In packages/api/src/client/client/client.ts at line 158, the fallback assignment
sets finalError to an empty object cast as a string, causing a type
inconsistency. Change the fallback to assign an empty string or another suitable
string value to finalError to maintain type consistency.


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" }),
};
};
Loading
Loading