diff --git a/apps/portal/src/app/vault/sdk/api-reference/page.mdx b/apps/portal/src/app/vault/sdk/api-reference/page.mdx new file mode 100644 index 00000000000..64bd6f3336c --- /dev/null +++ b/apps/portal/src/app/vault/sdk/api-reference/page.mdx @@ -0,0 +1,130 @@ +# API Reference + +This section lists every exported function from **@thirdweb-dev/vault-sdk** grouped by broad capability. All functions are fully typed – hover in your editor for exact type information. + +--- + +## 1. Client utilities + +| Function | Description | +| --- | --- | +| `createVaultClient({ secretKey })` | Uses your project secret key to establish a connection to your Vault instance and returns a `VaultClient`. Create **one** client per Vault instance and reuse it. | +| `ping({ client, request })` | Health-check endpoint mainly used in examples and tests. Returns the current server time. | + +```ts +const client = await createVaultClient({ secretKey: "PROJECT_SECRET_KEY" }); +await ping({ client, request: { message: "pong?" } }); +``` + +--- + +## 2. Service Accounts + +| Function | When to use | +| --- | --- | +| `createServiceAccount({ request })` | Bootstrap a brand-new Vault. Returns the **admin key** and **rotation code**. _Run once during provisioning_. | +| `getServiceAccount({ request })` | Retrieve metadata for the current service account. Requires **admin key** or a token with `serviceAccount:read` policy. | +| `rotateServiceAccount({ request })` | Rotate (invalidate) the admin key **and** all existing access tokens in a single atomic operation. Authenticate with the **rotation code**. | + +Example – rotate an account after a key leak: + +```ts +await rotateServiceAccount({ + client, + request: { + auth: { rotationCode: process.env.VAULT_ROTATION_CODE! }, + }, +}); +``` + +--- + +## 3. EOAs (Wallets) + +| Function | Purpose | +| --- | --- | +| `createEoa` | Create a new EOA (wallet) inside the Vault. Optionally attach arbitrary `metadata` for later querying. | +| `listEoas` | Pagination-aware listing with optional metadata filters. | +| `signTransaction` | Ask the Vault to sign an EVM transaction (legacy, 2930, 1559, 4844 or 7702). | +| `signMessage` | Sign a plain string / hex message. | +| `signTypedData` | Sign EIP-712 typed data with full generic type safety. | +| `signAuthorization` | Sign an [`Authorization`](#authorization) struct used by some L2s / account-abstraction schemes. | +| `signStructuredMessage` | Sign EIP-4337 user-operations (v0.6 & v0.7). | + +```ts +// sign a 1559 tx +import { parseTransaction, signTransaction } from "@thirdweb-dev/vault-sdk"; + +const tx = parseTransaction({ + to: "0x...", + value: 0n, + chainId: 1, + maxFeePerGas: 30n * 10n ** 9n, + maxPriorityFeePerGas: 1n * 10n ** 9n, + gasLimit: 21_000, +}); + +await signTransaction({ + client, + request: { + auth: { accessToken: process.env.VAULT_SIG_TOKEN! }, + options: { from: "0xEoaAddress", transaction: tx }, + }, +}); +``` + +> **Note**: `parseTransaction` is a convenience helper that normalises user-supplied objects – you can also build the canonical tx object yourself. + +--- + +## 4. Access Tokens + +| Function | Purpose | +| --- | --- | +| `createAccessToken` | Mint a **base token** scoped by policies & metadata. Requires **admin key**. | +| `createSignedAccessToken` | Pure-client helper that turns a *base* token into a short-lived, signed JWT (prefixed with `vt_sat_`). No server round-trip required. | +| `listAccessTokens` | List existing tokens with pagination and optional metadata filters. | +| `revokeAccessToken` | Immediately invalidate a token (or all derived signed tokens) by `id`. | + +```ts +// Derive a time-boxed signed token for a serverless function +const sat = await createSignedAccessToken({ + vaultClient: client, + baseAccessToken: process.env.VAULT_BASE_TOKEN!, + additionalPolicies: [ + { type: "eoa:signMessage", chainId: 1, messagePattern: "^0x.*" }, + ], + expiryTimestamp: Math.floor(Date.now() / 1000) + 60 * 5, // 5 min +}); +``` + +--- + +## 5. Utilities + +| Function | Notes | +| --- | --- | +| `parseTransaction` | Normalises user input into a canonical `EthereumTypedTransaction` (supports Legacy, 2930, 1559, 4844, 7702). Throws `ParseTransactionError` on invalid input. | +| `ParseTransactionError` | Custom error class thrown by the above helper. | + +```ts +try { + parseTransaction({ gas: 100_000 }); +} catch (err) { + if (err instanceof ParseTransactionError) { + console.error(err.message); + } +} +``` + +--- + +### Types + +All request & response shapes are exported as TypeScript types so you can easily model higher-level abstractions: + +```ts +import type { CreateEoaPayload, SignMessagePayload, PolicyComponent } from "@thirdweb-dev/vault-sdk"; +``` + +Refer to the generated `.d.ts` files for the complete list. \ No newline at end of file diff --git a/apps/portal/src/app/vault/sdk/guides/access-tokens/page.mdx b/apps/portal/src/app/vault/sdk/guides/access-tokens/page.mdx new file mode 100644 index 00000000000..414c63a18be --- /dev/null +++ b/apps/portal/src/app/vault/sdk/guides/access-tokens/page.mdx @@ -0,0 +1,105 @@ +# Guide – Managing Access Tokens + +Access tokens let you delegate finely-scoped permissions to services, cron jobs or external partners without sharing the admin key. + +This guide covers: + +1. Creating a **base** access token (server-side) +2. Deriving short-lived **signed** tokens client-side +3. Listing and revoking tokens + +--- + +## 1. Create a base token + +Base tokens are created **once** and stored securely (e.g. in your secrets manager). They cannot be scoped by expiry – instead you define *policies* that control which operations are allowed. + +```ts +import { createVaultClient, createAccessToken } from "@thirdweb-dev/vault-sdk"; + +const client = await createVaultClient({ secretKey: "PROJECT_SECRET_KEY" }); + +const res = await createAccessToken({ + client, + request: { + auth: { adminKey: process.env.VAULT_ADMIN_KEY! }, + options: { + policies: [ + { + type: "eoa:signMessage", + allowlist: ["0xEoaAddress"], + messagePattern: "^0x.*", // only allow hex messages + }, + ], + expiresAt: new Date("2030-01-01").toISOString(), + metadata: { env: "prod", owner: "backend" }, + }, + }, +}); + +console.log("base token", res.data.accessToken); +``` + +--- + +## 2. Create a signed access token (JWT) + +A **signed access token (SAT)** is a JWT created entirely on the client side. You derive it from a base token, embed additional policies and set a short expiry. + +```ts +import { createSignedAccessToken } from "@thirdweb-dev/vault-sdk"; + + +const sat = await createSignedAccessToken({ + vaultClient: client, + baseAccessToken: process.env.BASE_ACCESS_TOKEN, + + additionalPolicies: [ + { type: "eoa:signMessage", chainId: 1 }, + ], + expiryTimestamp: Math.floor(Date.now() / 1000) + 300, // 5 minutes +}); + +// Prefix: vt_sat_ +console.log(sat); +``` + +Send SATs to un-trusted environments (browser, serverless) – they only work until the expiry timestamp and can be revoked centrally by revoking their base token. + +--- + +## 3. List & revoke + +```ts +import { listAccessTokens, revokeAccessToken } from "@thirdweb-dev/vault-sdk"; + +// List the first 10 tokens created by "backend" +const list = await listAccessTokens({ + client, + request: { + auth: { adminKey: process.env.VAULT_ADMIN_KEY! }, + options: { page: 1, pageSize: 10 }, + }, +}); + +const tokenId = list.data.items[0].id; + +// Revoke it +await revokeAccessToken({ + client, + request: { + auth: { adminKey: process.env.VAULT_ADMIN_KEY! }, + options: { id: tokenId }, + }, +}); +``` + +Once revoked, **all signed tokens derived** from the base are automatically invalidated. + +--- + +### Best practices + +1. **Never ship base tokens to browsers** – always derive SATs. +2. **Keep expiries short** (minutes) for web-apps and serverless functions. +3. **Scope by metadata** to share the same policies across many EOAs. \ No newline at end of file diff --git a/apps/portal/src/app/vault/sdk/guides/creating-eoas/page.mdx b/apps/portal/src/app/vault/sdk/guides/creating-eoas/page.mdx new file mode 100644 index 00000000000..1a51f153ee3 --- /dev/null +++ b/apps/portal/src/app/vault/sdk/guides/creating-eoas/page.mdx @@ -0,0 +1,75 @@ +# Guide – Creating & Managing EOAs + +This guide walks you through creating your first Externally Owned Account (EOA) inside the Vault and querying it later. + +## Prerequisites + +- A running Vault instance. +- The **admin key** or a token with the `eoa:create` / `eoa:read` policies. +- The Vault SDK installed (see Installation). + +--- + +## 1. Create a new EOA + +```ts +import { createVaultClient, createEoa } from "@thirdweb-dev/vault-sdk"; + +const client = await createVaultClient({ + secretKey: "PROJECT_SECRET_KEY", +}); + +const res = await createEoa({ + client, + request: { + auth: { adminKey: process.env.VAULT_ADMIN_KEY! }, + options: { + metadata: { + team: "treasury", + purpose: "eth-cold-storage", + }, + }, + }, +}); + +if (!res.success) throw new Error(res.error.message); + +console.log("EOA address", res.data.address); +``` + +### Metadata + +`metadata` accepts arbitrary key-value pairs that can later be used for filtering (`listEoas`) or for access-token policies. + +--- + +## 2. Listing EOAs + +```ts +import { listEoas } from "@thirdweb-dev/vault-sdk"; + +const list = await listEoas({ + client, + request: { + auth: { adminKey: process.env.VAULT_ADMIN_KEY! }, + options: { + page: 1, + pageSize: 20, + }, + }, +}); + +for (const eoa of list.data.items) { + console.log(eoa.address, "metadata:", eoa.metadata); +} +``` + +You can build richer UIs by using the pagination data (`page`, `pageSize`, `totalRecords`). + +--- + +## 3. Best practices + +1. **Group wallets via metadata** – e.g. `{ env: "prod" }`, `{ team: "ops" }`. +2. **Use access tokens for app-specific actions** – never ship the admin key to client-side code. +3. **Rotate when you onboard/offboard admins** – see the rotation guide. \ No newline at end of file diff --git a/apps/portal/src/app/vault/sdk/guides/signing/page.mdx b/apps/portal/src/app/vault/sdk/guides/signing/page.mdx new file mode 100644 index 00000000000..1f59fbb0c80 --- /dev/null +++ b/apps/portal/src/app/vault/sdk/guides/signing/page.mdx @@ -0,0 +1,126 @@ +# Guide – Signing Transactions & Messages + +In this guide we will request signatures from the Vault for common payload types: + +1. Plain text / hex messages +2. EIP-712 typed data +3. EVM transactions (legacy & EIP-1559) + +## Prerequisites + +- An EOA created in the Vault +- An **access token** with the corresponding `eoa:sign*` policies (or the admin key for testing) + +--- + +## 1. Sign a plain message + +```ts +import { createVaultClient, signMessage } from "@thirdweb-dev/vault-sdk"; + +const client = await createVaultClient({ secretKey: "PROJECT_SECRET_KEY" }); + +const res = await signMessage({ + client, + request: { + auth: { accessToken: process.env.VAULT_SIG_TOKEN! }, + options: { + from: "0xEoaAddress", // address of the signer wallet + message: "gm", + chainId: 1, + }, + }, +}); + +console.log(res.data.signature); +``` + +--- + +## 2. Sign EIP-712 typed data + +The helper is fully generic – you get compile-time checks that the `message` matches your `types`. + +```ts +import { signTypedData } from "@thirdweb-dev/vault-sdk"; +import type { TypedData } from "abitype"; + +interface Types extends TypedData { + Person: [ + { name: "name"; type: "string" }, + { name: "wallet"; type: "address" }, + ]; +} + +await signTypedData({ + client, + request: { + auth: { accessToken: process.env.VAULT_SIG_TOKEN! }, + options: { + from: "0xEoaAddress", + typedData: { + domain: { name: "Example", version: "1", chainId: 1 }, + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + ], + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" }, + ], + }, + primaryType: "Person", + message: { + name: "Alice", + wallet: "0xEoaAddress", + }, + }, + }, + }, +}); +``` + +--- + +## 3. Sign an EIP-1559 transaction + +```ts +import { parseTransaction, signTransaction } from "@thirdweb-dev/vault-sdk"; + +const tx1559 = parseTransaction({ + type: "0x02", + chainId: 1, + to: "0xRecipient", + value: 0n, + gasLimit: 21_000, + maxFeePerGas: 30n * 10n ** 9n, + maxPriorityFeePerGas: 1n * 10n ** 9n, +}); + +await signTransaction({ + client, + request: { + auth: { accessToken: process.env.VAULT_SIG_TOKEN! }, + options: { from: "0xEoaAddress", transaction: tx1559 }, + }, +}); +``` + +--- + +### Error handling + +All SDK functions return an object with `{ success, data, error }`. Prefer pattern matching over exceptions: + +```ts +const res = await signMessage(/* … */); + +if (!res.success) { + console.error(res.error); + return; +} + +console.log("signature", res.data.signature); +``` \ No newline at end of file diff --git a/apps/portal/src/app/vault/sdk/installation/page.mdx b/apps/portal/src/app/vault/sdk/installation/page.mdx new file mode 100644 index 00000000000..35a2f04b36a --- /dev/null +++ b/apps/portal/src/app/vault/sdk/installation/page.mdx @@ -0,0 +1,54 @@ +# Installation & Setup + +The Vault SDK targets modern **ESM** runtimes (Node >= 18 and all evergreen browsers). + +## Add the package + +```bash +# with pnpm (recommended) +pnpm add @thirdweb-dev/vault-sdk + +# or npm +npm install @thirdweb-dev/vault-sdk + +# or Yarn +yarn add @thirdweb-dev/vault-sdk +``` + +## Peer dependencies + +The SDK has no required peer-dependencies, but it expects the **`fetch`** Web API to be globally available (Node 18 provides it by default). + +If you run on an older Node version, install a fetch polyfill: + +```bash +npm install undici --save +``` + +```ts +// polyfill global fetch (Node < 18) +import { fetch, Headers, Request, Response } from "undici"; +// @ts-ignore +globalThis.fetch = fetch; +``` + +## Initialise a client instance + +All requests go through a `VaultClient` that holds your Vault's base URL and the enclave public key: + +```ts +import { createVaultClient } from "@thirdweb-dev/vault-sdk"; + +const vault = await createVaultClient({ + // Get this from your thirdweb dashboard + secretKey: "PROJECT_SECRET_KEY", +}); +``` + +`createVaultClient` uses your secret key to establish a connection to the thirdweb vault instance. You can also specify a baseUrl that points to your own vault instance. Cache the resulting `vault` object and reuse it for all subsequent operations. + +> **Tip**: If you run multiple Vault instances (dev, staging, prod) create one `VaultClient` per instance. + +--- + +Next: explore the **API Reference** or jump straight into the guides. \ No newline at end of file diff --git a/apps/portal/src/app/vault/sdk/page.mdx b/apps/portal/src/app/vault/sdk/page.mdx new file mode 100644 index 00000000000..0df2409e793 --- /dev/null +++ b/apps/portal/src/app/vault/sdk/page.mdx @@ -0,0 +1,70 @@ +# Vault SDK + +The **@thirdweb-dev/vault-sdk** gives you a type-safe JavaScript/TypeScript client for interacting with a Vault instance from any Node.js or browser environment. + +It is built on top of the Vault HTTP API and handles: + +- End-to-end encryption to the TEEs (Nitro Enclaves) that protect your keys. +- Type-safe request/response objects (generated from the Rust server types). +- Convenience helpers for signing messages, transactions and typed data. +- Utility helpers for JSON-Web-Token (JWT) based _signed access tokens_. + +Use the SDK whenever you need programmatic, non-interactive access to the Vault—CLI tooling, server-side scripts, CI/CD pipelines or browser-based dashboards. + +--- + +## What you can do + +1. **Create and manage service accounts** – bootstrap a new Vault, rotate admin keys. +2. **Create EOAs (wallets)** – generate new non-custodial accounts inside the Vault. +3. **Sign payloads** – transactions, messages, typed-data, EIP-4337 user-ops, … +4. **Mint access tokens** – granular, policy-based bearer tokens for internal or 3rd-party services. +5. **Parse raw transactions** – normalise heterogeneous user input into canonical EVM tx objects. + +See the Installation guide for how to add the package and the API Reference for every function available. + +--- + +## Quick example + +```ts +import { + createVaultClient, + createEoa, + signMessage, +} from "@thirdweb-dev/vault-sdk"; + +// 1. Connect to your Vault instance +const vault = await createVaultClient({ + secretKey: "PROJECT_SECRET_KEY" /* Your thirdweb project secret key */, +}); + +// 2. Create a new wallet inside the Vault +const { success, data: eoa } = await createEoa({ + client: vault, + request: { + auth: { adminKey: process.env.VAULT_ADMIN_KEY! }, + options: { + metadata: { purpose: "treasury" }, + }, + }, +}); + +if (!success) throw new Error("Failed to create EOA"); + +// 3. Sign a message with that wallet +await signMessage({ + client: vault, + request: { + auth: { adminKey: process.env.VAULT_ADMIN_KEY! }, + options: { + from: eoa!.address, + message: "Hello from Vault ✨", + }, + }, +}); +``` + +--- + +Continue with the **Installation** section to get started. diff --git a/apps/portal/src/app/vault/sidebar.tsx b/apps/portal/src/app/vault/sidebar.tsx index 2160cd87285..79b4ece2ff7 100644 --- a/apps/portal/src/app/vault/sidebar.tsx +++ b/apps/portal/src/app/vault/sidebar.tsx @@ -1,5 +1,6 @@ import type { SideBar } from "@/components/Layouts/DocLayout"; import { + Code2Icon, KeyIcon, MessageCircleQuestionIcon, RocketIcon, @@ -47,6 +48,41 @@ export const sidebar: SideBar = { }, ], }, + { + name: "TypeScript SDK", + icon: , + links: [ + { + name: "Overview", + href: "/vault/sdk", + }, + { + name: "Installation", + href: "/vault/sdk/installation", + }, + { + name: "API Reference", + href: "/vault/sdk/api-reference", + }, + { + name: "Guides", + links: [ + { + name: "Creating & Managing EOAs", + href: "/vault/sdk/guides/creating-eoas", + }, + { + name: "Signing Transactions & Messages", + href: "/vault/sdk/guides/signing", + }, + { + name: "Managing Access Tokens", + href: "/vault/sdk/guides/access-tokens", + }, + ], + }, + ], + }, { name: "Security", href: "/vault/security",