diff --git a/apps/playground-web/package.json b/apps/playground-web/package.json index 1332c0a73da..f443911f821 100644 --- a/apps/playground-web/package.json +++ b/apps/playground-web/package.json @@ -22,7 +22,7 @@ "next": "15.3.5", "next-themes": "^0.4.6", "nextjs-toploader": "^1.6.12", - "openapi-types": "^12.1.3", + "openapi-types": "12.1.3", "posthog-js": "1.256.1", "prettier": "3.6.2", "react": "19.1.0", diff --git a/apps/portal/package.json b/apps/portal/package.json index 3f014468ce6..ca4879b1b84 100644 --- a/apps/portal/package.json +++ b/apps/portal/package.json @@ -24,6 +24,7 @@ "nextjs-toploader": "^1.6.12", "node-html-markdown": "^1.3.0", "node-html-parser": "^6.1.13", + "openapi-types": "12.1.3", "posthog-js": "1.256.1", "prettier": "3.6.2", "react": "19.1.0", diff --git a/apps/portal/src/app/ai/chat/EndpointMetadata.tsx b/apps/portal/src/app/ai/chat/EndpointMetadata.tsx deleted file mode 100644 index 8c1c9d0ca17..00000000000 --- a/apps/portal/src/app/ai/chat/EndpointMetadata.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { - type APIParameter, - ApiEndpoint, -} from "@/components/Document/APIEndpointMeta/ApiEndpoint"; -import { secretKeyHeaderParameter } from "../../../components/Document/APIEndpointMeta/common"; - -const contextFilterType = `\ -{ - from: string | null; - chain_ids: string[] | null; - session_id: string | null; -}`; - -const contextParameter: APIParameter = { - description: "Provide additional context information along with the message", - name: "context", - required: false, - type: contextFilterType, - example: { - from: "0x2247d5d238d0f9d37184d8332aE0289d1aD9991b", - chain_ids: [8453], - }, -}; - -const response200Example = `\ -{ - "message": "I've prepared a native ETH transfer as requested. Would you like to proceed with executing this transfer?", - "session_id": "123", - "request_id": "456", - "actions": [ - { - "type": "sign_transaction", - "data": { - "chainId": 8453, - "to": "0x1234567890123456789012345678901234567890", - "value": "10000000000000000", - "data": "0x" - }, - "session_id": "123", - "request_id": "456", - "source": "model", - "tool_name": null, - "description": null, - "kwargs": null - } - ] -}`; - -export function EndpointMetadata() { - return ( - - ); -} diff --git a/apps/portal/src/app/ai/chat/page.mdx b/apps/portal/src/app/ai/chat/page.mdx index 97f474c0c81..03191c57b06 100644 --- a/apps/portal/src/app/ai/chat/page.mdx +++ b/apps/portal/src/app/ai/chat/page.mdx @@ -1,23 +1,20 @@ -import { EndpointMetadata } from "./EndpointMetadata"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@doc"; +import { Tabs, TabsContent, TabsList, TabsTrigger, OpenApiEndpoint } from "@doc"; # Chat API -The thirdweb AI chat API is a standard OpenAI-compatible chat completion API that allows you to interact with the thirdweb AI model, specialized for blockchain interactions. - -The thirdweb proprietary model is optimized for blockchain interactions and can: +The thirdweb AI chat API is a standard OpenAI-compatible chat completion API that allows you to interact with the thirdweb AI model, optimized for blockchain interactions. - Query real-time data from the blockchain - Analyze transactions - Fetch token balances, prices and metadata - Prepare any contract call or transaction for signing -- Prepare swaps from any token pair +- Prepare swaps from/to any token pair - Deploy contracts - Generate images -- Search the web for information +- Search the web - And more! -You can use the API with the API directly, or with any OpenAI-compatible client library. +You can use the API with the HTTP API directly, or with any OpenAI-compatible client library. @@ -25,7 +22,7 @@ You can use the API with the API directly, or with any OpenAI-compatible client OpenAI Client - + @@ -50,4 +47,9 @@ chat_completion = client.chat.completions.create( print(chat_completion) ``` - \ No newline at end of file + + +### Going further + +- [Handle streaming responses](/ai/chat/streaming) +- [Full API Reference](https://api.thirdweb-dev.com/reference#tag/ai/post/ai/chat) \ No newline at end of file diff --git a/apps/portal/src/app/ai/chat/handling-responses/page.mdx b/apps/portal/src/app/ai/chat/streaming/page.mdx similarity index 100% rename from apps/portal/src/app/ai/chat/handling-responses/page.mdx rename to apps/portal/src/app/ai/chat/streaming/page.mdx diff --git a/apps/portal/src/app/ai/sidebar.tsx b/apps/portal/src/app/ai/sidebar.tsx index 5ae9dbb8624..bde4af53c03 100644 --- a/apps/portal/src/app/ai/sidebar.tsx +++ b/apps/portal/src/app/ai/sidebar.tsx @@ -13,8 +13,8 @@ export const sidebar: SideBar = { icon: , }, { - name: "Response Handling", - href: "/ai/chat/handling-responses", + name: "Streaming Responses", + href: "/ai/chat/streaming", }, { name: "API Reference", diff --git a/apps/portal/src/app/contracts/deploy/page.mdx b/apps/portal/src/app/contracts/deploy/page.mdx index 22aee32efe4..1da010b0d24 100644 --- a/apps/portal/src/app/contracts/deploy/page.mdx +++ b/apps/portal/src/app/contracts/deploy/page.mdx @@ -1,3 +1,14 @@ +import { OpenApiEndpoint, createMetadata } from "@doc"; + +export const metadata = createMetadata({ + image: { + title: "Deploy contracts", + icon: "contracts", + }, + title: "Deploy Contracts", + description: "Deploy contracts on any EVM chain.", +}); + # Deploy Contracts You can deploy contracts via CLI or programatically. @@ -16,8 +27,6 @@ You can also publish your contract to be deployable by anyone on any chain. npx thirdweb publish -k ``` - - ## Deploying via SDK You can deploy contracts from ABI and bytecode. @@ -26,15 +35,15 @@ You can deploy contracts from ABI and bytecode. import { deployContract } from "thirdweb/deploys"; const address = await deployContract({ - client, - chain, - bytecode: "0x...", - abi: contractAbi, - constructorParams: { - param1: "value1", - param2: 123, - }, - salt, // optional: salt enables deterministic deploys + client, + chain, + bytecode: "0x...", + abi: contractAbi, + constructorParams: { + param1: "value1", + param2: 123, + }, + salt, // optional: salt enables deterministic deploys }); ``` @@ -44,14 +53,26 @@ Or alternatively, you can deploy a contract from a previously published contract import { deployPublishedContract } from "thirdweb/deploys"; const address = await deployPublishedContract({ - client, - chain, - account, - contractId: "MyPublishedContract", - contractParams: { - param1: "value1", - param2: 123, - }, - publisher: "0x...", // optional, defaults to the thirdweb deployer + client, + chain, + account, + contractId: "MyPublishedContract", + contractParams: { + param1: "value1", + param2: 123, + }, + publisher: "0x...", // optional, defaults to the thirdweb deployer }); -``` \ No newline at end of file +``` + +## Deploy via API + +You can also deploy contracts via API by passing the contract bytecode and ABI. This will automatically verify the contract on block explorers and add it to your project dashboard. + + + +## List all deployed contracts + +You can list all deployed contracts for your project. + + diff --git a/apps/portal/src/app/contracts/events/page.mdx b/apps/portal/src/app/contracts/events/page.mdx index 4afd6688a1b..ca98eaa672e 100644 --- a/apps/portal/src/app/contracts/events/page.mdx +++ b/apps/portal/src/app/contracts/events/page.mdx @@ -3,6 +3,7 @@ import { TabsList, TabsTrigger, TabsContent, + OpenApiEndpoint, } from "@doc"; import { ReactIcon, @@ -31,27 +32,7 @@ Query and listen to contract events for any deployed contract on any EVM chain. - ### Get Contract Events - - You can fetch contract events using the [contract events API](https://api.thirdweb.com/reference#tag/contracts/get/v1/contracts/{address}/events). - - ```http - GET /v1/contracts/{address}/events?chainId=&decode=true - Host: api.thirdweb.com - x-secret-key: - ``` - - Authentication requires either `x-secret-key` (backend) or `x-client-id` (frontend) to be set in the request headers. - - #### Parameters - - - `address` - The contract address - - `chainId` - The chain ID where the contract is deployed - - `decode` - Whether to decode the event data (optional, defaults to false) - - #### Response - - The API returns a list of events that have been emitted by the specified contract, including event details and decoded function calls when `decode=true` is specified. + diff --git a/apps/portal/src/app/contracts/page.mdx b/apps/portal/src/app/contracts/page.mdx index 89e77511c13..2bc003eb0a0 100644 --- a/apps/portal/src/app/contracts/page.mdx +++ b/apps/portal/src/app/contracts/page.mdx @@ -10,6 +10,7 @@ import { TabsList, TabsTrigger, TabsContent, + OpenApiEndpoint, } from "@doc"; import { ReactIcon, @@ -67,89 +68,18 @@ Read, write, and deploy smart contracts on any EVM compatible blockchain. - ### Read a Contract - - You can read contract data efficiently using the [contract read API](https://api.thirdweb.com/reference#tag/contracts/post/v1/contracts/read). - - ```http - GET /v1/contracts/read - Host: api.thirdweb.com - Content-Type: application/json - x-client-id: - - { - "chainId": "1" // your chain id - "calls": [{ - "contractAddress": "0x...", - "method": "function allowance(address owner, address spender)", - "params": ["0x...", "0x..."], - }], - } - ``` - You can batch multiple contract reads in a single request, and the response will be an array of results or errors. + ### Read from Contracts - Authentication requires either `x-secret-key` (backend) or `x-client-id` (frontend) to be set in the request headers. + You can efficiently read data from multiple functions or contracts in a single request. - ### Write to a Contract + - You can write to a contract using the [contract write API](https://api.thirdweb.com/reference#tag/contracts/post/v1/contracts/write). - - - - Frontend - Backend - - - + ### Write to Contracts - On the frontend, use your project client ID and the users's auth token to send a transaction on their behalf. - - ```http - POST /v1/contracts/write - Host: api.thirdweb.com - Content-Type: application/json - x-client-id: - Authorization: Bearer - - { - "from": "0x...", // the user wallet address - "chainId": "1" // the chain id - "calls": [{ - "contractAddress": "0x...", - "method": "function transfer(address to, uint256 amount)", - "params": ["0x...", "1000000000000000000"], - }], - } - ``` - - - - - - On the backend, use your project secret key to send a transaction from any of your server wallets. - - ```http - POST /v1/contracts/write - Host: api.thirdweb.com - Content-Type: application/json - x-secret-key: - - { - "from": "0x...", // your server wallet address - "chainId": "1" // your chain id - "calls": [{ - "contractAddress": "0x...", - "method": "function transfer(address to, uint256 amount)", - "params": ["0x...", "1000000000000000000"], - }], - } - ``` - - - + You can write multiple functions to multiple contracts on the same chain in a single request atomically, which will result in a single transaction for optimal gas efficiency. - You can batch multiple contract writes in a single request, and the transactions will be batched atomically onchain. + diff --git a/apps/portal/src/app/contracts/transactions/page.mdx b/apps/portal/src/app/contracts/transactions/page.mdx index a4a6ac9415a..971e974e92a 100644 --- a/apps/portal/src/app/contracts/transactions/page.mdx +++ b/apps/portal/src/app/contracts/transactions/page.mdx @@ -3,6 +3,7 @@ import { TabsList, TabsTrigger, TabsContent, + OpenApiEndpoint, } from "@doc"; import { EngineIcon, @@ -21,27 +22,7 @@ Query all transactions for any deployed contract on any EVM chain. - ### Get Contract Transactions - - You can fetch all transactions for a contract using the [contract transactions API](https://api.thirdweb.com/reference#tag/contracts/get/v1/contracts/{address}/transactions). - - ```http - GET /v1/contracts/{address}/transactions?chainId=&decode=true - Host: api.thirdweb.com - x-secret-key: - ``` - - Authentication requires either `x-secret-key` (backend) or `x-client-id` (frontend) to be set in the request headers. - - #### Parameters - - - `address` - The contract address - - `chainId` - The chain ID where the contract is deployed - - `decode` - Whether to decode the transaction data (optional, defaults to false) - - #### Response - - The API returns a list of transactions that have interacted with the specified contract, including transaction details and decoded function calls when `decode=true` is specified. + diff --git a/apps/portal/src/app/nebula/api-reference/chat/EndpointMetadata.tsx b/apps/portal/src/app/nebula/api-reference/chat/EndpointMetadata.tsx index c2f0856262c..d69c52f7dc8 100644 --- a/apps/portal/src/app/nebula/api-reference/chat/EndpointMetadata.tsx +++ b/apps/portal/src/app/nebula/api-reference/chat/EndpointMetadata.tsx @@ -30,7 +30,9 @@ export function EndpointMetadata() { method: "POST", origin: "https://nebula-api.thirdweb.com", path: "/chat", + referenceUrl: "https://api.thirdweb.com/reference#tag/ai/ai/chat", request: { + queryParameters: [], bodyParameters: [ { description: "The message to be processed.", diff --git a/apps/portal/src/app/nebula/api-reference/clear-session/EndpointMetadata.tsx b/apps/portal/src/app/nebula/api-reference/clear-session/EndpointMetadata.tsx index 4af16becf6c..d1a6339905a 100644 --- a/apps/portal/src/app/nebula/api-reference/clear-session/EndpointMetadata.tsx +++ b/apps/portal/src/app/nebula/api-reference/clear-session/EndpointMetadata.tsx @@ -15,7 +15,10 @@ export function EndpointMetadata() { method: "POST", origin: "https://nebula-api.thirdweb.com", path: "/session/{session_id}/clear", + referenceUrl: + "https://api.thirdweb.com/reference#tag/ai/ai/clear-session", request: { + queryParameters: [], bodyParameters: [], headers: [nebulaSecretKeyHeaderParameter], pathParameters: [nebulaSessionIdPathParameter], diff --git a/apps/portal/src/app/nebula/api-reference/create-session/EndpointMetadata.tsx b/apps/portal/src/app/nebula/api-reference/create-session/EndpointMetadata.tsx index 4772585a374..a56fc3db122 100644 --- a/apps/portal/src/app/nebula/api-reference/create-session/EndpointMetadata.tsx +++ b/apps/portal/src/app/nebula/api-reference/create-session/EndpointMetadata.tsx @@ -15,7 +15,10 @@ export function EndpointMetadata() { method: "POST", origin: "https://nebula-api.thirdweb.com", path: "/session", + referenceUrl: + "https://api.thirdweb.com/reference#tag/ai/ai/create-session", request: { + queryParameters: [], bodyParameters: [ { description: "Set a custom title for the session.", diff --git a/apps/portal/src/app/nebula/api-reference/delete-session/EndpointMetadata.tsx b/apps/portal/src/app/nebula/api-reference/delete-session/EndpointMetadata.tsx index 0e02d2b846d..81e4f6784b9 100644 --- a/apps/portal/src/app/nebula/api-reference/delete-session/EndpointMetadata.tsx +++ b/apps/portal/src/app/nebula/api-reference/delete-session/EndpointMetadata.tsx @@ -22,7 +22,10 @@ export function EndpointMetadata() { method: "DELETE", origin: "https://nebula-api.thirdweb.com", path: "/session/{session_id}", + referenceUrl: + "https://api.thirdweb.com/reference#tag/ai/ai/delete-session", request: { + queryParameters: [], bodyParameters: [], headers: [nebulaSecretKeyHeaderParameter], pathParameters: [nebulaSessionIdPathParameter], diff --git a/apps/portal/src/app/nebula/api-reference/execute/EndpointMetadata.tsx b/apps/portal/src/app/nebula/api-reference/execute/EndpointMetadata.tsx index 9af5d5aa1ed..93631e5c1b5 100644 --- a/apps/portal/src/app/nebula/api-reference/execute/EndpointMetadata.tsx +++ b/apps/portal/src/app/nebula/api-reference/execute/EndpointMetadata.tsx @@ -36,7 +36,9 @@ export function EndpointMetadata() { method: "POST", origin: "https://nebula-api.thirdweb.com", path: "/execute", + referenceUrl: "https://api.thirdweb.com/reference#tag/ai/ai/execute", request: { + queryParameters: [], bodyParameters: [ { description: "The message to be processed.", diff --git a/apps/portal/src/app/nebula/api-reference/get-session/EndpointMetadata.tsx b/apps/portal/src/app/nebula/api-reference/get-session/EndpointMetadata.tsx index 2acd77f5132..f82ce213e0a 100644 --- a/apps/portal/src/app/nebula/api-reference/get-session/EndpointMetadata.tsx +++ b/apps/portal/src/app/nebula/api-reference/get-session/EndpointMetadata.tsx @@ -15,10 +15,13 @@ export function EndpointMetadata() { method: "GET", origin: "https://nebula-api.thirdweb.com", path: "/session/{session_id}", + referenceUrl: + "https://api.thirdweb.com/reference#tag/ai/ai/get-session", request: { bodyParameters: [], headers: [nebulaSecretKeyHeaderParameter], pathParameters: [nebulaSessionIdPathParameter], + queryParameters: [], }, responseExamples: { 200: nebulaFullSessionResponse, diff --git a/apps/portal/src/app/nebula/api-reference/list-session/EndpointMetadata.tsx b/apps/portal/src/app/nebula/api-reference/list-session/EndpointMetadata.tsx index 2b7ac71f436..da97738c965 100644 --- a/apps/portal/src/app/nebula/api-reference/list-session/EndpointMetadata.tsx +++ b/apps/portal/src/app/nebula/api-reference/list-session/EndpointMetadata.tsx @@ -24,10 +24,13 @@ export function EndpointMetadata() { method: "GET", origin: "https://nebula-api.thirdweb.com", path: "/session/list", + referenceUrl: + "https://api.thirdweb.com/reference#tag/ai/ai/list-session", request: { bodyParameters: [], headers: [nebulaSecretKeyHeaderParameter], pathParameters: [], + queryParameters: [], }, responseExamples: { 200: response200Example, diff --git a/apps/portal/src/app/nebula/api-reference/update-session/EndpointMetadata.tsx b/apps/portal/src/app/nebula/api-reference/update-session/EndpointMetadata.tsx index 9c954c1f4ab..67e875ced33 100644 --- a/apps/portal/src/app/nebula/api-reference/update-session/EndpointMetadata.tsx +++ b/apps/portal/src/app/nebula/api-reference/update-session/EndpointMetadata.tsx @@ -16,6 +16,8 @@ export function EndpointMetadata() { method: "PUT", origin: "https://nebula-api.thirdweb.com", path: "/session/{session_id}", + referenceUrl: + "https://api.thirdweb.com/reference#tag/ai/ai/update-session", request: { bodyParameters: [ { @@ -28,6 +30,7 @@ export function EndpointMetadata() { ], headers: [nebulaSecretKeyHeaderParameter], pathParameters: [nebulaSessionIdPathParameter], + queryParameters: [], }, responseExamples: { 200: nebulaFullSessionResponse, diff --git a/apps/portal/src/app/page.tsx b/apps/portal/src/app/page.tsx index 215b8544222..9b1eb52f4e3 100644 --- a/apps/portal/src/app/page.tsx +++ b/apps/portal/src/app/page.tsx @@ -1,4 +1,10 @@ -import { ArrowUpRightIcon, BotIcon, WebhookIcon, ZapIcon } from "lucide-react"; +import { + ArrowUpRightIcon, + BotIcon, + MessageCircleIcon, + WebhookIcon, + ZapIcon, +} from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import { Heading } from "@/components/Document"; @@ -16,7 +22,6 @@ import { BridgeIcon } from "../icons/products/BridgeIcon"; import { ConnectIcon } from "../icons/products/ConnectIcon"; import { EngineIcon } from "../icons/products/EngineIcon"; import { InsightIcon } from "../icons/products/InsightIcon"; -import { PlaygroundIcon } from "../icons/products/PlaygroundIcon"; import DocsHeroDark from "./_images/docs-hero-dark.png"; import DocsHeroLight from "./_images/docs-hero-light.png"; @@ -25,7 +30,7 @@ export default function Page() {
- +
@@ -70,20 +75,19 @@ function Hero() { ); } -function PlaygroundSection() { +function AISection() { return (
- +
- The thirdweb Payments REST API is available at `https://payments.thirdweb.com/`. See the full [API Reference](https://payments.thirdweb.com/reference) for details. + Use the thirdweb payments API to create and manage payments programmatically. + + ### Create Payment + + Create a payment for your product or service: + + + + ### Complete Payment + + Complete a payment using the product ID: + + + + ### Swap Tokens + + Swap tokens for optimal payment processing: + + + + For more details, see the full [API Reference](https://api.thirdweb.com/reference). diff --git a/apps/portal/src/app/transactions/monitor/page.mdx b/apps/portal/src/app/transactions/monitor/page.mdx index 2c6f34edfb6..32d13549b3e 100644 --- a/apps/portal/src/app/transactions/monitor/page.mdx +++ b/apps/portal/src/app/transactions/monitor/page.mdx @@ -6,6 +6,7 @@ import { createMetadata, Steps, Step, + OpenApiEndpoint, } from "@doc"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { WalletsSmartIcon } from "@/icons"; @@ -38,16 +39,17 @@ Monitor and get notified about transactions in your application, both on your pr - You can track the status of transactions sent via the [transactions API](https://engine.thirdweb.com/reference) using its transaction id. + ### Get transaction status -```http -GET /v1/transactions/{transactionId} -Host: api.thirdweb.com -Content-Type: application/json -x-secret-key: -``` + Get the information about a transaction by its id. -You can also list and search multiple transactions by with the [`/transactions` endpoint](https://api.thirdweb.com/reference#tag/transactions/get/v1/transactions). + + + ### List transactions + + List all transactions for your project with filtering and pagination. + + diff --git a/apps/portal/src/app/transactions/page.mdx b/apps/portal/src/app/transactions/page.mdx index 76cf0ca03dd..7d90f6135ee 100644 --- a/apps/portal/src/app/transactions/page.mdx +++ b/apps/portal/src/app/transactions/page.mdx @@ -10,6 +10,7 @@ import { TabsList, TabsTrigger, TabsContent, + OpenApiEndpoint, } from "@doc"; import { ReactIcon, @@ -68,96 +69,17 @@ Send, monitor, and manage transactions. Send transactions from user or server wa - ### Send a Transaction - - Send a transaction from a [user wallet](/wallets/users) from the frontend, or [server wallet](/wallets/server) from the backend using the [thirdweb API](https://api.thirdweb.com/reference#tag/transactions/post/v1/transactions). - - Transactions can be of type `contract-call`, `encoded` or `native-transfer`, and will be batched atomically onchain. + ### Send raw transactions - - - Frontend - Backend - + Send a raw transaction from a [user wallet](/wallets/users) from the frontend, or [server wallet](/wallets/server) from the backend using the [thirdweb API](https://api.thirdweb.com/reference#tag/transactions/post/v1/transactions). - + - For server wallets, you can execute transactions with just your project secret key. + - For user wallets in React applications that use the SDK, you can obtain the user wallet auth token (JWT) with the [`useAuthToken()`](/references/typescript/v5/useAuthToken) hook. + - For user wallets in TypeScript applications, you can get it by calling `wallet.getAuthToken()` on a connected [`inAppWallet()`](/references/typescript/v5/inAppWallet) or [`ecosystemWallet()`](/references/typescript/v5/ecosystemWallet). - On the frontend, use your project client ID and the users's auth token to send a transaction on their behalf. - - ```http - POST /v1/transactions - Host: api.thirdweb.com - Content-Type: application/json - x-client-id: - # user auth token can be obtained from one of the v1/wallet/user login endpoints - Authorization: Bearer - - { - "from": "0x...", // the user wallet address - "chainId": "1" // the chain id - "transactions": [{ - // contract call - "type": "contract-call", - "contractAddress": "0x...", - "method": "function transfer(address to, uint256 amount)", - "params": ["0x...", "1000000000000000000"], - }, { - // encoded transaction - "type": "encoded", - "to": "0x...", - "data": "0x...", - }, { - // native transfer - "type": "native-transfer", - "to": "0x...", - "value": "1000000000000000000", // in wei - }], - } - ``` - - In React applications that use the SDK, you can obtain the user auth token with the [`useAuthToken()`](/references/typescript/v5/useAuthToken) hook. - - In TypeScript applications, you can get it by calling `wallet.getAuthToken()` on a connected [`inAppWallet()`](/references/typescript/v5/inAppWallet) or [`ecosystemWallet()`](/references/typescript/v5/ecosystemWallet). + - - - - - On the backend, use your project secret key to send a transaction from any of your server wallets. - - ```http - POST /v1/transactions - Host: api.thirdweb.com - Content-Type: application/json - x-secret-key: - - { - "from": "0x...", // the server wallet address - "chainId": "1" // the chain id - "transactions": [{ - // contract call - "type": "contract-call", - "contractAddress": "0x...", - "method": "function transfer(address to, uint256 amount)", - "params": ["0x...", "1000000000000000000"], - }, { - // encoded transaction - "type": "encoded", - "to": "0x...", - "data": "0x...", - }, { - // native transfer - "type": "native-transfer", - "to": "0x...", - "value": "1000000000000000000", // in wei - }], - } - ``` - - - - - + ### Installation diff --git a/apps/portal/src/app/transactions/sponsor/page.mdx b/apps/portal/src/app/transactions/sponsor/page.mdx index fe38f22f178..647753f9ecf 100644 --- a/apps/portal/src/app/transactions/sponsor/page.mdx +++ b/apps/portal/src/app/transactions/sponsor/page.mdx @@ -6,6 +6,7 @@ import { createMetadata, Steps, Step, + OpenApiEndpoint, } from "@doc"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { WalletsSmartIcon } from "@/icons"; @@ -56,22 +57,9 @@ Sponsor gas fees using [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702), enab EIP-7702 is the default execution mode in the [thirdweb API](https://api.thirdweb.com/reference) for your user and server wallets. -```http -POST /v1/contracts/write -Host: api.thirdweb.com -Content-Type: application/json -x-secret-key: +Example sponsored contract write request: -{ - "from": "0x...", // your user or server wallet **EOA address** address - "chainId": "1", // your chain id - "calls": [{ - "contractAddress": "0x...", - "method": "function transfer(address to, uint256 amount)", - "params": ["0x...", "1000000000000000000"], - }], -} -``` + @@ -143,11 +131,10 @@ For chains that don't support EIP-7702, you can use EIP-4337 smart contract wall -ERC4337 execution is not supported in the high level thirdweb API, but you can use the lower level [transactions API](https://engine.thirdweb.com/reference#tag/write/post/v1/write/contract) with specific execution options: +ERC4337 execution is not supported in the high level thirdweb API, but you can use the lower level [Engine transactions API](https://engine.thirdweb.com/reference#tag/write/post/v1/write/contract) with specific execution options: ```http -POST /v1/contracts/write -Host: engine.thirdweb.com +POST https://engine.thirdweb.com/v1/write/contract Content-Type: application/json x-secret-key: diff --git a/apps/portal/src/app/wallets/custom-auth/page.mdx b/apps/portal/src/app/wallets/custom-auth/page.mdx index 5332ad44f2f..e7eec24f415 100644 --- a/apps/portal/src/app/wallets/custom-auth/page.mdx +++ b/apps/portal/src/app/wallets/custom-auth/page.mdx @@ -5,6 +5,7 @@ import { TabsContent, DocImage, createMetadata, + OpenApiEndpoint, } from "@doc"; import EWCustomAuth from "./images/customauth.png"; import EWCustomAuthdb from "./images/customauthdb.png"; @@ -67,18 +68,10 @@ You will be asked to enter the following values -```http -POST https://api.thirdweb.com/v1/wallets/user/generic-auth -Content-Type: application/json -x-client-id: -x-ecosystem-id: (optional) -x-ecosystem-partner-id: (optional) - -{ - "type": "jwt", - "jwt": "" -} -``` +", +}}/> @@ -198,18 +191,10 @@ Once you've logged in with your own auth, you can pass the user's JWT to the in- -```http -POST https://api.thirdweb.com/v1/wallets/user/generic-auth -Content-Type: application/json -x-client-id: -x-ecosystem-id: (optional) -x-ecosystem-partner-id: (optional) - -{ - "type": "auth-payload", - "payload": "" -} -``` +", +}} /> diff --git a/apps/portal/src/app/wallets/get-users/page.mdx b/apps/portal/src/app/wallets/get-users/page.mdx index 43106125e27..6fb35c8b225 100644 --- a/apps/portal/src/app/wallets/get-users/page.mdx +++ b/apps/portal/src/app/wallets/get-users/page.mdx @@ -1,5 +1,5 @@ import { Callout } from "@doc"; -import { createMetadata, ArticleIconCard, Stack } from "@doc"; +import { createMetadata, ArticleIconCard, Stack, OpenApiEndpoint } from "@doc"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { TypeScriptIcon } from "@/icons"; @@ -24,103 +24,68 @@ From your backend, you can list all your users and fetch the details of any user -### Endpoint +## Get Single User -`GET` request to the following endpoint: - -```http -GET /v1/wallets/user?email=user@example.com -Host: api.thirdweb.com -x-secret-key: -``` +Fetch details for a specific user by email, phone, wallet address, or user ID. ### Query Parameters -You can then query by different user identifiers: +You can query by any of these user identifiers: -- `address`: The user's wallet address that thirdweb has generated for them +- `address`: The user's wallet address that thirdweb generated - `email`: The user's email address - `phone`: The user's phone number -- `externalWalletAddress`: The user's wallet address that used to login via SIWE +- `externalWalletAddress`: The user's external wallet address (SIWE login) - `id`: The user's ID (for custom auth) ### Authentication -You need to include the following headers: - +Required headers: - `x-secret-key`: Your secret key for authentication - `x-ecosystem-id` (optional): Your ecosystem ID - `x-ecosystem-partner-id` (optional): Your ecosystem partner ID -### Example Requests +### Example -Here's an example curl command to fetch user details by email: + -```bash +For ecosystem wallets, include the ecosystem headers in your requests. -curl -X GET 'https://api.thirdweb.com/v1/wallets/user?email=user@example.com' \ - -H 'x-secret-key: YOUR_SECRET_KEY' + -``` + -Here's an example curl command to fetch user details by address: +## Get All Users -```bash -curl -X GET 'https://api.thirdweb.com/v1/wallets/user?address=0x123456789abcdef' \ - -H 'x-secret-key: YOUR_SECRET_KEY' -``` +List all users in your in-app or ecosystem wallet with pagination support. -Here's an example curl command to fetch the user details for an ecosystem owner: +### Query Parameters -```bash -curl -X GET 'https://api.thirdweb.com/v1/wallets/user?address=0x123456789abcdef' \ - -H 'x-secret-key: YOUR_SECRET_KEY' \ - -H 'x-ecosystem-id: ecosystem.YOUR_ECOSYSTEM_ID' \ - -H 'x-ecosystem-partner-id: YOUR_PARTNER_ID' -``` +- `page` (optional): Page number for pagination +- `limit` (optional): Maximum users per request (default: 50) -In both examples, replace `YOUR_SECRET_KEY` with your actual ThirdWeb Client Secret. +### Authentication + +Required headers: +- `x-secret-key`: Your secret key for authentication +- `x-ecosystem-id` (optional): Your ecosystem ID +- `x-ecosystem-partner-id` (optional): Your ecosystem partner ID -Replace `YOUR_ECOSYSTEM_ID` and `YOUR_PARTNER_ID` with your actual ecosystem ID and partner ID respectively. The partner ID can be one you set up for yourself as the ecosystem owner. + + For ecosystem wallets, the secret key must be from the same account as the ecosystem owner. + -### Response Format +### Example -The API returns a JSON array with the following structure for each user: + -```json -{ - "result": { - "pagination": { - "hasMore": false, - "limit": 50, - "page": 1 - }, - "wallets": [ - { - "address": "0x3aA70e68BBA8BC922a75d07388e368892c15c9Da", - "createdAt": "2025-07-21T22:58:12.834Z", - "profiles": [ - { - "id": "107302390467834615186", - "name": "Richard Hendricks", - "type": "google", - "email": "richard@piedpiper.com", - "picture": "https://lh3.googleusercontent.com/a/ACg8ocKC1D6ezzzaZxxUk4qtK_HCwVwpNamVopazXwklGBwuuHeSf_c=s96-c", - "givenName": "Richard", - "emailVerified": true - } - ] - } - ] - } -} -``` + -For more information, [view the full reference](https://api.thirdweb.com/reference#tag/wallets/get/v1/wallets/user) + -### Convenience Methods +## SDK Integration -If you are using the thirdweb SDK, you can use the `getUser` method to retrieve user details. +If you're using the thirdweb SDK, you can use the `getUser` method for easier integration: - - - - - -### Endpoint - -`GET` request to the following endpoint: - -```http -GET /v1/wallets/users -Host: api.thirdweb.com -x-secret-key: -``` - -### Query Parameters - -- `page` (optional): The page number to fetch (for pagination) -- `limit` (optional): Maximum number of users to return per request (defaults to 50) - -### Authentication - -You need to include the following headers: - -- `x-secret-key`: Your secret key for authentication -- `x-ecosystem-id` (optional): Your ecosystem ID -- `x-ecosystem-partner-id` (optional): Your ecosystem partner ID - - - For ecosystem wallets, the secret key have to be from the same account as the - ecosystem owner. - - -### Example Requests - -Here's an example curl command to fetch all users: - -```bash -curl -X GET 'https://api.thirdweb.com/v1/wallets/users?page=2&limit=100' \ - -H 'x-secret-key: YOUR_SECRET_KEY' \ - -H 'Content-Type: application/json' -``` - -### Response Format - -A successful API call returns an array of user objects in the following format: - -```json -{ - "result": { - "pagination": { - "hasMore": false, - "limit": 50, - "page": 1 - }, - "wallets": [ - { - "address": "0x3aA70e68BBA8BC922a75d07388e368892c15c9Da", - "createdAt": "2025-07-21T22:58:12.834Z", - "profiles": [ - { - "id": "107302390467834615186", - "name": "Richard Hendricks", - "type": "google", - "email": "richard@piedpiper.com", - "picture": "https://lh3.googleusercontent.com/a/ACg8ocKC1D6ezzzaZxxUk4qtK_HCwVwpNamVopazXwklGBwuuHeSf_c=s96-c", - "givenName": "Richard", - "emailVerified": true - }] - }, - ] -} -``` - - - diff --git a/apps/portal/src/app/wallets/page.mdx b/apps/portal/src/app/wallets/page.mdx index 4fa28d6e7d7..c5b869da205 100644 --- a/apps/portal/src/app/wallets/page.mdx +++ b/apps/portal/src/app/wallets/page.mdx @@ -10,6 +10,8 @@ import { TabsList, TabsTrigger, TabsContent, + OpenApiEndpoint, + Stack, } from "@doc"; import { ReactIcon, @@ -32,12 +34,7 @@ export const metadata = createMetadata({ # Get Started -Create user or server wallets, authenticate with your backend, connect to external wallets, and more. User wallets can be created using different authentication methods: - -- verification code (email, phone, etc.) -- oauth (google, apple, etc.) -- passkey -- sign in with ethereum +Create wallets for your users with flexible authentication options. Choose from email/phone verification, social OAuth, passkeys, or external wallet connections. @@ -73,67 +70,34 @@ Create user or server wallets, authenticate with your backend, connect to extern -### API Usage - -You can use the [thirdweb API](https://api.thirdweb.com/reference) to create user wallets. - -Authentication requires either `x-secret-key` (backend) or `x-client-id` (frontend) to be set in the request headers. - -#### Send a login code to the user - -```http -POST /v1/wallets/user/code -Host: api.thirdweb.com -Content-Type: application/json -x-client-id: +## API Authentication -{ - "type": "email", - "email": "user@example.com" -} -``` +Authenticating a user is done in two steps: -#### Verify the code and authenticate the user +1. Initiate authentication +2. Complete authentication -```http -POST /v1/wallets/user/code/verify -Host: api.thirdweb.com -Content-Type: application/json -x-client-id: +### Initiate Authentication +Start authentication with email, phone, passkey, or social providers -{ - "type": "email", - "email": "user@example.com", - "code": "123456", -} -``` + -Once authenticated, the endpoint will return the wallet address and a JWT token for usage with the rest of the API. +### Complete Authentication +Verify and complete the authentication process: - - - - You can create a server wallet with your project secret key and an identifier for the wallet: - - ```http - POST /v1/wallets/server - Host: api.thirdweb.com - Content-Type: application/json - x-secret-key: + - { - "identifier": "treasury-wallet", // your identifier - } - ``` +### Get Wallet Information +Retrieve authenticated user's wallet details: - This will return the server wallet address to use with the rest of the API. If the wallet already exists, the same wallet will be returned. + ### Installation - Install the thirdweb SDK in your TypeScript project: + Install the thirdweb SDK in your TypeScript project ### Installation - 1. Download the latest [thirdweb Unity SDK](https://github.com/thirdweb-dev/unity-sdk/releases) (.unitypackage file) + 1. Download the latest [thirdweb Unity SDK](https://github.com/thirdweb-dev/unity/releases) (.unitypackage file) 2. Import the package into your Unity project via Assets > Import Package > Custom Package ### Configure Client ID After importing the SDK: - 1. Go to Project Settings > Thirdweb - 2. Enter your Client ID from the thirdweb dashboard + 1. Drag the ThirdwebManager prefab into your scene. + 2. Enter your Client ID and Bundle ID from the thirdweb dashboard 3. Allowlist your game's Bundle ID on the thirdweb dashboard for security - ### Initialize the SDK - - Create a new script to manage wallet connections: - - ```csharp - using Thirdweb; - using UnityEngine; - using UnityEngine.UI; - - public class WalletManager : MonoBehaviour - { - private ThirdwebSDK sdk; - public Text walletAddressText; - public Button connectButton; - - void Start() - { - // Client ID is set in Project Settings > Thirdweb - sdk = new ThirdwebSDK("ethereum"); // Or any supported chain - connectButton.onClick.AddListener(ConnectWallet); - } - - public async void ConnectWallet() - { - try { - // Connect with an external wallet like Coinbase Wallet - string address = await sdk.wallet.Connect(new WalletConnection() { - provider = WalletProvider.CoinbaseWallet, - chainId = 1 // Ethereum Mainnet - }); - - walletAddressText.text = "Connected: " + address; - } - catch (System.Exception e) { - Debug.LogError("Error connecting wallet: " + e.Message); - } - } - } - ``` - - ### Using the Connect Wallet Prefab - - For a quicker implementation, use the provided prefab: - - 1. Add the `ThirdwebManager` prefab to your scene - 2. Configure your Client ID in the inspector - 3. Add the `ConnectWallet` prefab to your UI Canvas - 4. Connect the prefab to your ThirdwebManager - ### Implementing In-App Wallets Enable email login in Unity: @@ -463,17 +378,13 @@ Once authenticated, the endpoint will return the wallet address and a JWT token public async void ConnectWithEmail(string email) { try { - string address = await sdk.wallet.Connect(new WalletConnection() { - provider = WalletProvider.EmbeddedWallet, - email = email, - chainId = 1 // Ethereum Mainnet - }); - - walletAddressText.text = "Connected: " + address; - - // Read wallet balance - var balance = await sdk.wallet.GetBalance(); - Debug.Log($"Balance: {balance.DisplayValue} {balance.Symbol}"); + var inAppWalletOptions = new InAppWalletOptions(email: "myepicemail@domain.id"); + var options = new WalletOptions( + provider: WalletProvider.InAppWallet, + chainId: 1, + inAppWalletOptions: inAppWalletOptions + ); + var wallet = await ThirdwebManager.Instance.ConnectWallet(options); } catch (System.Exception e) { Debug.LogError("Error connecting wallet: " + e.Message); @@ -498,18 +409,15 @@ Once authenticated, the endpoint will return the wallet address and a JWT token ```csharp using Thirdweb; - - // For client-side applications: - var sdk = new ThirdwebSDK("ethereum", new ThirdwebSDK.Options - { - ClientId = "YOUR_CLIENT_ID" // From thirdweb dashboard - }); - - // For server-side applications: - // var sdk = new ThirdwebSDK("ethereum", new ThirdwebSDK.Options - // { - // SecretKey = Environment.GetEnvironmentVariable("THIRDWEB_SECRET_KEY") - // }); + + // For native applications + var client = ThirdwebClient.Create(clientId: "yourClientId", bundleId: "yourBundleId"); + + // For frontend applications + var client = ThirdwebClient.Create(clientId: "yourClientId"); + + // For backend applications + var client = ThirdwebClient.Create(secretKey: "yourSecretKey"); ``` ### Connect External Wallets @@ -517,17 +425,15 @@ Once authenticated, the endpoint will return the wallet address and a JWT token For .NET applications that need to interact with external wallets: ```csharp - // For server-side applications or wallet management - var privateKey = Environment.GetEnvironmentVariable("WALLET_PRIVATE_KEY"); // Never hardcode - var wallet = new PrivateKeyWallet(privateKey); + var privateKeyHex = "yourPrivateKeyHex"; // Should be securely stored and accessed + var wallet = await PrivateKeyWallet.Create(client, privateKeyHex); - await sdk.SetWallet(wallet); - var address = await sdk.Wallet.GetAddress(); + var address = await wallet.GetAddress(); Console.WriteLine($"Connected wallet address: {address}"); // Read wallet balance - var balance = await sdk.Wallet.GetBalance(); - Console.WriteLine($"Balance: {balance.DisplayValue} {balance.Symbol}"); + var balance = await wallet.GetBalance(chainId); + Console.WriteLine($"Balance: {balance.ToString().ToEth()}"); ``` ### Using In-App Wallets @@ -535,20 +441,9 @@ Once authenticated, the endpoint will return the wallet address and a JWT token Create wallets with email authentication: ```csharp - // Create an embedded wallet with email - var walletOptions = new EmbeddedWalletOptions - { - Email = "user@example.com", - AuthProvider = AuthProvider.Email - }; - - var wallet = new EmbeddedWallet(walletOptions); - await sdk.SetWallet(wallet); - - // Authenticate and get the wallet address - await wallet.Authenticate(); - var address = await sdk.Wallet.GetAddress(); - Console.WriteLine($"Connected with embedded wallet: {address}"); + var wallet = await InAppWallet.Create(client: client, email: "userEmail"); + await wallet.SendOTP(); // and fetch the otp + var address = await wallet.LoginWithOtp("userEnteredOTP"); // try catch and retry if needed ``` ### Reading Contract Data @@ -558,9 +453,9 @@ Once authenticated, the endpoint will return the wallet address and a JWT token ```csharp // Get a contract instance var contract = await ThirdwebContract.Create( - client: sdk.Client, - address: "0x...", - chain: Chain.Ethereum + client, + "contractAddress", + chainId, ); // Read a value from the contract diff --git a/apps/portal/src/app/wallets/pregenerate-wallets/page.mdx b/apps/portal/src/app/wallets/pregenerate-wallets/page.mdx index a9d5d30ed99..7e7b0aa3545 100644 --- a/apps/portal/src/app/wallets/pregenerate-wallets/page.mdx +++ b/apps/portal/src/app/wallets/pregenerate-wallets/page.mdx @@ -1,4 +1,4 @@ -import { Callout } from "@doc"; +import { Callout, OpenApiEndpoint } from "@doc"; import { createMetadata, ArticleIconCard } from "@doc"; export const metadata = createMetadata({ @@ -12,132 +12,30 @@ export const metadata = createMetadata({ # Pregenerate Wallets -### What is Wallet Pregeneration? +Create wallets for users before they authenticate, enabling smoother onboarding and pre-funded asset distribution. -Wallet pregeneration is a technique where you create wallets for users before they actually sign up or authenticate. This allows you to: +## Use Cases -1. Pre assign wallets to users before they sign up -2. Reduce the time users spend waiting during the onboarding process -3. Pre-fund wallets with tokens or NFTs before users claim them -4. Create smoother user experiences for gaming and other applications +- **Pre-assign wallets** to users before signup +- **Reduce onboarding time** by eliminating wallet creation delays +- **Pre-fund wallets** with tokens or NFTs before users claim them +- **Create smooth experiences** for gaming and app onboarding +- **Distribute assets** like game items, welcome bonuses, or promotional NFTs -### Why Use Wallet Pregeneration? +## API Reference -You can distribute assets to wallets before users claim them, enabling: -- Pre-loaded game assets -- Welcome bonus tokens -- Promotional NFTs -- Airdrops + ---- - -## Endpoint - -To pregenerate an in-app or ecosystem wallet wallet, you can make a `POST` request to the following endpoint: - -```http -POST https://api.thirdweb.com/v1/wallets/user/pregenerate -Content-Type: application/json -x-secret-key: -x-ecosystem-id: (optional) -x-ecosystem-partner-id: (optional) - -{ - "type": "email", - "email": "user@example.com" -} -``` - -## Request Body - -The request body should be a JSON object with the following parameters: - -- `type`: The type of wallet identifier to pregenerate a wallet for: - - Email based: `email`, `google`, `facebook`, `discord` - - Phone based: `phone` - - Wallet based: `signer` - - User ID based: `custom_jwt` or `custom_auth_endpoint` -- `email` or `phone` or `userId` or `walletAddress`: The user identifier associated with the wallet to be generated - -### Email based wallets - -``` -{ type: "email", email: "user@example.com" } -``` - -When the user logs in with any method associated with that email (including google, facebook, discord auth), they will get access to the same pregenerated wallet. - -### Phone based wallets - -``` -{ type: "phone", phone: "+1321123321" } -``` - -### Custom user id based wallets - -``` -{ type: "custom_auth_endpoint", userId: "some_user_id" } -``` - -{/* TODO: update link when custom auth documentation has been updated */} -Use this when [bringing your own authentication method](/wallets/custom-auth). When the user logs in, if the user ids you provide from the auth endpoint match, they will get access to the same pregenerated wallet. - -## Headers - -You need to include the following headers: - -- `Content-Type`: Must be set to `application/json` -- `x-secret-key`: Your secret key for authentication -- `x-ecosystem-id` (optional): Your ecosystem ID -- `x-ecosystem-partner-id` (optional): Your ecosystem partner ID - -## Example Request - -Here's an example curl command to pregenerate a thirdweb wallet for the user `user@example.com`: - -```bash -curl -X POST 'https://api.thirdweb.com/v1/wallets/user/pregenerate' \ - -H 'x-ecosystem-id: ecosystem.example-eco-123' \ - -H 'x-ecosystem-partner-id: 1415d24e-c7b0-4fce-846e-740841ef2c32' \ - -H 'x-secret-key: ' \ - -H 'Content-Type: application/json' \ - -d '{ - "type": "email", - "email": "user@example.com" - }' -``` - -Replace the header values with your actual client ID, ecosystem ID, and secret key. - - -For ecosystem wallets, the secret key have to be from the same account as the ecosystem owner. If this is an issue, please reach out to us. + +For ecosystem wallets, the secret key must be from the same account as the ecosystem owner. - -## Response Format - -A successful API call returns a JSON object in the following format: - -```json -{ - "result": { - "address": "string", - "createdAt": "string", - "profile": "object[]" - } -} -``` - - -Use [`predictSmartAccountAddress`](/references/typescript/v5/predictSmartAccountAddress) to generate the corresponding smart contract wallet address. -The `address` field always corresponds to the EOA address (admin wallet) that has been pregenerated. + +The API returns the EOA (admin wallet) address. Use [`predictSmartAccountAddress`](/references/typescript/v5/predictSmartAccountAddress) to get the corresponding smart contract wallet address. +## Next Steps -## What's Next - -Pre-generating is independent and doesn't change the user's experience. - -Your users can continue to login as per usual. When they do, they will be assigned the pregenerated wallet. +Pregeneration doesn't change the user experience. Users can login normally and will automatically receive their pregenerated wallet. -For more information on signing in, see the [User Wallets](/wallets/users) section. +For more information, see the [User Wallets](/wallets/users) documentation. diff --git a/apps/portal/src/app/wallets/server/page.mdx b/apps/portal/src/app/wallets/server/page.mdx index 8d9a3a267d5..a7631add06c 100644 --- a/apps/portal/src/app/wallets/server/page.mdx +++ b/apps/portal/src/app/wallets/server/page.mdx @@ -1,4 +1,4 @@ -import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; +import { Tabs, TabsList, TabsTrigger, TabsContent, OpenApiEndpoint } from "@doc"; import { TypeScriptIcon, EngineIcon } from "@/icons"; import { createMetadata } from "@doc"; @@ -33,27 +33,38 @@ Server wallets are wallets that are managed by your own application, like a trea Once created, you can use your server wallet by passing it as the `from` field of the [thirdweb API](https://api.thirdweb.com/reference#tag/transactions/post/v1/transactions). -### Create a new Server Wallet Programmatically +### Create a new Server Wallet -```http -POST /v1/wallets/server -Host: api.thirdweb.com -Content-Type: application/json -x-secret-key: +You can create new server wallets via API by just passing an identifier. This can be any string, but we recommend using a descriptive name. -{ - "identifier": "My Server Wallet" -} -``` + ### List all Server Wallets -```http -GET /v1/wallets/server -Host: api.thirdweb.com -Content-Type: application/json -x-secret-key: -``` +You can also list all server wallets for your project to get their addresses and identifiers. + + diff --git a/apps/portal/src/components/Document/APIEndpointMeta/ApiEndpoint.tsx b/apps/portal/src/components/Document/APIEndpointMeta/ApiEndpoint.tsx index eff115b8664..6c1f295e0b7 100644 --- a/apps/portal/src/components/Document/APIEndpointMeta/ApiEndpoint.tsx +++ b/apps/portal/src/components/Document/APIEndpointMeta/ApiEndpoint.tsx @@ -1,96 +1,139 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { cn } from "../../../lib/utils"; import { CodeBlock } from "../Code"; +import { Details } from "../Details"; import { Heading } from "../Heading"; -import { Paragraph } from "../Paragraph"; +import { DynamicRequestExample } from "./DynamicRequestExample"; import { RequestExample } from "./RequestExample"; -export type APIParameter = - | { - name: string; - required: false; - description: React.ReactNode; - type?: string; - example?: - | string - | boolean - | number - | object - | Array; - } - | { - name: string; - required: true; - example: - | string - | boolean - | number - | object - | Array; - description: React.ReactNode; - type?: string; - }; - -type ApiEndpointMeta = { +export type APIParameter = { + name: string; + required: boolean; + description: React.ReactNode; + type?: string; + example?: + | string + | boolean + | number + | object + | Array; +}; + +type RequestExampleType = { + title: string; + bodyParameters: APIParameter[]; +}; + +export type ApiEndpointMeta = { title: string; description: React.ReactNode; + referenceUrl: string; path: string; origin: string; method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; request: { pathParameters: APIParameter[]; headers: APIParameter[]; + queryParameters: APIParameter[]; bodyParameters: APIParameter[]; + // Support for multiple request examples (oneOf schemas) + requestExamples?: RequestExampleType[]; }; responseExamples: Record; }; export function ApiEndpoint(props: { metadata: ApiEndpointMeta }) { - const { responseExamples } = props.metadata; + const { responseExamples, request } = props.metadata; + // If we have multiple request examples (oneOf schemas), create code examples for each const requestExamples: Array<{ lang: "javascript" | "bash"; code: string; label: string; - }> = [ - { - code: createFetchCommand({ - metadata: props.metadata, - }), - label: "Fetch", - lang: "javascript", - }, - { - code: createCurlCommand({ - metadata: props.metadata, - }), - label: "Curl", - lang: "bash", - }, - ]; + format: "fetch" | "curl"; + exampleType?: string; + bodyParameters?: APIParameter[]; + }> = []; + + if (request.requestExamples && request.requestExamples.length > 0) { + // Create examples for each oneOf schema + for (const requestExample of request.requestExamples) { + const metadataWithExample = { + ...props.metadata, + request: { + ...request, + bodyParameters: requestExample.bodyParameters, + }, + }; + + requestExamples.push( + { + code: createFetchCommand({ metadata: metadataWithExample }), + label: `Fetch - ${requestExample.title}`, + lang: "javascript", + format: "fetch", + exampleType: requestExample.title, + bodyParameters: requestExample.bodyParameters, + }, + { + code: createCurlCommand({ metadata: metadataWithExample }), + label: `Curl - ${requestExample.title}`, + lang: "bash", + format: "curl", + exampleType: requestExample.title, + bodyParameters: requestExample.bodyParameters, + }, + ); + } + } else { + // Default single example + requestExamples.push( + { + code: createFetchCommand({ metadata: props.metadata }), + label: "Fetch", + lang: "javascript", + format: "fetch", + bodyParameters: request.bodyParameters, + }, + { + code: createCurlCommand({ metadata: props.metadata }), + label: "Curl", + lang: "bash", + format: "curl", + bodyParameters: request.bodyParameters, + }, + ); + } const responseKeys = Object.keys(responseExamples); return (
-
- - {props.metadata.title} - - {props.metadata.description} -
-
- -
- + Request -
- on server and pass it to client - codeExamples={requestExamples.map((example) => { - return { + + {/* Use DynamicRequestExample for multiple examples, regular for single */} + {request.requestExamples && request.requestExamples.length > 0 ? ( + + ) : ( +
+ ({ code: ( ), label: example.label, - }; - })} - endpointUrl={props.metadata.path} - method={props.metadata.method} - /> -
+ }))} + endpointUrl={props.metadata.path} + referenceUrl={props.metadata.referenceUrl} + method={props.metadata.method} + /> + + {/* Parameters section inside the card */} +
+ {request.headers.length > 0 && ( + + )} + + {request.pathParameters.length > 0 && ( + + )} + + {request.queryParameters.length > 0 && ( + + )} + + {request.bodyParameters.length > 0 && ( + + )} +
+
+ )}
- + Response
@@ -149,68 +228,111 @@ export function ApiEndpoint(props: { metadata: ApiEndpointMeta }) { ); } -// function ParameterSection(props: { -// title: string; -// parameters: APIParameter[]; -// }) { -// return ( -//
-// -// {props.title} -// -//
-// {props.parameters -// .sort((a, b) => { -// if (a.required === b.required) { -// return 0; -// } -// return a.required ? -1 : 1; -// }) -// .map((param) => ( -// -// ))} -//
-//
-// ); -// } - -// function ParameterItem({ param }: { param: APIParameter }) { -// return ( -//
-//
-// {param.description} -// {param.type && ( -//
-//

Type

-// -//
-// )} -//
-//
-// ); -// } +function ParameterSection(props: { + title: string; + parameters: APIParameter[]; +}) { + return ( +
+
+ {props.title} + + {props.parameters.length} + +
+ } + accordionItemClassName="border-0 my-0" + accordionTriggerClassName="p-4 hover:bg-muted/50 transition-colors" + > +
+ {props.parameters + .sort((a, b) => { + if (a.required === b.required) { + return 0; + } + return a.required ? -1 : 1; + }) + .map((param) => ( + + ))} +
+ +
+ ); +} + +function InlineParameterItem({ param }: { param: APIParameter }) { + return ( +
+
+ + {param.name} + + {param.type && ( + + {param.type} + + )} + {param.required && ( + + Required + + )} +
+ + {param.description && ( +
{param.description}
+ )} + + {param.example !== undefined && ( +
+ Example: + +
+ )} +
+ ); +} function createCurlCommand(params: { metadata: ApiEndpointMeta }) { - const url = `${params.metadata.origin}${params.metadata.path}`; + let url = `${params.metadata.origin}${params.metadata.path}`; const bodyObj: Record = {}; - const headers = params.metadata.request.headers - .filter((h) => h.example !== undefined) + // Add query parameters to URL + const queryParams = params.metadata.request.queryParameters + .filter((q) => q.example !== undefined) + .map((q) => `${q.name}=${encodeURIComponent(String(q.example))}`) + .join("&"); + + if (queryParams) { + url += `?${queryParams}`; + } + + const displayHeaders = params.metadata.request.headers.filter( + (h) => h.example !== undefined, + ); + + // always show secret key header in examples + displayHeaders.push({ + name: "x-secret-key", + example: "", + description: + "Project secret key - for backend usage only. Should not be used in frontend code.", + required: false, + }); + + const headers = displayHeaders .map((h) => { return `-H "${h.name}:${ typeof h.example === "object" && h !== null @@ -240,9 +362,40 @@ function createFetchCommand(params: { metadata: ApiEndpointMeta }) { const headersObj: Record = {}; const bodyObj: Record = {}; const { request } = params.metadata; - const url = `${params.metadata.origin}${params.metadata.path}`; + let url = `${params.metadata.origin}${params.metadata.path}`; + + // Add query parameters to URL + const queryParams = request.queryParameters + .filter((q) => q.example !== undefined) + .map((q) => `${q.name}=${encodeURIComponent(String(q.example))}`) + .join("&"); + + if (queryParams) { + url += `?${queryParams}`; + } + + const displayHeaders = request.headers.filter((h) => h.example !== undefined); + + // always show secret key header in examples + if (params.metadata.path.includes("/v1/auth")) { + displayHeaders.push({ + name: "x-client-id", + example: "", + description: + "Project client ID - for frontend usage on authorized domains.", + required: false, + }); + } else { + displayHeaders.push({ + name: "x-secret-key", + example: "", + description: + "Project secret key - for backend usage only. Should not be used in frontend code.", + required: false, + }); + } - for (const param of request.headers) { + for (const param of displayHeaders) { if (param.example !== undefined) { headersObj[param.name] = param.example; } @@ -251,6 +404,8 @@ function createFetchCommand(params: { metadata: ApiEndpointMeta }) { for (const param of request.bodyParameters) { if (param.example !== undefined) { bodyObj[param.name] = param.example; + } else { + bodyObj[param.name] = param.type || ""; } } diff --git a/apps/portal/src/components/Document/APIEndpointMeta/DynamicRequestExample.tsx b/apps/portal/src/components/Document/APIEndpointMeta/DynamicRequestExample.tsx new file mode 100644 index 00000000000..5ae508c515c --- /dev/null +++ b/apps/portal/src/components/Document/APIEndpointMeta/DynamicRequestExample.tsx @@ -0,0 +1,209 @@ +"use client"; + +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { useEffect, useState } from "react"; +import CodeClient, { CodeLoading } from "../../code/code.client"; +import { Details } from "../Details"; +import type { APIParameter } from "./ApiEndpoint"; +import { RequestExample } from "./RequestExample"; + +interface DynamicRequestExampleProps { + requestExamples: Array<{ + lang: "javascript" | "bash"; + code: string; + label: string; + format: "fetch" | "curl"; + exampleType?: string; + bodyParameters?: APIParameter[]; + }>; + endpointUrl: string; + referenceUrl: string; + method: string; + pathParameters: APIParameter[]; + headers: APIParameter[]; + queryParameters: APIParameter[]; + hasMultipleExamples: boolean; +} + +function InlineParameterItem({ param }: { param: APIParameter }) { + return ( +
+
+ + {param.name} + + {param.type && ( + + {param.type} + + )} + {param.required && ( + + Required + + )} +
+ + {param.description && ( +
{param.description}
+ )} + + {param.example !== undefined && ( +
+ Example: + } + scrollableContainerClassName="m-0" + scrollableClassName="max-h-[200px]" + /> +
+ )} +
+ ); +} + +function ParameterSection(props: { + title: string; + parameters: APIParameter[]; +}) { + if (props.parameters.length === 0) return null; + + return ( +
+
+ {props.title} + + {props.parameters.length} + +
+ } + accordionItemClassName="border-0 my-0" + accordionTriggerClassName="p-4 hover:bg-muted/50 transition-colors" + > +
+ {props.parameters + .sort((a, b) => { + if (a.required === b.required) { + return 0; + } + return a.required ? -1 : 1; + }) + .map((param) => ( + + ))} +
+ +
+ ); +} + +const queryClient = new QueryClient(); + +export function DynamicRequestExample(props: DynamicRequestExampleProps) { + const [selectedFormat, setSelectedFormat] = useState<"fetch" | "curl">( + "fetch", + ); + const [selectedExampleType, setSelectedExampleType] = useState(""); + + // Initialize selected example type from first available example + useEffect(() => { + if (props.requestExamples.length > 0) { + const firstExampleType = + props.requestExamples.find((ex) => ex.format === "fetch") + ?.exampleType || ""; + setSelectedExampleType(firstExampleType); + } + }, [props.requestExamples]); + + // Get the current example based on selected format and example type + const selectedExample = + props.requestExamples.find( + (ex) => + ex.format === selectedFormat && ex.exampleType === selectedExampleType, + ) || + props.requestExamples.find((ex) => ex.format === selectedFormat) || + props.requestExamples[0]; + + // Get the current body parameters based on selected example + const currentBodyParameters = selectedExample?.bodyParameters || []; + + return ( + +
+ ({ + code: ( + } + scrollableContainerClassName="m-0" + lang={example.lang} + /> + ), + label: example.label, + format: example.format, + exampleType: example.exampleType, + }))} + endpointUrl={props.endpointUrl} + referenceUrl={props.referenceUrl} + method={props.method} + hasSeparateDropdowns={props.hasMultipleExamples} + selectedExample={ + selectedExample + ? { + code: ( + } + scrollableContainerClassName="m-0" + lang={selectedExample.lang} + /> + ), + label: selectedExample.label, + format: selectedExample.format, + exampleType: selectedExample.exampleType, + } + : undefined + } + selectedFormat={selectedFormat} + selectedExampleType={selectedExampleType} + onFormatChange={(format) => { + setSelectedFormat(format); + }} + onExampleTypeChange={(exampleType) => { + setSelectedExampleType(exampleType); + }} + /> + + {/* Parameters section inside the card */} +
+ + + + +
+
+
+ ); +} diff --git a/apps/portal/src/components/Document/APIEndpointMeta/OpenApiEndpoint.tsx b/apps/portal/src/components/Document/APIEndpointMeta/OpenApiEndpoint.tsx new file mode 100644 index 00000000000..7a57f6dad94 --- /dev/null +++ b/apps/portal/src/components/Document/APIEndpointMeta/OpenApiEndpoint.tsx @@ -0,0 +1,511 @@ +import type { OpenAPIV3_1 } from "openapi-types"; +import { cache, Suspense } from "react"; +import { + type APIParameter, + ApiEndpoint, + type ApiEndpointMeta, +} from "./ApiEndpoint"; + +// OpenAPI 3.0 types (simplified for our needs) +type OpenApiSpec = OpenAPIV3_1.Document; + +interface OpenApiEndpointProps { + specUrl?: string; + path: string; + method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; + requestBodyOverride?: Record; + responseExampleOverride?: Record; +} + +// Cache the OpenAPI spec fetch for 1 hour +const fetchOpenApiSpec = cache(async (url: string): Promise => { + const response = await fetch(url, { + next: { revalidate: 3600 }, // Cache for 1 hour (3600 seconds) + }); + if (!response.ok) { + throw new Error(`Failed to fetch OpenAPI spec: ${response.statusText}`); + } + return response.json(); +}); + +// Recursively generate example values from OpenAPI schema +function generateExampleFromSchema(schema: any, visited = new Set()): any { + // Prevent infinite recursion with circular references + if (visited.has(schema)) { + return {}; + } + + if (!schema || typeof schema !== "object") { + return null; + } + + // If there's an explicit example, use it + if (schema.example !== undefined) { + return schema.example; + } + + // If there are examples array, use the first one + if ( + schema.examples && + Array.isArray(schema.examples) && + schema.examples.length > 0 + ) { + return schema.examples[0]; + } + + // If there's a default value, use it + if (schema.default !== undefined) { + return schema.default; + } + + visited.add(schema); + + try { + switch (schema.type) { + case "string": + if (schema.enum && schema.enum.length > 0) { + // Join all enum values with | separator for display + return schema.enum.join(" | "); + } + if (schema.format === "date-time") { + return new Date().toISOString(); + } + if (schema.format === "date") { + return new Date().toISOString().split("T")[0]; + } + if (schema.format === "email") { + return "user@example.com"; + } + if (schema.format === "uri" || schema.format === "url") { + return "https://example.com"; + } + return "string"; + + case "number": + case "integer": + if (schema.enum && schema.enum.length > 0) { + // Join all enum values with | separator for display + return schema.enum.join(" | "); + } + if (schema.minimum !== undefined) { + return schema.minimum; + } + if (schema.maximum !== undefined) { + return Math.min(schema.maximum, 100); + } + return schema.type === "integer" ? 0 : 0.0; + + case "boolean": + return true; + + case "array": + if (schema.items) { + const itemExample = generateExampleFromSchema(schema.items, visited); + return [itemExample]; + } + return []; + + case "object": { + const obj: Record = {}; + + if (schema.properties) { + const required = schema.required || []; + + // Always include required properties + for (const requiredProp of required) { + if (schema.properties[requiredProp]) { + obj[requiredProp] = generateExampleFromSchema( + schema.properties[requiredProp], + visited, + ); + } + } + + // Include some optional properties (up to 3 additional ones) + const optionalProps = Object.keys(schema.properties).filter( + (prop) => !required.includes(prop), + ); + const propsToInclude = optionalProps.slice(0, 3); + + for (const prop of propsToInclude) { + obj[prop] = generateExampleFromSchema( + schema.properties[prop], + visited, + ); + } + } + + // If no properties defined but additionalProperties is allowed + if ( + Object.keys(obj).length === 0 && + schema.additionalProperties !== false + ) { + obj.property = "value"; + } + + return obj; + } + + case "null": + return null; + + default: + // Handle allOf, oneOf, anyOf + if (schema.allOf && Array.isArray(schema.allOf)) { + const merged: Record = {}; + for (const subSchema of schema.allOf) { + const subExample = generateExampleFromSchema(subSchema, visited); + if (typeof subExample === "object" && subExample !== null) { + Object.assign(merged, subExample); + } + } + return merged; + } + + if ( + schema.oneOf && + Array.isArray(schema.oneOf) && + schema.oneOf.length > 0 + ) { + return generateExampleFromSchema(schema.oneOf[0], visited); + } + + if ( + schema.anyOf && + Array.isArray(schema.anyOf) && + schema.anyOf.length > 0 + ) { + return generateExampleFromSchema(schema.anyOf[0], visited); + } + + // If we can't determine the type, return a generic object + return schema.properties + ? generateExampleFromSchema( + { type: "object", properties: schema.properties }, + visited, + ) + : "value"; + } + } finally { + visited.delete(schema); + } +} + +function transformOpenApiToApiEndpointMeta( + spec: OpenApiSpec, + path: string, + method: string, + requestBodyOverride?: Record, + responseExampleOverride?: Record, +): ApiEndpointMeta { + const pathItem = spec.paths?.[path]; + if (!pathItem) { + throw new Error(`Path ${path} not found in OpenAPI spec`); + } + + const operation = pathItem[ + method.toLowerCase() as keyof typeof pathItem + ] as OpenAPIV3_1.OperationObject; + if (!operation) { + throw new Error(`Method ${method} not found for path ${path}`); + } + + // Get the base URL from servers + const baseUrl = spec.servers?.[0]?.url || ""; + + // Transform parameters + const pathParameters: APIParameter[] = []; + const headers: APIParameter[] = []; + const queryParameters: APIParameter[] = []; + + if (operation.parameters) { + for (const param of operation.parameters as OpenAPIV3_1.ParameterObject[]) { + const schema = param.schema as OpenAPIV3_1.SchemaObject; + const apiParam: APIParameter = { + name: param.name, + description: param.description || "", + type: schema?.type || "string", + required: param.required || false, + example: param.example || schema?.example || schema?.default, + } as APIParameter; + + switch (param.in) { + case "path": + pathParameters.push(apiParam); + break; + case "header": + headers.push(apiParam); + break; + case "query": + queryParameters.push(apiParam); + break; + } + } + } + + // push default headers hardcoded for now + headers.push({ + name: "x-secret-key", + type: "backend", + description: + "Project secret key - for backend usage only. Should not be used in frontend code.", + required: false, + example: undefined, + }); + headers.push({ + name: "x-client-id", + type: "frontend", + description: + "Project client ID - for frontend usage on authorized domains.", + required: false, + example: undefined, + }); + headers.push({ + name: "x-ecosystem-id", + type: "optional", + description: "Ecosystem ID - for ecosystem wallets.", + required: false, + example: undefined, + }); + headers.push({ + name: "x-ecosystem-partner-id", + type: "optional", + description: "Ecosystem partner ID - for ecosystem wallets.", + required: false, + example: undefined, + }); + + if ( + method === "POST" && + !path.includes("/v1/contracts/read") && + !path.includes("/v1/auth") + ) { + headers.push({ + name: "Authorization", + type: "frontend", + description: "Bearer token (JWT) for user wallet authentication", + required: false, + example: undefined, + }); + } + + // Transform request body parameters + const bodyParameters: APIParameter[] = []; + const requestExamples: Array<{ + title: string; + bodyParameters: APIParameter[]; + }> = []; + + // Use override if provided + if (requestBodyOverride) { + for (const [key, value] of Object.entries(requestBodyOverride)) { + const apiParam: APIParameter = { + name: key, + description: "", + required: false, + example: value, + } as APIParameter; + + bodyParameters.push(apiParam); + } + } else if (operation.requestBody) { + const content = (operation.requestBody as OpenAPIV3_1.RequestBodyObject) + .content; + const jsonContent = content["application/json"]; + const schema = jsonContent?.schema as OpenAPIV3_1.SchemaObject; + + // Check if this is a oneOf schema (multiple possible request bodies) + if (schema?.oneOf && Array.isArray(schema.oneOf)) { + // Parse each oneOf schema into separate request examples + for (const [index, oneOfSchema] of schema.oneOf.entries()) { + const schema = oneOfSchema as any; + const title = schema.title || `Option ${index + 1}`; + const required = schema.required || []; + const schemaBodyParameters: APIParameter[] = []; + + // Use schema examples if available + const schemaExamples = schema.examples || []; + const schemaExample = schemaExamples[index]; + + if (schemaExample && typeof schemaExample === "object") { + // Use the provided example + for (const [key, value] of Object.entries(schemaExample)) { + const property = schema.properties?.[key]; + const apiParam: APIParameter = { + name: key, + description: property?.description || "", + type: property?.type, + required: required.includes(key), + example: value as any, + }; + schemaBodyParameters.push(apiParam); + } + } else if (schema.properties) { + // Generate from schema properties + for (const [propName, propSchema] of Object.entries( + schema.properties, + )) { + const prop = propSchema as any; + const apiParam: APIParameter = { + name: propName, + description: prop.description || "", + type: prop.type, + required: required.includes(propName), + example: generateExampleFromSchema(prop) as any, + }; + schemaBodyParameters.push(apiParam); + } + } + + requestExamples.push({ + title, + bodyParameters: schemaBodyParameters, + }); + } + } else { + // Single schema (not oneOf) + const example = schema?.example || schema?.examples?.[0]; + const required = schema?.required || []; + + if (example) { + // If there's a global example use that + if (typeof example === "object" && example !== null) { + for (const [key, value] of Object.entries(example)) { + const apiParam: APIParameter = { + name: key, + description: schema?.properties?.[key]?.description || "", + required: required.includes(key), + example: value, + } as APIParameter; + + bodyParameters.push(apiParam); + } + } + } else if (schema?.properties) { + for (const [propName, propSchema] of Object.entries( + schema.properties, + )) { + const prop = propSchema as any; + const apiParam: APIParameter = { + name: propName, + description: prop.description || "", + type: prop.type, + required: required.includes(propName), + example: prop.example || generateExampleFromSchema(prop), + } as APIParameter; + + bodyParameters.push(apiParam); + } + } + } + } + + // Transform responses + const responseExamples: Record = {}; + + // Use override if provided, otherwise generate from OpenAPI spec + if (responseExampleOverride) { + responseExamples["200"] = JSON.stringify(responseExampleOverride, null, 2); + } else { + for (const [statusCode, response] of Object.entries( + (operation as OpenAPIV3_1.OperationObject).responses || {}, + )) { + const content = (response as OpenAPIV3_1.ResponseObject).content; + let example = ""; + + if (content) { + const jsonContent = content["application/json"]; + if (jsonContent?.schema) { + // Use the recursive function to generate examples from schema + const generatedExample = generateExampleFromSchema( + jsonContent.schema, + ); + example = JSON.stringify(generatedExample, null, 2); + } else { + example = JSON.stringify({ message: response.description }, null, 2); + } + } else { + example = JSON.stringify({ message: response.description }, null, 2); + } + + responseExamples[statusCode] = example; + } + } + + const tag = operation.tags?.[0] || ""; + + return { + title: operation.summary || `${method.toUpperCase()} ${path}`, + description: operation.description || operation.summary || "", + path, + origin: baseUrl, + method: method.toUpperCase() as "GET" | "POST" | "PUT" | "DELETE" | "PATCH", + referenceUrl: generateReferenceUrl(tag, path, method), + request: { + pathParameters, + headers, + queryParameters, + bodyParameters, + // Include request examples if we have oneOf schemas + requestExamples: requestExamples.length > 0 ? requestExamples : undefined, + }, + responseExamples, + }; +} + +function LoadingFallback() { + return ( +
+
+ Loading API documentation... +
+
+ ); +} + +export function OpenApiEndpoint(props: OpenApiEndpointProps) { + return ( + }> + + + ); +} + +async function OpenApiEndpointInner({ + specUrl = "https://api.thirdweb.com/openapi.json", + path, + method = "GET", + requestBodyOverride, + responseExampleOverride, +}: OpenApiEndpointProps) { + try { + const spec = await fetchOpenApiSpec(specUrl); + const metadata = transformOpenApiToApiEndpointMeta( + spec, + path, + method, + requestBodyOverride, + responseExampleOverride, + ); + return ; + } catch (error) { + return ( +
+
+ Failed to load OpenAPI specification:{" "} + {error instanceof Error ? error.message : "Unknown error"} +
+
+ ); + } +} + +const BASE_API_URL = "https://api.thirdweb.com"; + +function generateReferenceUrl( + tag: string, + path: string, + method: string, +): string { + return `${BASE_API_URL}/reference#tag/${tag.toLowerCase()}/${method.toLowerCase()}${path}`; +} diff --git a/apps/portal/src/components/Document/APIEndpointMeta/RequestExample.tsx b/apps/portal/src/components/Document/APIEndpointMeta/RequestExample.tsx index 988165a6662..d3cb2bd0447 100644 --- a/apps/portal/src/components/Document/APIEndpointMeta/RequestExample.tsx +++ b/apps/portal/src/components/Document/APIEndpointMeta/RequestExample.tsx @@ -1,6 +1,7 @@ "use client"; -import { useState } from "react"; +import { ExternalLinkIcon } from "lucide-react"; +import { useEffect, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Select, @@ -14,11 +15,61 @@ export function RequestExample(props: { codeExamples: Array<{ label: string; code: React.ReactElement; + format?: "fetch" | "curl"; + exampleType?: string; }>; method: string; endpointUrl: string; + referenceUrl: string; + onExampleChange?: (label: string) => void; + onFormatChange?: (format: "fetch" | "curl") => void; + onExampleTypeChange?: (exampleType: string) => void; + hasSeparateDropdowns?: boolean; + selectedExample?: (typeof props.codeExamples)[0]; + selectedFormat?: "fetch" | "curl"; + selectedExampleType?: string; }) { - const [selectedExample, setSelectedExample] = useState(props.codeExamples[0]); + const [internalSelectedExample, setInternalSelectedExample] = useState( + props.codeExamples[0], + ); + const [internalSelectedFormat, setInternalSelectedFormat] = useState< + "fetch" | "curl" + >("fetch"); + const [internalSelectedExampleType, setInternalSelectedExampleType] = + useState(""); + + // Use props values if provided, otherwise use internal state + const selectedExample = props.selectedExample || internalSelectedExample; + const selectedFormat = props.selectedFormat || internalSelectedFormat; + const selectedExampleType = + props.selectedExampleType || internalSelectedExampleType; + + // Initialize selected example type from first example (only for internal state) + useEffect(() => { + if ( + props.hasSeparateDropdowns && + props.codeExamples.length > 0 && + !props.selectedExampleType + ) { + const firstExampleType = + props.codeExamples.find((ex) => ex.format === "fetch")?.exampleType || + ""; + setInternalSelectedExampleType(firstExampleType); + // Set the initial selected example based on format and example type + const initialExample = props.codeExamples.find( + (ex) => + ex.format === selectedFormat && ex.exampleType === firstExampleType, + ); + if (initialExample) { + setInternalSelectedExample(initialExample); + } + } + }, [ + props.hasSeparateDropdowns, + props.codeExamples, + selectedFormat, + props.selectedExampleType, + ]); return (
@@ -28,28 +79,108 @@ export function RequestExample(props: { {props.method} - {props.endpointUrl} + + {props.endpointUrl} + +
- + {props.hasSeparateDropdowns ? ( +
+ {/* Format Dropdown (Fetch/Curl) */} + + + {/* Example Type Dropdown */} + {(() => { + const exampleTypes = [ + ...new Set( + props.codeExamples + .map((ex) => ex.exampleType) + .filter(Boolean), + ), + ]; + return exampleTypes.length > 1 ? ( + + ) : null; + })()} +
+ ) : ( + + )}
{selectedExample?.code}
diff --git a/apps/portal/src/components/Document/APIEndpointMeta/common.tsx b/apps/portal/src/components/Document/APIEndpointMeta/common.tsx deleted file mode 100644 index 799d67a0363..00000000000 --- a/apps/portal/src/components/Document/APIEndpointMeta/common.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type { APIParameter } from "./ApiEndpoint"; - -export const secretKeyHeaderParameter: APIParameter = { - description: "Your project secret key for authentication.", - example: "", - name: "x-secret-key", - required: true, - type: "string", -}; diff --git a/apps/portal/src/components/Document/AuthMethodsTabs.tsx b/apps/portal/src/components/Document/AuthMethodsTabs.tsx index dc21f7b1179..c0fd3fec1ab 100644 --- a/apps/portal/src/components/Document/AuthMethodsTabs.tsx +++ b/apps/portal/src/components/Document/AuthMethodsTabs.tsx @@ -122,7 +122,7 @@ const getHTTPSnippet = (authMethod: AuthMethod): string[] => { case "email": return [ `# Send a login code to the user -POST https://api.thirdweb.com/v1/wallets/user/code +POST https://api.thirdweb.com/v1/auth/initiate Content-Type: application/json x-client-id: @@ -131,7 +131,7 @@ x-client-id: "email": "user@example.com" }`, `# Verify the code and authenticate the user -POST https://api.thirdweb.com/v1/wallets/user/code/verify +POST https://api.thirdweb.com/v1/auth/complete Content-Type: application/json x-client-id: @@ -148,7 +148,7 @@ x-client-id: case "phone": return [ `# Send a login code to the user -POST https://api.thirdweb.com/v1/wallets/user/code +POST https://api.thirdweb.com/v1/auth/initiate Content-Type: application/json x-client-id: @@ -157,7 +157,7 @@ x-client-id: "phone": "+1234567890" }`, `# Verify the code and authenticate the user -POST https://api.thirdweb.com/v1/wallets/user/code/verify +POST https://api.thirdweb.com/v1/auth/complete Content-Type: application/json x-client-id: diff --git a/apps/portal/src/components/Document/Details.tsx b/apps/portal/src/components/Document/Details.tsx index 6b063115a58..9fb6e1b55c3 100644 --- a/apps/portal/src/components/Document/Details.tsx +++ b/apps/portal/src/components/Document/Details.tsx @@ -22,21 +22,21 @@ export function Details(props: { -

{props.summary} -

+ {props.tags && props.tags.length > 0 && (
{props.tags?.map((flag) => { @@ -58,7 +58,7 @@ export function Details(props: { props.accordionTriggerClassName, )} > -
{props.children}
+
{props.children}
); } diff --git a/apps/portal/src/components/Document/index.ts b/apps/portal/src/components/Document/index.ts index fe318889920..63e2dd9b22a 100644 --- a/apps/portal/src/components/Document/index.ts +++ b/apps/portal/src/components/Document/index.ts @@ -3,6 +3,7 @@ export { Badge } from "../ui/badge"; // export { Table } from "./Table"; export { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"; +export { OpenApiEndpoint } from "./APIEndpointMeta/OpenApiEndpoint"; export { AuthList } from "./AuthList"; export { Breadcrumb } from "./Breadcrumb"; export { Callout } from "./Callout"; @@ -18,7 +19,6 @@ export { GithubButtonLink } from "./GithubButtonLink"; // export { EditPage } from "./EditPage"; export { Grid } from "./Grid"; export { Heading } from "./Heading"; -// export { ApiEndpoint } from "./APIEndpointMeta/ApiEndpoint"; export { InlineCode } from "./InlineCode"; export { InstallTabs } from "./InstallTabs"; export { UnorderedList } from "./List"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62b37fe8a9f..a47e78e8cf8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -637,7 +637,7 @@ importers: specifier: ^1.6.12 version: 1.6.12(next@15.3.5(@opentelemetry/api@1.9.0)(@playwright/test@1.53.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) openapi-types: - specifier: ^12.1.3 + specifier: 12.1.3 version: 12.1.3 posthog-js: specifier: 1.256.1 @@ -793,6 +793,9 @@ importers: node-html-parser: specifier: ^6.1.13 version: 6.1.13 + openapi-types: + specifier: 12.1.3 + version: 12.1.3 posthog-js: specifier: 1.256.1 version: 1.256.1 @@ -7282,7 +7285,6 @@ packages: '@walletconnect/modal@2.7.0': resolution: {integrity: sha512-RQVt58oJ+rwqnPcIvRFeMGKuXb9qkgSmwz4noF8JZGUym3gUAzVs+uW2NQ1Owm9XOJAV+sANrtJ+VoVq1ftElw==} - deprecated: Please follow the migration guide on https://docs.reown.com/appkit/upgrade/wcm '@walletconnect/react-native-compat@2.17.3': resolution: {integrity: sha512-lHKwXKoB0rdDH1ukxUx7o86xosWbttWIHYMZ8tgAQC1k9VH3CZZCoBcHOAAX8iBzyb0n0UP3/9zRrOcJE5nz7Q==} @@ -16745,7 +16747,7 @@ snapshots: dependencies: '@babel/template': 7.27.2 '@babel/traverse': 7.28.0 - '@babel/types': 7.28.0 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color @@ -17491,7 +17493,7 @@ snapshots: '@babel/helper-globals': 7.28.0 '@babel/parser': 7.28.0 '@babel/template': 7.27.2 - '@babel/types': 7.28.0 + '@babel/types': 7.28.2 debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -26797,7 +26799,7 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.0 + '@babel/types': 7.28.2 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.7 @@ -28469,8 +28471,8 @@ snapshots: '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.8.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.0) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) eslint-plugin-react: 7.37.5(eslint@8.57.0) eslint-plugin-react-hooks: 5.2.0(eslint@8.57.0) @@ -28489,7 +28491,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1(supports-color@8.1.1) @@ -28500,7 +28502,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.10.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -28525,18 +28527,18 @@ snapshots: - bluebird - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.12.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.8.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -28547,7 +28549,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -31316,7 +31318,7 @@ snapshots: '@babel/core': 7.28.0 '@babel/generator': 7.28.0 '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 + '@babel/types': 7.28.2 flow-enums-runtime: 0.0.6 metro: 0.81.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) metro-babel-transformer: 0.81.4