From 59e650a6d24e36bbda8e59deff54172d0d5220ad Mon Sep 17 00:00:00 2001 From: bashtwigs Date: Thu, 23 Jul 2026 00:24:56 +0800 Subject: [PATCH 1/4] fix(docs): resolve biome complexity error and unused suppression --- .../scripts/patch-fumadocs-source.mjs | 13 -- .../components/docs-shell/navigation-node.tsx | 130 ++++++++++-------- 2 files changed, 73 insertions(+), 70 deletions(-) diff --git a/apps/sdp-docs/scripts/patch-fumadocs-source.mjs b/apps/sdp-docs/scripts/patch-fumadocs-source.mjs index a24e1a1bd..1640a2c64 100644 --- a/apps/sdp-docs/scripts/patch-fumadocs-source.mjs +++ b/apps/sdp-docs/scripts/patch-fumadocs-source.mjs @@ -8,16 +8,6 @@ const sourceFileCandidates = [ path.resolve(__dirname, "../.source/server.ts"), path.resolve(__dirname, "../.source/index.ts"), ]; -const sourceConfigShimPath = path.resolve(__dirname, "../.source/source.config.mjs"); -const sourceConfigShim = `import { defineDocs } from "fumadocs-mdx/config"; - -export const docs = defineDocs({ - dir: "content/docs", -}); - -export default docs; -`; - const run = async () => { let content; let sourceFilePath; @@ -49,7 +39,6 @@ const run = async () => { { pattern: /export const defaultCollection =/g, localName: "_defaultCollection", - // biome-ignore lint/security/noSecrets: Generated Fumadocs export identifier, not a secret. exportName: "defaultCollection", }, ]; @@ -78,8 +67,6 @@ const run = async () => { } else { console.log("No Fumadocs source patch required"); } - - await fs.writeFile(sourceConfigShimPath, sourceConfigShim, "utf8"); }; run().catch((error) => { diff --git a/apps/sdp-docs/src/components/docs-shell/navigation-node.tsx b/apps/sdp-docs/src/components/docs-shell/navigation-node.tsx index fc4414bac..ecf66413c 100644 --- a/apps/sdp-docs/src/components/docs-shell/navigation-node.tsx +++ b/apps/sdp-docs/src/components/docs-shell/navigation-node.tsx @@ -29,12 +29,6 @@ type NavigationNodeProps = { export function NavigationNode({ node, depth = 0 }: NavigationNodeProps) { const pathname = usePathname(); - const containsPath = useMemo(() => nodeContainsPath(node, pathname), [node, pathname]); - const [isOpen, setIsOpen] = useState(containsPath); - - useEffect(() => { - if (containsPath) setIsOpen(true); - }, [containsPath]); if (node.type === "separator") { return node.name ? ( @@ -54,66 +48,88 @@ export function NavigationNode({ node, depth = 0 }: NavigationNodeProps) { className={cn("launch-docs-nav-item", isActive && "is-active")} style={{ paddingLeft: `${12 + depth * 14}px` }} > + {node.icon ? {node.icon} : null} {node.name} ); } if (node.type === "folder") { - const folderName = typeof node.name === "string" ? node.name : String(node.name ?? ""); - const hasChildren = node.children.length > 0; - const isActive = node.index?.url === pathname; - const ChevronIcon = isOpen ? ChevronDown : ChevronRight; + return ; + } - return ( -
-
- {node.index ? ( - <> - - {node.name} - - {hasChildren && ( - - )} - - ) : ( - - )} -
- - {hasChildren && isOpen ? ( -
- {node.children.map((child) => ( - - ))} -
- ) : null} + {node.icon ? {node.icon} : null} + {node.name} + + {hasChildren && ( + + )} + + ) : ( + + )}
- ); - } - return null; + {hasChildren && isOpen ? ( +
+ {node.children.map((child) => ( + + ))} +
+ ) : null} + + ); } From a451928507a27b4041cdc0b7a2ead16873bbd113 Mon Sep 17 00:00:00 2001 From: bashtwigs Date: Thu, 23 Jul 2026 00:27:10 +0800 Subject: [PATCH 2/4] feat: revamp docs --- apps/sdp-docs/CLAUDE.md | 8 +- .../developing-with-sdp/authentication.mdx | 15 + .../docs/developing-with-sdp/idempotency.mdx | 16 + .../docs/developing-with-sdp/index.mdx | 24 + .../manage-api-keys.mdx | 18 +- .../docs/developing-with-sdp/meta.json | 4 + .../content/docs/guides/create-a-token.mdx | 18 +- .../content/docs/guides/deploy-a-token.mdx | 12 +- .../docs/guides/freeze-and-compliance.mdx | 42 +- .../content/docs/guides/manage-allowlists.mdx | 18 +- apps/sdp-docs/content/docs/guides/meta.json | 2 +- .../content/docs/guides/mint-and-burn.mdx | 30 +- .../docs/guides/prepare-vs-execute.mdx | 12 +- .../docs/guides/setup-organization.mdx | 15 +- .../content/docs/guides/setup-wallets.mdx | 24 +- .../content/docs/guides/tokenize-an-asset.mdx | 6 +- apps/sdp-docs/content/docs/home.mdx | 7 - apps/sdp-docs/content/docs/index.mdx | 8 + apps/sdp-docs/content/docs/introduction.mdx | 153 ++- apps/sdp-docs/content/docs/meta.json | 2 +- .../content/docs/payments/accept-indexing.mdx | 8 +- .../content/docs/payments/ramps-providers.mdx | 2 +- apps/sdp-docs/content/docs/payments/ramps.mdx | 10 +- .../docs/payments/send-basic-payment.mdx | 12 +- .../docs/payments/send-payment-with-memo.mdx | 8 +- .../content/docs/payments/send-payouts.mdx | 10 +- .../{ai-consumption.mdx => docs-for-ai.mdx} | 2 +- .../sdp-docs/content/docs/reference/meta.json | 2 +- .../self-hosting/first-devnet-deployment.mdx | 10 +- .../content/docs/self-hosting/quickstart.mdx | 8 +- .../docs/self-hosting/troubleshooting.mdx | 4 +- .../docs/self-hosting/upgrade-and-backup.mdx | 8 +- .../content/docs/tokens/allowlists.mdx | 12 +- .../content/docs/tokens/create-a-token.mdx | 12 +- .../content/docs/tokens/deploy-a-token.mdx | 12 +- .../docs/tokens/freeze-and-compliance.mdx | 14 +- .../docs/tokens/manage-token-settings.mdx | 10 +- .../content/docs/tokens/mint-and-burn.mdx | 12 +- .../docs/tokens/prepare-vs-execute.mdx | 4 +- .../content/docs/tokens/tokenize-an-asset.mdx | 6 +- .../issue-a-regulated-stablecoin.mdx | 60 +- .../tutorials/tokenize-a-treasury-fund.mdx | 46 +- .../docs/wallet-operations/balances.mdx | 8 +- .../content/docs/wallet-operations/index.mdx | 2 +- .../docs/wallet-operations/policies.mdx | 12 +- apps/sdp-docs/mdx-components.tsx | 26 +- .../public/images/partners/alchemy.svg | 19 + .../public/images/partners/anchorage.svg | 24 + apps/sdp-docs/public/images/partners/bvnk.svg | 13 + .../public/images/partners/chainalysis.svg | 11 + .../public/images/partners/coinbase.svg | 10 + apps/sdp-docs/public/images/partners/dfns.svg | 1 + .../public/images/partners/elliptic.svg | 17 + .../public/images/partners/fireblocks.svg | 26 + .../public/images/partners/helius.svg | 29 + .../public/images/partners/ibm-haven.svg | 1 + .../public/images/partners/lightspark.svg | 14 + .../public/images/partners/moneygram.svg | 27 + .../public/images/partners/moonpay.svg | 10 + .../public/images/partners/muralpay.svg | 1 + apps/sdp-docs/public/images/partners/para.svg | 7 + .../sdp-docs/public/images/partners/privy.svg | 16 + .../public/images/partners/quicknode.svg | 19 + .../sdp-docs/public/images/partners/range.svg | 12 + .../public/images/partners/stripe.svg | 9 + .../public/images/partners/triton.svg | 19 + apps/sdp-docs/public/images/partners/trm.svg | 11 + .../public/images/partners/turnkey.svg | 16 + .../sdp-docs/public/images/partners/utila.svg | 155 +++ apps/sdp-docs/public/llms-full.txt | 892 ++++++++++-------- apps/sdp-docs/public/llms.txt | 12 +- .../sdp-docs/scripts/generate-ai-resources.ts | 6 +- apps/sdp-docs/scripts/generate-api-docs.mjs | 2 +- apps/sdp-docs/source.config.ts | 131 ++- apps/sdp-docs/src/app/api/search/route.ts | 4 + .../src/app/docs/[[...slug]]/page.tsx | 16 +- apps/sdp-docs/src/app/globals.css | 606 ++++++------ apps/sdp-docs/src/app/layout.tsx | 12 +- .../src/components/docs-shell/breadcrumb.tsx | 2 +- .../components/docs-shell/home-sketches.tsx | 473 ++++++++++ .../src/components/docs-shell/home.tsx | 475 ++++++---- .../src/components/docs-shell/page.tsx | 5 +- .../src/components/docs-shell/sidebar.tsx | 29 +- .../src/components/docs-shell/toc.tsx | 3 +- apps/sdp-docs/src/lib/site.ts | 2 +- apps/sdp-docs/src/lib/source.ts | 9 + apps/sdp-docs/src/styles/ramp-rotator.css | 75 ++ apps/sdp-web/src/app/layout.tsx | 10 + docs/contributing/local-development.md | 2 +- 89 files changed, 2770 insertions(+), 1217 deletions(-) create mode 100644 apps/sdp-docs/content/docs/developing-with-sdp/authentication.mdx create mode 100644 apps/sdp-docs/content/docs/developing-with-sdp/idempotency.mdx create mode 100644 apps/sdp-docs/content/docs/developing-with-sdp/index.mdx rename apps/sdp-docs/content/docs/{guides => developing-with-sdp}/manage-api-keys.mdx (95%) create mode 100644 apps/sdp-docs/content/docs/developing-with-sdp/meta.json delete mode 100644 apps/sdp-docs/content/docs/home.mdx create mode 100644 apps/sdp-docs/content/docs/index.mdx rename apps/sdp-docs/content/docs/reference/{ai-consumption.mdx => docs-for-ai.mdx} (98%) create mode 100644 apps/sdp-docs/public/images/partners/alchemy.svg create mode 100644 apps/sdp-docs/public/images/partners/anchorage.svg create mode 100644 apps/sdp-docs/public/images/partners/bvnk.svg create mode 100644 apps/sdp-docs/public/images/partners/chainalysis.svg create mode 100644 apps/sdp-docs/public/images/partners/coinbase.svg create mode 100644 apps/sdp-docs/public/images/partners/dfns.svg create mode 100644 apps/sdp-docs/public/images/partners/elliptic.svg create mode 100644 apps/sdp-docs/public/images/partners/fireblocks.svg create mode 100644 apps/sdp-docs/public/images/partners/helius.svg create mode 100644 apps/sdp-docs/public/images/partners/ibm-haven.svg create mode 100644 apps/sdp-docs/public/images/partners/lightspark.svg create mode 100644 apps/sdp-docs/public/images/partners/moneygram.svg create mode 100644 apps/sdp-docs/public/images/partners/moonpay.svg create mode 100644 apps/sdp-docs/public/images/partners/muralpay.svg create mode 100644 apps/sdp-docs/public/images/partners/para.svg create mode 100644 apps/sdp-docs/public/images/partners/privy.svg create mode 100644 apps/sdp-docs/public/images/partners/quicknode.svg create mode 100644 apps/sdp-docs/public/images/partners/range.svg create mode 100644 apps/sdp-docs/public/images/partners/stripe.svg create mode 100644 apps/sdp-docs/public/images/partners/triton.svg create mode 100644 apps/sdp-docs/public/images/partners/trm.svg create mode 100644 apps/sdp-docs/public/images/partners/turnkey.svg create mode 100644 apps/sdp-docs/public/images/partners/utila.svg create mode 100644 apps/sdp-docs/src/app/api/search/route.ts create mode 100644 apps/sdp-docs/src/components/docs-shell/home-sketches.tsx create mode 100644 apps/sdp-docs/src/styles/ramp-rotator.css diff --git a/apps/sdp-docs/CLAUDE.md b/apps/sdp-docs/CLAUDE.md index 854023397..80d4fccd5 100644 --- a/apps/sdp-docs/CLAUDE.md +++ b/apps/sdp-docs/CLAUDE.md @@ -99,7 +99,7 @@ import { DocsPage, DocsTitle, DocsDescription, DocsBody } from "@/components/doc | Class | Element | |---|---| -| `.launch-docs-title` | H1, ABC Diatype, clamp(36px, 6vw, 56px), weight 700 | +| `.launch-docs-title` | H1, ABC Diatype, clamp(32px, 4.5vw, 40px), weight 700 | | `.launch-docs-description` | Subtitle `

`, ABC Diatype 18px, max-width 720px, margin-bottom 36px | | `.launch-docs-body` | Content wrapper, Inter, `--launch-text` color | | `.launch-mdx-heading` | Base heading style (ABC Diatype, bold, ink color) | @@ -123,7 +123,11 @@ import { DocsPage, DocsTitle, DocsDescription, DocsBody } from "@/components/doc ### Navigation node icons (`navigation-node.tsx`) -The current sidebar does not render per-page icons. Page and folder links are text-only; only the folder expand/collapse chevron uses an icon (`ChevronDown` / `ChevronRight` from Lucide). If you want to reintroduce keyword-based icon mapping, add it to `NavigationNode` in `src/components/docs-shell/navigation-node.tsx`. +Sidebar icons are optional and opt-in per page/folder. Set `icon: ` (e.g. `icon: BookOpen`) in a page's MDX frontmatter or a folder's `meta.json`; the loader in `src/lib/source.ts` resolves the name against `lucide-react` exports (unknown names throw at build time) and `NavigationNode` renders it before the label via `.launch-docs-nav-icon`. Pages without an `icon` stay text-only. The folder expand/collapse chevron (`ChevronDown` / `ChevronRight`) is unrelated and always shown. + +### Search + +⌘K search works out of the box: `RootProvider` (root `layout.tsx`) provides the Fumadocs search dialog + hotkey, backed by the Orama route at `src/app/api/search/route.ts` (`createFromSource(source)`). The sidebar header has a "Search docs" trigger button (`.launch-docs-sidebar-search`). Only `content/docs/` MDX is indexed — JSX-rendered content like `DocsHome` is not. --- diff --git a/apps/sdp-docs/content/docs/developing-with-sdp/authentication.mdx b/apps/sdp-docs/content/docs/developing-with-sdp/authentication.mdx new file mode 100644 index 000000000..eb378db03 --- /dev/null +++ b/apps/sdp-docs/content/docs/developing-with-sdp/authentication.mdx @@ -0,0 +1,15 @@ +--- +title: Authentication +description: Authenticate API requests with project-scoped API keys. +--- + +Every API request carries an API key in the `Authorization` header: + +```bash title="Terminal" +curl https://api.solana.com/v1/wallets \ + -H "Authorization: Bearer sk_test_..." +``` + +Key prefixes tell you which environment a key belongs to: `sk_test_` keys are sandbox, `sk_live_` keys are production. + +Keys are project-scoped — a key can only reach the wallets, tokens, and payments of the project it was issued for. See [Manage API Keys](/docs/developing-with-sdp/manage-api-keys) for creating, rotating, and revoking keys. diff --git a/apps/sdp-docs/content/docs/developing-with-sdp/idempotency.mdx b/apps/sdp-docs/content/docs/developing-with-sdp/idempotency.mdx new file mode 100644 index 000000000..d368fc288 --- /dev/null +++ b/apps/sdp-docs/content/docs/developing-with-sdp/idempotency.mdx @@ -0,0 +1,16 @@ +--- +title: Idempotency +description: Prevent duplicate operations by sending an Idempotency-Key header on mutation requests. +--- + +Include an `Idempotency-Key` header on mutation requests to prevent duplicates. If a request times out or your client retries, replaying the same key returns the original result instead of performing the operation twice. + +```bash title="Terminal" +curl -X POST https://api.solana.com/v1/payments/transfers \ + -H "Authorization: Bearer sk_test_..." \ + -H "Idempotency-Key: 8a2f6c1e-9d4b-4f3a-b7e5-2c8d9f0a1b3c" \ + -H "Content-Type: application/json" \ + -d '{ ... }' +``` + +Use a unique key per logical operation (a UUID works well) and reuse the same key for retries of that operation. diff --git a/apps/sdp-docs/content/docs/developing-with-sdp/index.mdx b/apps/sdp-docs/content/docs/developing-with-sdp/index.mdx new file mode 100644 index 000000000..7693580e2 --- /dev/null +++ b/apps/sdp-docs/content/docs/developing-with-sdp/index.mdx @@ -0,0 +1,24 @@ +--- +title: Overview +description: The interfaces, environments, and conventions you build against. +--- + +SDP exposes two interfaces that connect to the same backend: + +- **Dashboard** — browser-based UI for setup, token management, and compliance operations. Sign in with email, Google, or GitHub. +- **REST API** — programmatic access for backend integrations. Authenticate with an API key in the `Authorization` header. + +Everything the dashboard does is available through the API. Build against the API for automation and integrations; use the dashboard to set up your organization, inspect state, and perform one-off operations. + +## Environments + +| Environment | Network | API key prefix | +| ------------ | ------------ | -------------- | +| `sandbox` | devnet | `sk_test_` | +| `production` | mainnet-beta | `sk_live_` | + +Both environments expose identical APIs. Develop and test against sandbox, then switch to production by swapping your API key. + +## Signing modes + +Most transaction endpoints support Execute (SDP signs and submits) or Prepare (SDP builds the transaction, you sign). See [Prepare vs Execute](/docs/guides/prepare-vs-execute). diff --git a/apps/sdp-docs/content/docs/guides/manage-api-keys.mdx b/apps/sdp-docs/content/docs/developing-with-sdp/manage-api-keys.mdx similarity index 95% rename from apps/sdp-docs/content/docs/guides/manage-api-keys.mdx rename to apps/sdp-docs/content/docs/developing-with-sdp/manage-api-keys.mdx index 203891e74..a2625f51f 100644 --- a/apps/sdp-docs/content/docs/guides/manage-api-keys.mdx +++ b/apps/sdp-docs/content/docs/developing-with-sdp/manage-api-keys.mdx @@ -56,7 +56,7 @@ After dismissing, the key appears in the table with its prefix, role, environmen -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/api-keys \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -70,7 +70,7 @@ curl -X POST https://api.solana.com/v1/api-keys \ ``` -```typescript +```typescript title="create-api-key.ts" const response = await fetch("https://api.solana.com/v1/api-keys", { method: "POST", headers: { @@ -90,7 +90,7 @@ const { data } = await response.json(); ``` -```java +```java title="CreateApiKey.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/api-keys")) .header("Authorization", "Bearer sk_test_...") @@ -131,7 +131,7 @@ During the grace period both the old and new key are valid. The new key value ap -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/api-keys/key_abc123/rotate \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -139,7 +139,7 @@ curl -X POST https://api.solana.com/v1/api-keys/key_abc123/rotate \ ``` -```typescript +```typescript title="rotate-api-key.ts" const response = await fetch( "https://api.solana.com/v1/api-keys/key_abc123/rotate", { @@ -156,7 +156,7 @@ const { data } = await response.json(); ``` -```java +```java title="RotateApiKey.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/api-keys/key_abc123/rotate")) .header("Authorization", "Bearer sk_test_...") @@ -185,7 +185,7 @@ Open the **Actions** dropdown next to the key and click **Delete key**. The key -```bash +```bash title="Terminal" curl -X DELETE https://api.solana.com/v1/api-keys/key_abc123 \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -193,7 +193,7 @@ curl -X DELETE https://api.solana.com/v1/api-keys/key_abc123 \ ``` -```typescript +```typescript title="revoke-api-key.ts" await fetch("https://api.solana.com/v1/api-keys/key_abc123", { method: "DELETE", headers: { @@ -205,7 +205,7 @@ await fetch("https://api.solana.com/v1/api-keys/key_abc123", { ``` -```java +```java title="RevokeApiKey.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/api-keys/key_abc123")) .header("Authorization", "Bearer sk_test_...") diff --git a/apps/sdp-docs/content/docs/developing-with-sdp/meta.json b/apps/sdp-docs/content/docs/developing-with-sdp/meta.json new file mode 100644 index 000000000..080a30429 --- /dev/null +++ b/apps/sdp-docs/content/docs/developing-with-sdp/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Developing with SDP", + "pages": ["index", "idempotency", "authentication", "manage-api-keys"] +} diff --git a/apps/sdp-docs/content/docs/guides/create-a-token.mdx b/apps/sdp-docs/content/docs/guides/create-a-token.mdx index 7d4d7d161..25d5c992e 100644 --- a/apps/sdp-docs/content/docs/guides/create-a-token.mdx +++ b/apps/sdp-docs/content/docs/guides/create-a-token.mdx @@ -63,13 +63,13 @@ A success toast confirms creation. The token is now in `pending` status, ready t -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/templates \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="list-templates.ts" const response = await fetch("https://api.solana.com/v1/issuance/templates", { headers: { "Authorization": "Bearer sk_test_..." }, }); @@ -77,7 +77,7 @@ const { data } = await response.json(); ``` -```java +```java title="ListTemplates.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/templates")) .header("Authorization", "Bearer sk_test_...") @@ -91,7 +91,7 @@ HttpRequest request = HttpRequest.newBuilder() -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -108,7 +108,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens \ ``` -```typescript +```typescript title="create-token.ts" const response = await fetch("https://api.solana.com/v1/issuance/tokens", { method: "POST", headers: { @@ -131,7 +131,7 @@ const { data } = await response.json(); ``` -```java +```java title="CreateToken.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens")) .header("Authorization", "Bearer sk_test_...") @@ -160,7 +160,7 @@ Omit the `template` field or set it to `"custom"` and configure extensions via ` -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -184,7 +184,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens \ ``` -```typescript +```typescript title="create-custom-token.ts" const response = await fetch("https://api.solana.com/v1/issuance/tokens", { method: "POST", headers: { @@ -209,7 +209,7 @@ const response = await fetch("https://api.solana.com/v1/issuance/tokens", { ``` -```java +```java title="CreateCustomToken.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens")) .header("Authorization", "Bearer sk_test_...") diff --git a/apps/sdp-docs/content/docs/guides/deploy-a-token.mdx b/apps/sdp-docs/content/docs/guides/deploy-a-token.mdx index 7db337e52..ff54a341a 100644 --- a/apps/sdp-docs/content/docs/guides/deploy-a-token.mdx +++ b/apps/sdp-docs/content/docs/guides/deploy-a-token.mdx @@ -16,14 +16,14 @@ You can deploy from the dashboard or through the API. In the dashboard, open the -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/deploy \ -H "Authorization: Bearer sk_test_..." \ -H "Idempotency-Key: deploy-acme-001" ``` -```typescript +```typescript title="deploy-token.ts" const response = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/deploy", { @@ -39,7 +39,7 @@ const { data } = await response.json(); ``` -```java +```java title="DeployToken.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/deploy")) .header("Authorization", "Bearer sk_test_...") @@ -56,13 +56,13 @@ The token status changes from `pending` to `active`, and `mintAddress` is popula -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/deploy/prepare \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="prepare-deploy-token.ts" const response = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/deploy/prepare", { @@ -75,7 +75,7 @@ const { data } = await response.json(); ``` -```java +```java title="PrepareDeployToken.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/deploy/prepare")) .header("Authorization", "Bearer sk_test_...") diff --git a/apps/sdp-docs/content/docs/guides/freeze-and-compliance.mdx b/apps/sdp-docs/content/docs/guides/freeze-and-compliance.mdx index 2fb5b0783..fb1cf4df0 100644 --- a/apps/sdp-docs/content/docs/guides/freeze-and-compliance.mdx +++ b/apps/sdp-docs/content/docs/guides/freeze-and-compliance.mdx @@ -16,7 +16,7 @@ Use the Compliance API to check a Solana address across configured providers bef -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/compliance/address-screenings \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -28,7 +28,7 @@ curl -X POST https://api.solana.com/v1/compliance/address-screenings \ ``` -```typescript +```typescript title="screen-address.ts" const response = await fetch( "https://api.solana.com/v1/compliance/address-screenings", { @@ -49,7 +49,7 @@ const { data } = await response.json(); ``` -```java +```java title="ScreenAddress.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/compliance/address-screenings")) .header("Authorization", "Bearer sk_test_...") @@ -73,7 +73,7 @@ Prevent a specific holder from sending or receiving tokens: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/freeze \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -84,7 +84,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/freeze \ ``` -```typescript +```typescript title="freeze-account.ts" const response = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/freeze", { @@ -102,7 +102,7 @@ const response = await fetch( ``` -```java +```java title="FreezeAccount.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/freeze")) .header("Authorization", "Bearer sk_test_...") @@ -123,7 +123,7 @@ The `accountAddress` can be the holder wallet address or the matching token acco -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/unfreeze \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -131,7 +131,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/unfreeze \ ``` -```typescript +```typescript title="unfreeze-account.ts" await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/unfreeze", { @@ -146,7 +146,7 @@ await fetch( ``` -```java +```java title="UnfreezeAccount.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/unfreeze")) .header("Authorization", "Bearer sk_test_...") @@ -162,13 +162,13 @@ HttpRequest request = HttpRequest.newBuilder() -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/tokens/tok_abc123/frozen \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="list-frozen-accounts.ts" const response = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/frozen", { headers: { "Authorization": "Bearer sk_test_..." } } @@ -177,7 +177,7 @@ const { data } = await response.json(); ``` -```java +```java title="ListFrozenAccounts.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/frozen")) .header("Authorization", "Bearer sk_test_...") @@ -193,13 +193,13 @@ Halt token activity globally. Requires the `pausable` extension: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/pause \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="pause-token.ts" await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/pause", { @@ -210,7 +210,7 @@ await fetch( ``` -```java +```java title="PauseToken.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/pause")) .header("Authorization", "Bearer sk_test_...") @@ -226,13 +226,13 @@ While paused, transfer-related and supply-management actions should be treated a -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/unpause \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="unpause-token.ts" await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/unpause", { @@ -243,7 +243,7 @@ await fetch( ``` -```java +```java title="UnpauseToken.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/unpause")) .header("Authorization", "Bearer sk_test_...") @@ -259,7 +259,7 @@ Force-transfer tokens from one account to another without the holder's signature -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/seize \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -275,7 +275,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/seize \ ``` -```typescript +```typescript title="seize-tokens.ts" await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/seize", { @@ -298,7 +298,7 @@ await fetch( ``` -```java +```java title="SeizeTokens.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/seize")) .header("Authorization", "Bearer sk_test_...") diff --git a/apps/sdp-docs/content/docs/guides/manage-allowlists.mdx b/apps/sdp-docs/content/docs/guides/manage-allowlists.mdx index 8567b7211..d50137d65 100644 --- a/apps/sdp-docs/content/docs/guides/manage-allowlists.mdx +++ b/apps/sdp-docs/content/docs/guides/manage-allowlists.mdx @@ -18,7 +18,7 @@ For tokens with an on-chain allowlist configured, the mint endpoints also auto-a -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -29,7 +29,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist \ ``` -```typescript +```typescript title="add-allowlist-address.ts" const response = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist", { @@ -47,7 +47,7 @@ const response = await fetch( ``` -```java +```java title="AddAllowlistAddress.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist")) .header("Authorization", "Bearer sk_test_...") @@ -68,13 +68,13 @@ The `label` field is optional but recommended for identifying entries. -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="list-allowlist-entries.ts" const response = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist", { headers: { "Authorization": "Bearer sk_test_..." } } @@ -84,7 +84,7 @@ const { data, meta } = await response.json(); ``` -```java +```java title="ListAllowlistEntries.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist")) .header("Authorization", "Bearer sk_test_...") @@ -100,14 +100,14 @@ Supports `page` and `pageSize` query parameters for pagination. -```bash +```bash title="Terminal" curl -X DELETE \ https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist/alw_abc123 \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="remove-allowlist-address.ts" await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist/alw_abc123", { @@ -118,7 +118,7 @@ await fetch( ``` -```java +```java title="RemoveAllowlistAddress.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist/alw_abc123")) .header("Authorization", "Bearer sk_test_...") diff --git a/apps/sdp-docs/content/docs/guides/meta.json b/apps/sdp-docs/content/docs/guides/meta.json index cb97b3f1f..2a5960928 100644 --- a/apps/sdp-docs/content/docs/guides/meta.json +++ b/apps/sdp-docs/content/docs/guides/meta.json @@ -1,4 +1,4 @@ { "title": "Getting Started", - "pages": ["setup-organization", "setup-wallets", "manage-api-keys"] + "pages": ["setup-wallets"] } diff --git a/apps/sdp-docs/content/docs/guides/mint-and-burn.mdx b/apps/sdp-docs/content/docs/guides/mint-and-burn.mdx index 73421baa3..418f33978 100644 --- a/apps/sdp-docs/content/docs/guides/mint-and-burn.mdx +++ b/apps/sdp-docs/content/docs/guides/mint-and-burn.mdx @@ -24,7 +24,7 @@ For tokens with an on-chain allowlist configured, operator-revoked destinations -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/mint \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -39,7 +39,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/mint \ ``` -```typescript +```typescript title="mint-tokens.ts" const response = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/mint", { @@ -62,7 +62,7 @@ const { data } = await response.json(); ``` -```java +```java title="MintTokens.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/mint")) .header("Authorization", "Bearer sk_test_...") @@ -89,7 +89,7 @@ Add `/prepare` to simulate before signing: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/mint/prepare \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -103,7 +103,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/mint/prepare \ ``` -```typescript +```typescript title="prepare-mint.ts" const response = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/mint/prepare", { @@ -124,7 +124,7 @@ const { data } = await response.json(); ``` -```java +```java title="PrepareMint.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/mint/prepare")) .header("Authorization", "Bearer sk_test_...") @@ -154,7 +154,7 @@ Burn tokens from an account your wallet controls: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/burn \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -169,7 +169,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/burn \ ``` -```typescript +```typescript title="burn-tokens.ts" await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/burn", { @@ -191,7 +191,7 @@ await fetch( ``` -```java +```java title="BurnTokens.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/burn")) .header("Authorization", "Bearer sk_test_...") @@ -218,7 +218,7 @@ Burn tokens from any account without the holder's signature. Requires `tokens:ad -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/force-burn \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -233,7 +233,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/force-burn \ ``` -```typescript +```typescript title="force-burn-tokens.ts" await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/force-burn", { @@ -255,7 +255,7 @@ await fetch( ``` -```java +```java title="ForceBurnTokens.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/force-burn")) .header("Authorization", "Bearer sk_test_...") @@ -282,13 +282,13 @@ After minting or burning, refresh the cached supply: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/supply/refresh \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="refresh-supply.ts" await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/supply/refresh", { @@ -299,7 +299,7 @@ await fetch( ``` -```java +```java title="RefreshSupply.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/supply/refresh")) .header("Authorization", "Bearer sk_test_...") diff --git a/apps/sdp-docs/content/docs/guides/prepare-vs-execute.mdx b/apps/sdp-docs/content/docs/guides/prepare-vs-execute.mdx index e46020082..1a79170ba 100644 --- a/apps/sdp-docs/content/docs/guides/prepare-vs-execute.mdx +++ b/apps/sdp-docs/content/docs/guides/prepare-vs-execute.mdx @@ -16,7 +16,7 @@ When you call a mutation endpoint without the `/prepare` suffix, SDP handles eve -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/mint \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -30,7 +30,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/mint \ ``` -```typescript +```typescript title="mint-tokens.ts" const response = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/mint", { @@ -49,7 +49,7 @@ const { data } = await response.json(); ``` -```java +```java title="MintTokens.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/mint")) .header("Authorization", "Bearer sk_test_...") @@ -81,7 +81,7 @@ When you add `/prepare` to a mutation endpoint, SDP builds the transaction but r -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/mint/prepare \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -95,7 +95,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/mint/prepare \ ``` -```typescript +```typescript title="prepare-mint.ts" const response = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/mint/prepare", { @@ -116,7 +116,7 @@ const { data } = await response.json(); ``` -```java +```java title="PrepareMint.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/issuance/tokens/tok_abc123/mint/prepare")) .header("Authorization", "Bearer sk_test_...") diff --git a/apps/sdp-docs/content/docs/guides/setup-organization.mdx b/apps/sdp-docs/content/docs/guides/setup-organization.mdx index a32321dfb..bff5089db 100644 --- a/apps/sdp-docs/content/docs/guides/setup-organization.mdx +++ b/apps/sdp-docs/content/docs/guides/setup-organization.mdx @@ -1,9 +1,9 @@ --- -title: Set Up Your Organization -description: Create and configure your SDP organization through the dashboard. +title: Set Up Your Organization & Projects +description: Create your SDP organization and organize your work into projects. --- -An organization is the top-level container for all your SDP resources — projects, wallets, API keys, and tokens. Every API request is scoped to an organization. +An organization is the top-level container for all your SDP resources — projects, wallets, API keys, and tokens. Every API request is scoped to an organization. Within an organization, projects are scoped environments with their own API keys and members. ### 1. Sign up @@ -25,9 +25,12 @@ If you already have an organization, the **Organization Switcher** in the top-le Organization Switcher dropdown -### 3. Next steps +### 3. Organize with projects + +Projects segment your organization into scoped environments — separate apps, tenants, or environments that each carry their own API keys and members, so teams don't share the same wallet and token surface. See the [Projects API reference](/docs/reference/api/projects) for managing them programmatically. + +### 4. Next steps - [Set up wallets](/docs/guides/setup-wallets) for signing transactions -- [Create API keys](/docs/guides/manage-api-keys) for programmatic access -- [Projects API reference](/docs/reference/api/projects) when you want to segment apps, tenants, or environments within the organization +- [Create API keys](/docs/developing-with-sdp/manage-api-keys) for programmatic access - [Create your first token](/docs/guides/create-a-token) diff --git a/apps/sdp-docs/content/docs/guides/setup-wallets.mdx b/apps/sdp-docs/content/docs/guides/setup-wallets.mdx index d08181a9e..d1ea5d1c9 100644 --- a/apps/sdp-docs/content/docs/guides/setup-wallets.mdx +++ b/apps/sdp-docs/content/docs/guides/setup-wallets.mdx @@ -59,7 +59,7 @@ Each provider supports a different set of operations shown as capability chips: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/wallets/initialize \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -70,7 +70,7 @@ curl -X POST https://api.solana.com/v1/wallets/initialize \ ``` -```typescript +```typescript title="initialize-wallet-provider.ts" const response = await fetch("https://api.solana.com/v1/wallets/initialize", { method: "POST", headers: { @@ -87,7 +87,7 @@ const { data } = await response.json(); ``` -```java +```java title="InitializeWalletProvider.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/wallets/initialize")) .header("Authorization", "Bearer sk_test_...") @@ -106,7 +106,7 @@ HttpRequest request = HttpRequest.newBuilder() -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/wallets \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -118,7 +118,7 @@ curl -X POST https://api.solana.com/v1/wallets \ ``` -```typescript +```typescript title="create-wallet.ts" const response = await fetch("https://api.solana.com/v1/wallets", { method: "POST", headers: { @@ -134,7 +134,7 @@ const response = await fetch("https://api.solana.com/v1/wallets", { ``` -```java +```java title="CreateWallet.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/wallets")) .header("Authorization", "Bearer sk_test_...") @@ -156,13 +156,13 @@ The `purpose` field is optional metadata in the API. The current dashboard creat -```bash +```bash title="Terminal" curl "https://api.solana.com/v1/wallets?view=summary" \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="list-wallets.ts" const response = await fetch("https://api.solana.com/v1/wallets?view=summary", { headers: { "Authorization": "Bearer sk_test_..." }, }); @@ -171,7 +171,7 @@ const { data } = await response.json(); ``` -```java +```java title="ListWallets.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/wallets?view=summary")) .header("Authorization", "Bearer sk_test_...") @@ -187,7 +187,7 @@ Use the returned `walletId` when setting a default wallet or binding wallet-scop -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/wallets/default-wallet \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -195,7 +195,7 @@ curl -X POST https://api.solana.com/v1/wallets/default-wallet \ ``` -```typescript +```typescript title="set-default-wallet.ts" await fetch("https://api.solana.com/v1/wallets/default-wallet", { method: "POST", headers: { @@ -207,7 +207,7 @@ await fetch("https://api.solana.com/v1/wallets/default-wallet", { ``` -```java +```java title="SetDefaultWallet.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/wallets/default-wallet")) .header("Authorization", "Bearer sk_test_...") diff --git a/apps/sdp-docs/content/docs/guides/tokenize-an-asset.mdx b/apps/sdp-docs/content/docs/guides/tokenize-an-asset.mdx index 3b7341b0e..ec7f704fe 100644 --- a/apps/sdp-docs/content/docs/guides/tokenize-an-asset.mdx +++ b/apps/sdp-docs/content/docs/guides/tokenize-an-asset.mdx @@ -18,9 +18,9 @@ Default to SDP-controlled custody wallets for your main authorities when you exp ## The implementation sequence -1. [Set Up Your Organization](/docs/guides/setup-organization) +1. [Set Up Your Organization & Projects](/docs/guides/setup-organization) 2. [Set Up Wallets](/docs/guides/setup-wallets) -3. [Manage API Keys](/docs/guides/manage-api-keys) +3. [Manage API Keys](/docs/developing-with-sdp/manage-api-keys) 4. [Create a Token](/docs/guides/create-a-token) 5. [Deploy a Token](/docs/guides/deploy-a-token) 6. [Mint and Burn Tokens](/docs/guides/mint-and-burn) @@ -56,6 +56,6 @@ See the full public endpoint map in the [Issuance API reference](/docs/reference If you are driving SDP through an agent workflow, start from the public AI docs guide: -- [AI Consumption](/docs/reference/ai-consumption) +- [Documentation for AI](/docs/reference/docs-for-ai) That page links the machine-readable SDP discovery files and keeps AI-facing guidance grounded in the supported public docs and API surface rather than internal handlers. diff --git a/apps/sdp-docs/content/docs/home.mdx b/apps/sdp-docs/content/docs/home.mdx deleted file mode 100644 index c4c388dcf..000000000 --- a/apps/sdp-docs/content/docs/home.mdx +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Solana Developer Platform Docs -description: A dashboard and REST API for real-world asset issuance, payments, and markets on Solana — with built-in compliance controls. -hideTitle: true ---- - - diff --git a/apps/sdp-docs/content/docs/index.mdx b/apps/sdp-docs/content/docs/index.mdx new file mode 100644 index 000000000..80cbd27bb --- /dev/null +++ b/apps/sdp-docs/content/docs/index.mdx @@ -0,0 +1,8 @@ +--- +title: |- + Ship anything onchain + with Solana Developer Platform +description: A dashboard and REST API with built-in compliance controls for real-world asset issuance, payments, custody, and markets on Solana. +--- + + diff --git a/apps/sdp-docs/content/docs/introduction.mdx b/apps/sdp-docs/content/docs/introduction.mdx index 933d201d0..ee514154a 100644 --- a/apps/sdp-docs/content/docs/introduction.mdx +++ b/apps/sdp-docs/content/docs/introduction.mdx @@ -1,41 +1,124 @@ --- -title: Introduction -description: What SDP is, what it provides, and how it works. +title: What is SDP? +icon: BookOpen +description: Putting institutional-grade capital markets at your fingertips. Issue, move, and manage regulated assets on Solana. --- -Solana Developer Platform (SDP) is a dashboard and REST API for real-world asset (RWA) issuance, payments, and markets on Solana. It handles wallet custody, onchain transaction construction, and compliance operations so your team can bring regulated assets onchain without managing keys or setting up Solana infrastructure. - -## What you get - -**Token lifecycle management** — Create tokens from pre-built templates (stablecoin, tokenized security) or configure your own using Token-2022 extensions. Deploy, mint, burn, and manage supply through the dashboard or API. - -**Wallet custody** — Provision signing wallets through integrated custody providers. SDP manages the signing infrastructure so your team never handles private keys directly. - -**Projects and scoped access** — Organize integrations by project, assign members, and issue project-scoped API keys so teams or environments do not share the same wallet and token surface. - -**Compliance operations** — Freeze individual accounts, pause token activity, seize tokens, and manage allowlists for regulated issuance flows. Selected compliance actions accept reason or memo fields for audit trails. - -**Two signing modes** — SDP supports both modes for transaction-building flows such as issuance, payments, and compliance actions: +import { Steps, Step } from "fumadocs-ui/components/steps"; +import "../../src/styles/ramp-rotator.css"; + +## Background + +Institutional capital is moving onchain. As of July 2026, tokenized real-world assets total roughly [$35 billion across public blockchains](https://app.rwa.xyz/), alongside nearly $300 billion in stablecoins. On Solana that includes about [$3.2 billion in tokenized assets](https://app.rwa.xyz/networks/solana) and a $15.8 billion stablecoin supply that settled over [$560 billion in transfers](https://solana.com/data) in the past 30 days. + +Tokenized stocks are the fastest-growing slice of that market. Onchain equities now total [roughly $1.9 billion](https://app.rwa.xyz/stocks), and Solana settles [about 95% of all tokenized stock trading](https://cryptobriefing.com/solana-tokenized-stocks-volume-surges-h1-2026/), with $4.9 billion in volume in the first half of 2026 alone. Names like Micron, SanDisk, Tesla, and Apple already trade onchain, alongside tokenized ETFs like the iShares Core S&P 500. + +Assets at this scale need the same guarantees traditional markets provide: regulated custody, enforceable compliance, and auditable operational controls. Building that from raw Solana primitives means standing up wallet infrastructure, transaction construction, and compliance tooling before shipping anything. + +And there is more to be done: most of the world's capital still sits offchain. SDP aims to attract it by providing simplified programming models through a unified application layer for issuance, ramps, transfers, and compliance operations, so you can start without needing to maintain a huge team of Solana developers. + +## Core Features + + + +### Unified Programming Interface + +SDP puts one stable API in front of the Solana ecosystem's providers, so you integrate once and swap or add providers without changing your code. The same onramp call works for every ramp provider. The `provider` field is the only thing that changes. + + + +```javascript title="onramp-quote.js" rotate-providers +const response = await fetch( + "https://api.solana.com/v1/payments/ramps/onramp/quote", + { + method: "POST", + headers: { + Authorization: "Bearer sk_test_...", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + provider: "moonpay", + counterpartyId: "cp_...", + destinationWallet: "wal_...", + cryptoToken: "USDC", + fiatCurrency: "USD", + fiatAmount: "100.00", + }), + }, +); +const quote = await response.json(); +``` + + +```python title="onramp_quote.py" rotate-providers +import requests + +response = requests.post( + "https://api.solana.com/v1/payments/ramps/onramp/quote", + headers={"Authorization": "Bearer sk_test_..."}, + json={ + "provider": "moonpay", + "counterpartyId": "cp_...", + "destinationWallet": "wal_...", + "cryptoToken": "USDC", + "fiatCurrency": "USD", + "fiatAmount": "100.00", + }, +) +quote = response.json() +``` + + +```bash title="Terminal" rotate-providers +curl -X POST https://api.solana.com/v1/payments/ramps/onramp/quote \ + -H "Authorization: Bearer sk_test_..." \ + -H "Content-Type: application/json" \ + -d '{ + "provider": "moonpay", + "counterpartyId": "cp_...", + "destinationWallet": "wal_...", + "cryptoToken": "USDC", + "fiatCurrency": "USD", + "fiatAmount": "100.00" + }' +``` + + + + +### Wallet Custody + +Provision signing wallets through integrated custody providers, so your team never handles private keys. Hosted keys never leave the provider's MPC or TEE infrastructure and SDP only handles the signed transaction. Local development keys are encrypted at rest with `AES-256-GCM` using per-organization derived keys, and every signing request passes policy and permission checks before it executes. + +1. **Built on `@solana/keychain`** + + The unified Solana signing interface. Every custody backend is a keychain adapter behind the same signing API, which is what makes providers interchangeable. + +2. **[Wallet policies](/docs/wallet-operations/policies)** + + Constrain how a wallet can move funds. Destination controls, amount limits, operation permissions, approval requirements, and audit visibility, enforced before any operation executes. + +3. **Scoped custody configuration** + + Custody settings resolve per project, then organization, then environment, so different teams can run different providers side by side. + + +### Projects and scoped access + +Organize integrations by project, assign members, and issue project-scoped API keys so teams or environments do not share the same wallet and token surface. Generate keys with specific permission sets (`tokens:read`, `tokens:write`, `tokens:admin`, etc.) and rotate them without downtime. + + +### Compliance operations + +Freeze individual accounts, pause token activity, seize tokens, and manage allowlists for regulated issuance flows. Selected compliance actions accept reason or memo fields for audit trails. + + +### Two signing modes + +SDP supports both modes for transaction-building flows such as issuance, payments, and compliance actions: - **Execute** — SDP builds, signs, and submits the transaction in one API call -- **Prepare** — SDP builds the transaction and returns it unsigned. You sign with your own infrastructure (hardware wallet, multisig, internal HSM) and submit it yourself. - -**Transaction simulation** — Prepare endpoints accept a `simulate: true` option that dry-runs the transaction before you sign, returning compute units consumed, program logs, and any errors. - -**Scoped API keys** — Generate keys with specific permission sets (`tokens:read`, `tokens:write`, `tokens:admin`, etc.) and rotate them without downtime. - -## Architecture - -SDP exposes two interfaces that connect to the same backend: - -- **Dashboard** — browser-based UI for setup, token management, and compliance operations. Sign in with email, Google, or GitHub. -- **REST API** — programmatic access for backend integrations. Authenticate with an API key in the `Authorization` header. - -## Environments - -| Environment | API key prefix | Solana network | Purpose | -| ----------- | -------------- | -------------- | ------------------------------------------- | -| Sandbox | `sk_test_` | devnet | Development and testing with no real assets | -| Production | `sk_live_` | mainnet-beta | Live operations with real assets | +- **Prepare** — SDP builds the transaction and returns it unsigned. You sign with your own infrastructure (hardware wallet, multisig, internal HSM) and submit it yourself. Prepare endpoints accept a `simulate: true` option that dry-runs the transaction before you sign, returning compute units consumed, program logs, and any errors. + + -Both environments expose identical APIs. Develop and test against sandbox, then switch to production by swapping your API key. diff --git a/apps/sdp-docs/content/docs/meta.json b/apps/sdp-docs/content/docs/meta.json index 2127ab1d8..5298d6a71 100644 --- a/apps/sdp-docs/content/docs/meta.json +++ b/apps/sdp-docs/content/docs/meta.json @@ -1,8 +1,8 @@ { "title": "Solana Developer Platform Docs", "pages": [ - "home", "introduction", + "developing-with-sdp", "guides", "tokens", "wallet-operations", diff --git a/apps/sdp-docs/content/docs/payments/accept-indexing.mdx b/apps/sdp-docs/content/docs/payments/accept-indexing.mdx index 3506e759c..8a41fe6e0 100644 --- a/apps/sdp-docs/content/docs/payments/accept-indexing.mdx +++ b/apps/sdp-docs/content/docs/payments/accept-indexing.mdx @@ -11,13 +11,13 @@ The minimal inbound-only request: -```bash +```bash title="Terminal" curl "https://api.solana.com/v1/payments/transfers?direction=inbound&pageSize=50" \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="list-inbound-transfers.ts" const url = new URL("https://api.solana.com/v1/payments/transfers"); url.searchParams.set("direction", "inbound"); url.searchParams.set("pageSize", "50"); @@ -61,7 +61,7 @@ Customer-initiated inbound transfers do **not** populate `Transfer.memo` (memo-p The typical inbound match cycle: 1. **Read** the recent inbound transfers for the relevant token, using the **org-scoped path** (no `wallet`/`walletAddress` filter) so `from`/`to` are honored: - ``` + ```text title="Request" GET /v1/payments/transfers ?direction=inbound &token= @@ -85,7 +85,7 @@ A reconciliation worker that wakes on a tick and asks "what is new since last ti -```typescript +```typescript title="reconcile-tick.ts" const OVERLAP_MS = 5_000; // re-query the last 5s on each tick to catch boundary arrivals async function reconcileTick(state: WorkerState) { diff --git a/apps/sdp-docs/content/docs/payments/ramps-providers.mdx b/apps/sdp-docs/content/docs/payments/ramps-providers.mdx index bc3861f37..f312fd27b 100644 --- a/apps/sdp-docs/content/docs/payments/ramps-providers.mdx +++ b/apps/sdp-docs/content/docs/payments/ramps-providers.mdx @@ -62,7 +62,7 @@ BVNK uses Hawk authentication. Bearer-token auth is not currently supported. BVNK is the only provider that currently accepts the `bvnkCompliance` field on the ramp request — an object of the form `{ "partyDetails": [...] }` carrying compliance party records used to satisfy travel-rule and KYC sharing requirements. The exact shape of each party record is provider-defined; see BVNK's compliance docs for the field schema. -```json +```json title="request.json" { "provider": "bvnk", "destinationWallet": "wal_...", diff --git a/apps/sdp-docs/content/docs/payments/ramps.mdx b/apps/sdp-docs/content/docs/payments/ramps.mdx index 22af73a34..6cc7a8638 100644 --- a/apps/sdp-docs/content/docs/payments/ramps.mdx +++ b/apps/sdp-docs/content/docs/payments/ramps.mdx @@ -20,7 +20,7 @@ The end-user journey: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/payments/ramps/onramp/execute \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -35,7 +35,7 @@ curl -X POST https://api.solana.com/v1/payments/ramps/onramp/execute \ ``` -```typescript +```typescript title="onramp-execute.ts" const response = await fetch( "https://api.solana.com/v1/payments/ramps/onramp/execute", { @@ -60,7 +60,7 @@ const { data } = await response.json(); ``` -```java +```java title="OnrampExecute.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/payments/ramps/onramp/execute")) .header("Authorization", "Bearer sk_test_...") @@ -98,7 +98,7 @@ The mirror image — convert from crypto to fiat from a wallet you control: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/payments/ramps/offramp/execute \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -112,7 +112,7 @@ curl -X POST https://api.solana.com/v1/payments/ramps/offramp/execute \ ``` -```typescript +```typescript title="offramp-execute.ts" const response = await fetch( "https://api.solana.com/v1/payments/ramps/offramp/execute", { diff --git a/apps/sdp-docs/content/docs/payments/send-basic-payment.mdx b/apps/sdp-docs/content/docs/payments/send-basic-payment.mdx index 0374be4cc..d8b9bf6f8 100644 --- a/apps/sdp-docs/content/docs/payments/send-basic-payment.mdx +++ b/apps/sdp-docs/content/docs/payments/send-basic-payment.mdx @@ -56,7 +56,7 @@ Fill in: Build the request body: -```typescript +```typescript title="transfer-request.ts" const body = { source: "privy_wallet_123", // `walletId` from GET /v1/wallets destination: "7xKXz...9fGh", // recipient wallet (owner) address @@ -91,7 +91,7 @@ The transfer status updates to `Confirmed` once the transaction is finalized. -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/payments/transfers \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -105,7 +105,7 @@ curl -X POST https://api.solana.com/v1/payments/transfers \ ``` -```typescript +```typescript title="execute-transfer.ts" const response = await fetch( "https://api.solana.com/v1/payments/transfers", { @@ -128,7 +128,7 @@ const { data } = await response.json(); ``` -```java +```java title="ExecuteTransfer.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/payments/transfers")) .header("Authorization", "Bearer sk_test_...") @@ -169,14 +169,14 @@ Click the record to see full details including the Solana Explorer link. -```typescript +```typescript title="verify-transfer.ts" // data.transfer.status — "confirmed" // data.transfer.signature — onchain tx signature ``` Poll status for async confirmations: -```bash +```bash title="Terminal" curl https://api.solana.com/v1/payments/transfers/txn_abc123 \ -H "Authorization: Bearer sk_test_..." ``` diff --git a/apps/sdp-docs/content/docs/payments/send-payment-with-memo.mdx b/apps/sdp-docs/content/docs/payments/send-payment-with-memo.mdx index a8d11fe5c..44d0a51b0 100644 --- a/apps/sdp-docs/content/docs/payments/send-payment-with-memo.mdx +++ b/apps/sdp-docs/content/docs/payments/send-payment-with-memo.mdx @@ -19,7 +19,7 @@ Memos are stored by SDP. They are **not** automatically attached as a Solana mem -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/payments/transfers \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -33,7 +33,7 @@ curl -X POST https://api.solana.com/v1/payments/transfers \ ``` -```typescript +```typescript title="send-payment-with-memo.ts" const response = await fetch( "https://api.solana.com/v1/payments/transfers", { @@ -55,7 +55,7 @@ const { data } = await response.json(); ``` -```java +```java title="SendPaymentWithMemo.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/payments/transfers")) .header("Authorization", "Bearer sk_test_...") @@ -75,7 +75,7 @@ HttpRequest request = HttpRequest.newBuilder() The memo is preserved on the transfer record: -```json +```json title="response.json" { "id": "xfr_...", "memo": "order_2026-05-14_4837", diff --git a/apps/sdp-docs/content/docs/payments/send-payouts.mdx b/apps/sdp-docs/content/docs/payments/send-payouts.mdx index 672b2ce53..04b3be3d7 100644 --- a/apps/sdp-docs/content/docs/payments/send-payouts.mdx +++ b/apps/sdp-docs/content/docs/payments/send-payouts.mdx @@ -30,7 +30,7 @@ If your retry path replays a transfer with identical inputs but a fresh request, -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/payments/transfers \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -44,7 +44,7 @@ curl -X POST https://api.solana.com/v1/payments/transfers \ ``` -```typescript +```typescript title="payout-recipient.ts" async function payoutRecipient(row: PayoutRow): Promise { if (row.transferId) return row.transferId; // already submitted @@ -74,7 +74,7 @@ async function payoutRecipient(row: PayoutRow): Promise { ``` -```java +```java title="PayoutRecipient.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/payments/transfers")) .header("Authorization", "Bearer sk_test_...") @@ -98,13 +98,13 @@ After the loop finishes, list all outbound transfers in the run's time window an -```bash +```bash title="Terminal" curl "https://api.solana.com/v1/payments/transfers?direction=outbound&from=2026-05-14T00:00:00Z&to=2026-05-14T23:59:59Z&pageSize=100" \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="list-outbound-transfers.ts" async function* listOutboundTransfers(from: string, to: string) { let page = 1; while (true) { diff --git a/apps/sdp-docs/content/docs/reference/ai-consumption.mdx b/apps/sdp-docs/content/docs/reference/docs-for-ai.mdx similarity index 98% rename from apps/sdp-docs/content/docs/reference/ai-consumption.mdx rename to apps/sdp-docs/content/docs/reference/docs-for-ai.mdx index 6347bfd6d..65bf104de 100644 --- a/apps/sdp-docs/content/docs/reference/ai-consumption.mdx +++ b/apps/sdp-docs/content/docs/reference/docs-for-ai.mdx @@ -1,5 +1,5 @@ --- -title: AI Consumption +title: Documentation for AI description: Public machine-readable entry points and guidance for agents and AI systems consuming SDP docs and APIs. --- diff --git a/apps/sdp-docs/content/docs/reference/meta.json b/apps/sdp-docs/content/docs/reference/meta.json index ba5ecdbcf..b0c904c14 100644 --- a/apps/sdp-docs/content/docs/reference/meta.json +++ b/apps/sdp-docs/content/docs/reference/meta.json @@ -4,7 +4,7 @@ "index", "issuance-token-types", "provider-onboarding", - "ai-consumption", + "docs-for-ai", "postman-collection", "api" ] diff --git a/apps/sdp-docs/content/docs/self-hosting/first-devnet-deployment.mdx b/apps/sdp-docs/content/docs/self-hosting/first-devnet-deployment.mdx index 5159e91fa..bcaaff1e1 100644 --- a/apps/sdp-docs/content/docs/self-hosting/first-devnet-deployment.mdx +++ b/apps/sdp-docs/content/docs/self-hosting/first-devnet-deployment.mdx @@ -28,7 +28,7 @@ devnet RPC endpoint from your provider before treating this flow as reliable. From the directory where the installer placed `compose.yml` and `.env`: -```bash +```bash title="Terminal" cd ~/sdp docker compose up -d docker compose ps @@ -80,7 +80,7 @@ The signing step generates a local Solana signer in the browser and shows the public key to fund on devnet. Save the generated file as `~/sdp/.env`, then restart the stack: -```bash +```bash title="Terminal" cd ~/sdp docker compose up -d ``` @@ -103,7 +103,7 @@ show the organization and project context instead of onboarding errors. If the dashboard loads but organization state does not appear, check the API logs: -```bash +```bash title="Terminal" docker compose logs -f sdp-api ``` @@ -121,7 +121,7 @@ named `Devnet local wallet`. If you prefer the API path, use an admin key from the dashboard: -```bash +```bash title="Terminal" curl -X POST http://localhost:8787/v1/wallets/initialize \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -154,7 +154,7 @@ Save the full key when the dashboard shows it. SDP only displays it once. Use the new key against your self-hosted API: -```bash +```bash title="Terminal" export SDP_API_KEY="sk_test_..." curl "http://localhost:8787/v1/wallets?view=summary" \ diff --git a/apps/sdp-docs/content/docs/self-hosting/quickstart.mdx b/apps/sdp-docs/content/docs/self-hosting/quickstart.mdx index 76c322c62..7d94932cd 100644 --- a/apps/sdp-docs/content/docs/self-hosting/quickstart.mdx +++ b/apps/sdp-docs/content/docs/self-hosting/quickstart.mdx @@ -12,7 +12,7 @@ Docker daemon is running. The install script downloads `compose.yml` and `.env.example` for the latest release into `~/sdp` and verifies them against the release checksums. -```bash +```bash title="Terminal" curl -fsSL https://github.com/solana-foundation/solana-developer-platform/releases/latest/download/install.sh | bash ``` @@ -35,7 +35,7 @@ the app secrets, and validates your answers. answer the prompts, and download the `.env` into `~/sdp`. - **In your terminal** — run the configurator from the API image: - ```bash + ```bash title="Terminal" docker run --rm -it -v "$HOME/sdp:/out" \ ghcr.io/solana-foundation/sdp/sdp-api:latest \ node configure.js --out /out/.env @@ -49,7 +49,7 @@ variable means and which are required. ## 3. Bring up the stack -```bash +```bash title="Terminal" cd ~/sdp docker compose up -d ``` @@ -60,7 +60,7 @@ minutes. ## 4. Verify -```bash +```bash title="Terminal" docker compose ps docker compose logs -f sdp-api ``` diff --git a/apps/sdp-docs/content/docs/self-hosting/troubleshooting.mdx b/apps/sdp-docs/content/docs/self-hosting/troubleshooting.mdx index 3aa9e3907..029641dc4 100644 --- a/apps/sdp-docs/content/docs/self-hosting/troubleshooting.mdx +++ b/apps/sdp-docs/content/docs/self-hosting/troubleshooting.mdx @@ -6,7 +6,7 @@ description: Diagnose common startup and configuration failures on a self-hosted Most self-hosting problems surface at startup. Start by looking at the service state and logs: -```bash +```bash title="Terminal" cd ~/sdp docker compose ps docker compose logs -f sdp-api @@ -50,7 +50,7 @@ process holds one of them, the service won't bind. Override the host port in `sdp-migrate` runs once before the API starts; if it fails, the API stays down. Inspect it with: -```bash +```bash title="Terminal" docker compose logs sdp-migrate ``` diff --git a/apps/sdp-docs/content/docs/self-hosting/upgrade-and-backup.mdx b/apps/sdp-docs/content/docs/self-hosting/upgrade-and-backup.mdx index 4f7858f4f..bc6f0dba6 100644 --- a/apps/sdp-docs/content/docs/self-hosting/upgrade-and-backup.mdx +++ b/apps/sdp-docs/content/docs/self-hosting/upgrade-and-backup.mdx @@ -15,7 +15,7 @@ A release ships new container images plus an updated `compose.yml`. To upgrade: checksums) and leaves any existing `.env` and `.env.example` in place, printing a link to the new release's `.env.example` so you can check for new variables. - ```bash + ```bash title="Terminal" curl -fsSL https://github.com/solana-foundation/solana-developer-platform/releases/latest/download/install.sh | bash ``` @@ -25,7 +25,7 @@ A release ships new container images plus an updated `compose.yml`. To upgrade: 3. **Pull and restart.** - ```bash + ```bash title="Terminal" cd ~/sdp docker compose pull docker compose up -d @@ -64,7 +64,7 @@ Take a logical dump with `pg_dump` from the running container. Running it throug `sh -c` lets `pg_dump` read `POSTGRES_USER` and `POSTGRES_DB` from inside the container, so custom values are honored: -```bash +```bash title="Terminal" cd ~/sdp docker compose exec -T postgres \ sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > sdp-backup.sql @@ -79,7 +79,7 @@ into an **empty** database and let the stack start on top of it. Do not restore over an already-migrated database, or the dump's schema collides with the one `sdp-migrate` created on the previous `up`. -```bash +```bash title="Terminal" cd ~/sdp docker compose down # stop every service docker volume rm sdp_sdp-postgres-data # discard the current database (destructive) diff --git a/apps/sdp-docs/content/docs/tokens/allowlists.mdx b/apps/sdp-docs/content/docs/tokens/allowlists.mdx index c8707cb7a..32108a288 100644 --- a/apps/sdp-docs/content/docs/tokens/allowlists.mdx +++ b/apps/sdp-docs/content/docs/tokens/allowlists.mdx @@ -40,7 +40,7 @@ The Compliance tab on the token detail page shows the allowlist management inter Set `requiresAllowlist: true` when creating the token: -```typescript +```typescript title="enable-allowlist.ts" body: JSON.stringify({ name: "Acme Security", symbol: "ACMES", @@ -81,7 +81,7 @@ After adding, the entry appears below the form with a **Remove entry** button. -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -91,7 +91,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist \ }' ``` -```typescript +```typescript title="add-allowlist-entry.ts" const res = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist", { @@ -127,12 +127,12 @@ Use the search field to find a specific address by label or public key. -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="list-allowlist.ts" const { data, meta } = await res.json(); // meta: { total, page, pageSize, hasMore } ``` @@ -158,7 +158,7 @@ In the **Compliance → Allowlist** tab, click **Remove entry** next to the addr -```bash +```bash title="Terminal" curl -X DELETE \ https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist/alw_abc123 \ -H "Authorization: Bearer sk_test_..." diff --git a/apps/sdp-docs/content/docs/tokens/create-a-token.mdx b/apps/sdp-docs/content/docs/tokens/create-a-token.mdx index 74d958d35..fad821524 100644 --- a/apps/sdp-docs/content/docs/tokens/create-a-token.mdx +++ b/apps/sdp-docs/content/docs/tokens/create-a-token.mdx @@ -49,12 +49,12 @@ Select a template and click **Continue**. List available templates to see what fields each one pre-configures: -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/templates \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="list-templates.ts" const res = await fetch("https://api.solana.com/v1/issuance/templates", { headers: { Authorization: "Bearer sk_test_..." }, }); @@ -92,7 +92,7 @@ Click **Continue** when ready. Pass identity fields in the `POST /v1/issuance/tokens` body: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -133,7 +133,7 @@ Click **Create token** to save the draft. Add operational fields to the same body: -```typescript +```typescript title="create-token.ts" const res = await fetch("https://api.solana.com/v1/issuance/tokens", { method: "POST", headers: { @@ -155,7 +155,7 @@ const res = await fetch("https://api.solana.com/v1/issuance/tokens", { For a custom token, omit `template` or set it to `"custom"` and pass `overrides.extensions`: -```typescript +```typescript title="create-custom-token.ts" body: JSON.stringify({ name: "Game Coin", symbol: "GAME", @@ -199,7 +199,7 @@ Open the token to see its detail page. The status shows **Not deployed** and a * A successful response returns the new token object: -```typescript +```typescript title="create-token.ts" const { data } = await res.json(); // data.token.id — save this; used in all subsequent calls // data.token.status — "pending" diff --git a/apps/sdp-docs/content/docs/tokens/deploy-a-token.mdx b/apps/sdp-docs/content/docs/tokens/deploy-a-token.mdx index c45bf050a..2041a7177 100644 --- a/apps/sdp-docs/content/docs/tokens/deploy-a-token.mdx +++ b/apps/sdp-docs/content/docs/tokens/deploy-a-token.mdx @@ -37,7 +37,7 @@ The **Deploy** button appears in the token header and in the fund management pan You need the `tokenId` returned when you created the token. If you don't have it, list your tokens: -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/tokens \ -H "Authorization: Bearer sk_test_..." ``` @@ -73,13 +73,13 @@ SDP submits the transaction to Solana and polls for confirmation. The page updat **Execute mode** — SDP signs and submits: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/deploy \ -H "Authorization: Bearer sk_test_..." \ -H "Idempotency-Key: deploy-acme-001" ``` -```typescript +```typescript title="deploy-token.ts" const res = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/deploy", { @@ -96,7 +96,7 @@ const { data } = await res.json(); **Prepare mode** — returns an unsigned transaction: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/deploy/prepare \ -H "Authorization: Bearer sk_test_..." ``` @@ -128,14 +128,14 @@ The token status badge changes from `Not deployed` to the onchain mint address. The deploy response includes the confirmed state: -```typescript +```typescript title="deploy-token.ts" // data.token.status — "active" // data.token.mintAddress — e.g., "AcMeXYZ..." ``` Fetch the token at any time to verify: -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/tokens/tok_abc123 \ -H "Authorization: Bearer sk_test_..." ``` diff --git a/apps/sdp-docs/content/docs/tokens/freeze-and-compliance.mdx b/apps/sdp-docs/content/docs/tokens/freeze-and-compliance.mdx index 5f8d1b0b6..02944b19b 100644 --- a/apps/sdp-docs/content/docs/tokens/freeze-and-compliance.mdx +++ b/apps/sdp-docs/content/docs/tokens/freeze-and-compliance.mdx @@ -34,7 +34,7 @@ Enter the wallet address and select the intent (e.g., transfer destination or al -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/compliance/address-screenings \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -45,7 +45,7 @@ curl -X POST https://api.solana.com/v1/compliance/address-screenings \ }' ``` -```typescript +```typescript title="screen-address.ts" const { data } = await res.json(); // data.screening.providers[] // → { provider, status, riskScore, riskLevel, message, evaluatedAt } @@ -96,7 +96,7 @@ The "Unfreeze transaction finalized." toast appears and the Frozen Accounts coun **Freeze:** -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/freeze \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -110,7 +110,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/freeze \ **Unfreeze:** -```typescript +```typescript title="unfreeze-account.ts" await fetch("https://api.solana.com/v1/issuance/tokens/tok_abc123/unfreeze", { method: "POST", headers: { Authorization: "Bearer sk_test_...", "Content-Type": "application/json" }, @@ -120,7 +120,7 @@ await fetch("https://api.solana.com/v1/issuance/tokens/tok_abc123/unfreeze", { **List frozen accounts:** -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/tokens/tok_abc123/frozen \ -H "Authorization: Bearer sk_test_..." ``` @@ -158,7 +158,7 @@ To resume, click **Unpause token** and confirm. -```bash +```bash title="Terminal" # Pause curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/pause \ -H "Authorization: Bearer sk_test_..." @@ -189,7 +189,7 @@ In the **Compliance** tab, click **Seize tokens**. Provide the source, destinati -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/seize \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ diff --git a/apps/sdp-docs/content/docs/tokens/manage-token-settings.mdx b/apps/sdp-docs/content/docs/tokens/manage-token-settings.mdx index 350dfa586..dfa370d8f 100644 --- a/apps/sdp-docs/content/docs/tokens/manage-token-settings.mdx +++ b/apps/sdp-docs/content/docs/tokens/manage-token-settings.mdx @@ -43,12 +43,12 @@ The **Extensions** tab shows the template and each operational flag — allowlis Fetch the current token state: -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/tokens/tok_abc123 \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="get-token-settings.ts" const { data } = await res.json(); // data.token.authorities — { mintAuthority, freezeAuthority, permanentDelegate } // data.token.extensions — active extensions on the token @@ -80,7 +80,7 @@ On the token detail page, go to **Permissions**. Click **Edit** next to the auth **Execute mode** — SDP signs the authority rotation: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/authority \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -95,7 +95,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/authority \ **Prepare mode** — returns unsigned transaction for external signing: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/authority/prepare \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -130,7 +130,7 @@ In **Settings → Authorities**, click **Revoke** next to the authority and conf Set `newAuthority` to `null` to revoke: -```typescript +```typescript title="revoke-authority.ts" body: JSON.stringify({ authority: { type: "mintAuthority", diff --git a/apps/sdp-docs/content/docs/tokens/mint-and-burn.mdx b/apps/sdp-docs/content/docs/tokens/mint-and-burn.mdx index 4da5032ed..1aae76627 100644 --- a/apps/sdp-docs/content/docs/tokens/mint-and-burn.mdx +++ b/apps/sdp-docs/content/docs/tokens/mint-and-burn.mdx @@ -38,7 +38,7 @@ The **Fund management** panel shows the current supply with two primary actions: All supply operations share this URL pattern: -``` +```text title="Endpoints" POST /v1/issuance/tokens/{tokenId}/mint POST /v1/issuance/tokens/{tokenId}/burn POST /v1/issuance/tokens/{tokenId}/force-burn @@ -83,7 +83,7 @@ Once confirmed, the mint appears in the Transactions table with a `confirmed` st -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/mint \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -97,7 +97,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/mint \ }' ``` -```typescript +```typescript title="mint-tokens.ts" const res = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/mint", { @@ -161,7 +161,7 @@ For **Force burn** (requires `tokens:admin`), use the Compliance tab instead of **Burn from a controlled account:** -```typescript +```typescript title="burn-tokens.ts" await fetch("https://api.solana.com/v1/issuance/tokens/tok_abc123/burn", { method: "POST", headers: { @@ -177,7 +177,7 @@ await fetch("https://api.solana.com/v1/issuance/tokens/tok_abc123/burn", { **Force-burn from any holder** (requires `tokens:admin`): -```typescript +```typescript title="force-burn-tokens.ts" await fetch("https://api.solana.com/v1/issuance/tokens/tok_abc123/force-burn", { method: "POST", headers: { @@ -216,7 +216,7 @@ Reload the token detail page if the displayed supply value appears stale. -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/supply/refresh \ -H "Authorization: Bearer sk_test_..." ``` diff --git a/apps/sdp-docs/content/docs/tokens/prepare-vs-execute.mdx b/apps/sdp-docs/content/docs/tokens/prepare-vs-execute.mdx index 7c202cb4b..34762e337 100644 --- a/apps/sdp-docs/content/docs/tokens/prepare-vs-execute.mdx +++ b/apps/sdp-docs/content/docs/tokens/prepare-vs-execute.mdx @@ -14,7 +14,7 @@ Call a mutation endpoint without the `/prepare` suffix and SDP handles everythin 3. Submits it to Solana 4. Returns the confirmed result with a transaction signature -```typescript +```typescript title="mint-execute.ts" const res = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/mint", { @@ -45,7 +45,7 @@ Issuance `amount` fields use **UI units** (decimal strings such as `"1000"` for Add `/prepare` to a mutation endpoint and SDP builds the transaction but returns it unsigned: -```typescript +```typescript title="mint-prepare.ts" const res = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/mint/prepare", { diff --git a/apps/sdp-docs/content/docs/tokens/tokenize-an-asset.mdx b/apps/sdp-docs/content/docs/tokens/tokenize-an-asset.mdx index 869da77cb..4be356b12 100644 --- a/apps/sdp-docs/content/docs/tokens/tokenize-an-asset.mdx +++ b/apps/sdp-docs/content/docs/tokens/tokenize-an-asset.mdx @@ -22,9 +22,9 @@ Use SDP-controlled custody wallets for your main authorities when you expect SDP Each step links to its own guide with UI and API instructions: -1. [Set Up Your Organization](/docs/guides/setup-organization) +1. [Set Up Your Organization & Projects](/docs/guides/setup-organization) 2. [Set Up Wallets](/docs/guides/setup-wallets) -3. [Manage API Keys](/docs/guides/manage-api-keys) +3. [Manage API Keys](/docs/developing-with-sdp/manage-api-keys) 4. [Create a Token](/docs/tokens/create-a-token) 5. [Deploy a Token](/docs/tokens/deploy-a-token) 6. [Mint and Burn](/docs/tokens/mint-and-burn) @@ -57,4 +57,4 @@ See the full schema in the [Issuance API reference](/docs/reference/api/issuance ## For AI agents -If you are driving SDP through an agent workflow, start from [AI Consumption](/docs/reference/ai-consumption). That page links the machine-readable SDP discovery files and keeps AI-facing guidance grounded in the supported public API surface. +If you are driving SDP through an agent workflow, start from [Documentation for AI](/docs/reference/docs-for-ai). That page links the machine-readable SDP discovery files and keeps AI-facing guidance grounded in the supported public API surface. diff --git a/apps/sdp-docs/content/docs/tutorials/issue-a-regulated-stablecoin.mdx b/apps/sdp-docs/content/docs/tutorials/issue-a-regulated-stablecoin.mdx index 5b799b477..cd92a0c7d 100644 --- a/apps/sdp-docs/content/docs/tutorials/issue-a-regulated-stablecoin.mdx +++ b/apps/sdp-docs/content/docs/tutorials/issue-a-regulated-stablecoin.mdx @@ -53,7 +53,7 @@ From the dashboard, mint an org-scoped API key with **Role: Admin**, **Environme With the Admin key, call `GET /v1/projects` and pick the project with slug `default-sandbox`. Every organization is provisioned with exactly one sandbox and one production project automatically — isolation between workloads comes from separate organizations, not additional projects. The project ID is what you'll need in the next step: -```json +```json title="response.json" { "data": { "projects": [ @@ -80,7 +80,7 @@ With the Admin key, call `GET /v1/projects` and pick the project with slug `defa Call `POST /v1/projects/{projectId}/api-keys` twice to mint two project-scoped keys: one with `role: "api_developer"` for routine create, mint, and burn calls, and one with `role: "api_admin"` for compliance operations (screening, freeze, seize, force-burn). Each response includes the full key value exactly once: -```json +```json title="response.json" { "data": { "apiKey": { @@ -138,7 +138,7 @@ The canonical SDP API URL is `https://api.solana.com`. Use this hostname for eve Wire two HTTP clients (one per project-scoped key) using a thin wrapper around `fetch`: -```javascript +```javascript title="sdp-client.js" const BASE = "https://api.solana.com"; class SdpClient { @@ -173,14 +173,14 @@ Use `sdp` for routine operations (create, mint, burn) and `sdpAdmin` for complia Now confirm the setup by listing the wallets bound to your project: -```javascript +```javascript title="list-wallets.js" const { data } = await sdp.get("/v1/wallets"); console.log(`Custody wallets configured: ${data.wallets.length}`); ``` A working setup prints: -``` +```text title="Output" Custody wallets configured: 2 ``` @@ -226,7 +226,7 @@ You can delegate or revoke any of these later via `POST /v1/issuance/tokens/{id} Section 4 takes one input: the configuration object representing the design choices above. For the Treasury Pilot USD example this tutorial uses end-to-end: -```javascript +```javascript title="token-config.js" const tokenConfig = { template: "stablecoin", name: "Treasury Pilot USD", @@ -249,7 +249,7 @@ Three API calls take a design from object literal to a deployed regulated stable Every state-mutating execute endpoint in SDP (deploy, mint, burn, seize, force-burn, authority) supports an `Idempotency-Key` HTTP header. A retry with the same key replays the original response. A retry with a different key is a new request. The header is how you make network-level retries safe under partial failure. Replace the `SdpClient` definition from Section 2 with this version: -```javascript +```javascript title="sdp-client.js" class SdpClient { constructor(apiKey, baseUrl = BASE) { this.apiKey = apiKey; @@ -288,14 +288,14 @@ Generate one key per logical operation, not per retry. Create the key once befor The first call writes the token's metadata to SDP's database. Nothing happens on Solana yet. The token starts in `status: "pending"` with no `mintAddress`: -```javascript +```javascript title="create-token.js" const created = await sdp.post("/v1/issuance/tokens", tokenConfig); const tokenId = created.data.token.id; ``` A successful response: -```json +```json title="response.json" { "data": { "token": { @@ -320,7 +320,7 @@ Save the `tok_*` ID; the deploy call uses it. The deploy call submits a Token-2022 mint creation transaction to Solana via the custody signer, and updates the token record with the resulting on-chain addresses: -```javascript +```javascript title="deploy-token.js" const deployKey = idempotencyKey("deploy"); const deployed = await sdp.post( `/v1/issuance/tokens/${tokenId}/deploy`, @@ -332,7 +332,7 @@ const mintAddress = deployed.data.token.mintAddress; A successful response carries the deployed token record with `status: "active"` and the on-chain `mintAddress` populated: -```json +```json title="response.json" { "data": { "token": { @@ -361,7 +361,7 @@ Deploy can return a bare `500 INTERNAL_ERROR` with no `details` field. The most -```javascript +```javascript title="deploy-prepare.js" const prepared = await sdp.post( `/v1/issuance/tokens/${tokenId}/deploy/prepare`, {} @@ -371,7 +371,7 @@ console.log(prepared.data.simulation); `/prepare` builds the transaction and runs simulation against Solana without submitting. Its response surfaces the real error in `simulation.error`: -```json +```json title="response.json" { "data": { "preparedTransaction": { "serialized": "...", "blockhash": "..." }, @@ -391,7 +391,7 @@ This prepare-as-diagnostic pattern works for every endpoint that has a `/prepare Before moving to Section 5, confirm the on-chain state matches the record: -```javascript +```javascript title="confirm-deploy.js" const { data } = await sdp.get(`/v1/issuance/tokens/${tokenId}`); console.log(`Status: ${data.token.status}`); console.log(`Mint: ${data.token.mintAddress}`); @@ -399,7 +399,7 @@ console.log(`Mint: ${data.token.mintAddress}`); A working deploy prints: -``` +```text title="Output" Status: active Mint: 6V5bTuMsmXyhdY2Hj6VWQZsuacugACBmPqNtQgGtar8L ``` @@ -420,7 +420,7 @@ Section 4(a)(5) of the GENIUS Act treats a permitted payment stablecoin issuer a Every transfer destination and every allowlist entry gets screened before your application acts on it. One API call, one response. -```javascript +```javascript title="screen-address.js" const response = await sdpAdmin.post("/v1/compliance/address-screenings", { address: destinationAddress, network: "solana", @@ -442,7 +442,7 @@ On the sandbox response this tutorial cites, Range and Elliptic return numeric s Freezing halts one holder's account without touching any other holder. The freeze is on-chain, enforced by the Token-2022 program. SDP signs the freeze instruction with the freeze authority that was set at deploy; the on-chain program does the enforcement. -```javascript +```javascript title="freeze-account.js" const holderFreezeKey = idempotencyKey("freeze"); await sdpAdmin.post( `/v1/issuance/tokens/${tokenId}/freeze`, @@ -464,7 +464,7 @@ Section 7 demonstrates the resulting error: an HTTP 502 `SOLANA_RPC_ERROR` from Seize moves tokens out of a holder's account without their signature, via the **permanent delegate** authority set at deploy. The permanent delegate is a Token-2022 extension that grants a named authority the right to transfer or burn any holder's tokens regardless of holder signature. This is the technological capability the GENIUS Act requires you to have ready for lawful orders. The institutional use case is a court-ordered transfer or a sanctions matter where you must recover specific tokens to a controlled wallet. -```javascript +```javascript title="seize-tokens.js" const seizeKey = idempotencyKey("seize"); await sdpAdmin.post( `/v1/issuance/tokens/${tokenId}/seize`, @@ -486,7 +486,7 @@ The memo persists on the transaction record. It's the audit attribution your com Force-burn destroys tokens at a holder's account. Same authority as seize, different effect. Reach for it only when the tokens cannot be recovered to a controlled wallet, for example a self-custodied address whose key you cannot obtain. -```javascript +```javascript title="force-burn.js" const forceBurnKey = idempotencyKey("force-burn"); await sdpAdmin.post( `/v1/issuance/tokens/${tokenId}/force-burn`, @@ -518,7 +518,7 @@ Minting is how new supply enters circulation. For a regulated stablecoin, every Minting requires the `tokens:write` permission, so the Developer key (`sdp`) is the right credential: -```javascript +```javascript title="mint-tokens.js" const mintKey = idempotencyKey("mint"); const minted = await sdp.post( `/v1/issuance/tokens/${tokenId}/mint`, @@ -543,7 +543,7 @@ Three field rules to keep clear: A successful mint returns the transaction record and the token account that received the tokens: -```json +```json title="response.json" { "data": { "transaction": { @@ -580,14 +580,14 @@ The pattern that does not work is mint memos that don't reconcile cleanly to a r ### 4. Confirm the supply -```javascript +```javascript title="confirm-supply.js" const { data } = await sdp.get(`/v1/issuance/tokens/${tokenId}`); console.log(`Total supply: ${data.token.totalSupply}`); ``` Prints: -``` +```text title="Output" Total supply: 1000000 ``` @@ -601,7 +601,7 @@ Section 5's compliance controls only matter if they enforce. This section moves Transfers run on the `/v1/payments/transfers` endpoint family, separate from the issuance family that Section 4 used. The required permissions are `payments:write` and `wallets:read`, both of which the Developer key (`sdp`) carries: -```javascript +```javascript title="transfer-tokens.js" const { data } = await sdp.get("/v1/wallets"); const sourceWalletId = data.wallets[0].walletId; @@ -634,7 +634,7 @@ Three field rules to keep clear: A successful response includes the on-chain signature once the transaction confirms: -```json +```json title="response.json" { "data": { "transfer": { @@ -657,7 +657,7 @@ A successful response includes the on-chain signature once the transaction confi To demonstrate the on-chain block, freeze the destination's TPUSD account using the Admin key. Freeze requires `tokens:admin`, which the Developer key does not carry: -```javascript +```javascript title="freeze-destination.js" const transferFreezeKey = idempotencyKey("freeze"); await sdpAdmin.post( `/v1/issuance/tokens/${tokenId}/freeze`, @@ -677,7 +677,7 @@ The freeze records a frozen account in SDP's audit trail and applies the Token-2 Run the same transfer call. This time it fails: -```javascript +```javascript title="transfer-blocked.js" try { await sdp.post("/v1/payments/transfers", { source: sourceWalletId, @@ -693,7 +693,7 @@ try { The error envelope SDP returns: -```json +```json title="response.json" { "error": { "code": "SOLANA_RPC_ERROR", @@ -711,7 +711,7 @@ The institutional pattern is to write your error handler against the wrapped on- -```javascript +```javascript title="parse-error.js" function isOnChainCompliance(error) { return error.message?.includes("custom program error: 0x11"); } @@ -734,7 +734,7 @@ Token-2022's `0x11` is `AccountFrozen` (decimal 17). Treat it as a compliance ve For a production environment, unfreezing happens only after the underlying compliance review resolves. For sandbox testing, you can unfreeze immediately: -```javascript +```javascript title="unfreeze-account.js" await sdpAdmin.post(`/v1/issuance/tokens/${tokenId}/unfreeze`, { accountAddress: destinationAddress, }); diff --git a/apps/sdp-docs/content/docs/tutorials/tokenize-a-treasury-fund.mdx b/apps/sdp-docs/content/docs/tutorials/tokenize-a-treasury-fund.mdx index 1675d40cf..1aeee0f2e 100644 --- a/apps/sdp-docs/content/docs/tutorials/tokenize-a-treasury-fund.mdx +++ b/apps/sdp-docs/content/docs/tutorials/tokenize-a-treasury-fund.mdx @@ -66,7 +66,7 @@ Hit [faucet.solana.com](https://faucet.solana.com) on Devnet, paste the wallet's Wire two HTTP clients (Developer + Admin) against `https://api.solana.com` with Bearer auth. -```javascript +```javascript title="sdp-client.js" const BASE = "https://api.solana.com"; class SdpError extends Error { @@ -116,7 +116,7 @@ const sdpAdmin = new SdpClient(process.env.SDP_ADMIN_API_KEY); Confirm the setup by listing wallets: -```javascript +```javascript title="list-wallets.js" const { data } = await sdp.get("/v1/wallets"); console.log(`Custody wallets configured: ${data.wallets.length}`); ``` @@ -150,7 +150,7 @@ SDP does not expose Token-2022 transfer hooks. The permanent-delegate + frozen-b Here is the create call: -```javascript +```javascript title="create-token.js" const { data } = await sdp.post("/v1/issuance/tokens", { template: "tokenized-security", name: "Pilot Treasury Fund", @@ -176,7 +176,7 @@ console.log(token.id, token.status); The response echoes the auto-set defaults so a design-time compliance review can confirm them: -```json +```json title="response.json" { "data": { "token": { @@ -212,7 +212,7 @@ Every state-mutating execute endpoint in SDP supports an `Idempotency-Key` HTTP The header is how you make network-level retries safe for irreversible operations like deploy, mint, burn, seize, and force-burn. Extend the `SdpClient` from Section 2 (keep the `SdpError` class declared there; only the `SdpClient` body changes): -```javascript +```javascript title="sdp-client.js" import { randomUUID } from "node:crypto"; class SdpClient { @@ -257,7 +257,7 @@ Generate the key into a `const` once per operation, before the call. If a retry Every execute endpoint with an irreversible side effect has a sibling `/prepare` endpoint that simulates the on-chain transaction without sending it. Prepare returns `simulation.success`, `simulation.error`, and `simulation.logs`: the human-readable Token-2022 program output a Solana node would emit. When execute returns an opaque 500, prepare with the same body surfaces the actual on-chain error in `simulation.error` and the failing instruction in `simulation.logs`. The pattern generalizes: every execute endpoint in SDP that has a `/prepare` sibling supports this diagnostic flow, and Sections 6 and 7 reuse it. -```javascript +```javascript title="deploy-prepare.js" const tokenId = ""; const prepared = await sdp.post( @@ -276,7 +276,7 @@ console.log(`Simulation passed: ${simulation.unitsConsumed} CU`); A clean simulation: -```json +```json title="response.json" { "data": { "simulation": { @@ -293,7 +293,7 @@ A clean simulation: With prepare clean, the execute call is the actual on-chain transaction. Pass an `Idempotency-Key` so that if the network drops the response and you retry, you replay the original deploy result rather than producing a second on-chain transaction. -```javascript +```javascript title="deploy-token.js" const deployKey = idempotencyKey("deploy"); const deployed = await sdp.post( `/v1/issuance/tokens/${tokenId}/deploy`, @@ -307,7 +307,7 @@ console.log(token.mintAddress, token.status); The response carries the four on-chain authorities and the live mint: -```json +```json title="response.json" { "data": { "token": { @@ -370,7 +370,7 @@ SDP enforces compliance in two layers. The allowlist is your KYC'd cap table for this token. Your transfer agent feeds approved wallet addresses into it; you maintain the list on-chain. -```js +```js title="add-allowlist.js" await sdp.post(`/v1/issuance/tokens/${tokenId}/allowlist`, { address: holderAddress, label: "Institutional Holder A", @@ -391,7 +391,7 @@ Compliance frameworks expect cap-table revocation, not destructive deletion. Use Your transfer agent runs the primary KYC pipeline. Your pre-allowlist screening is defense in depth, catching addresses the transfer agent's process might miss. -```js +```js title="screen-address.js" await sdp.post("/v1/compliance/address-screenings", { address: holderAddress, network: "solana", @@ -407,7 +407,7 @@ As of 2026-05-22, Chainalysis credentials are broken in production sandbox and t **Freeze** is the surgical lever. It halts on-chain operations against a single investor's token account. Use it for sanctioned holders, court-ordered holds, or short-term incident response. -```js +```js title="freeze-account.js" await sdpAdmin.post(`/v1/issuance/tokens/${tokenId}/freeze`, { accountAddress: holderAddress, reason: "Court order 2026-CIV-1284 - account hold pending review", @@ -442,7 +442,7 @@ The `pausable` extension's authority lives outside this enum and routes to your Update an authority with `POST /v1/issuance/tokens/{tokenId}/authority`. If a key is compromised, revoke the role permanently: -```js +```js title="revoke-authority.js" await sdpAdmin.post(`/v1/issuance/tokens/${tokenId}/authority`, { authority: { role: "mint", newAuthority: null }, }); @@ -460,7 +460,7 @@ You have an allowlisted holder with a zero balance. Two operations move them thr When the holder's fiat lands in your custody bank, treasury operations computes the token quantity at the published NAV and mints to the holder's allowlisted address. SDP derives the holder's Associated Token Account (ATA) from the holder address and the mint, and deposits the new supply there. The `memo` field is optional in the API (max 256 chars) but it is the audit-trail anchor; always include one tied to your internal subscription identifier. -```javascript +```javascript title="mint-subscription.js" const subscriptionKey = idempotencyKey("mint-subscription"); const subscription = await sdp.post( `/v1/issuance/tokens/${tokenId}/mint`, @@ -480,7 +480,7 @@ console.log(transaction.id, transaction.status, tokenAccount); The response carries the transaction record and the ATA address: -```json +```json title="response.json" { "data": { "transaction": { @@ -509,7 +509,7 @@ Yield accrues daily off-chain in your treasury accounting system. Monthly distri For a holder with a 1000 PTFND subscription and a published rate of 4.5% APY: -```javascript +```javascript title="mint-dividend.js" // Compute in integer cents and basis points — float math on money // (e.g. 1000 * 0.045 / 12) accumulates rounding artifacts. const holderCents = 1000 * 100; @@ -538,7 +538,7 @@ The pro rata calculation runs in treasury operations. SDP mints the calculated a Pull the transactions history to confirm both records landed with their memos. The endpoint is paginated; a complete audit-trail fetch iterates until `meta.hasMore` is `false`. -```javascript +```javascript title="fetch-transactions.js" async function fetchAllTransactions(tokenId) { const all = []; let page = 1; @@ -570,7 +570,7 @@ You have a holder with 1003.75 PTFND from Section 6. Now exercise the failure su A compliance event has occurred: the screening provider flags the holder, the transfer agent requests a hold, or a court order arrives. The Admin client issues the freeze. SDP derives the holder's ATA from the wallet address you pass in and freezes it at the Token-2022 layer. -```javascript +```javascript title="freeze-account.js" const frozen = await sdpAdmin.post( `/v1/issuance/tokens/${tokenId}/freeze`, { @@ -585,7 +585,7 @@ console.log(frozenAccount.id, frozenAccount.accountAddress, frozenAccount.signat The `reason` field is your audit anchor. Tie it to a legal or operational identifier (court order, OFAC SDN match, internal incident ticket) so the compliance officer can trace each freeze back to its basis. The response carries the `frz_*` id and the derived ATA address: -```json +```json title="response.json" { "data": { "frozenAccount": { @@ -607,7 +607,7 @@ The same `frz_*` id is reused if you refreeze later; SDP tracks one record per h Try to deliver the next monthly dividend. With the ATA frozen, the mint should not land. Use the same `sdp.post` shape from Section 6. -```javascript +```javascript title="mint-blocked.js" const blockedMintKey = idempotencyKey("mint-blocked"); try { await sdp.post( @@ -632,7 +632,7 @@ The response is HTTP 500 with `error.code: "INTERNAL_ERROR"` and `error.message: Call `/mint/prepare` with the same body. Prepare simulates the transaction without executing and returns the on-chain reason the execute call hid. -```javascript +```javascript title="mint-prepare.js" const diagnosis = await sdp.post( `/v1/issuance/tokens/${tokenId}/mint/prepare`, { @@ -651,7 +651,7 @@ simulation.logs.forEach(log => console.log(" " + log)); The diagnostic response carries everything execute swallowed: -```json +```json title="response.json" { "data": { "simulation": { @@ -675,7 +675,7 @@ This is the runbook step. When execute returns opaque, call its prepare sibling The compliance event is resolved. Issue the unfreeze and retry the mint. -```javascript +```javascript title="unfreeze-account.js" const unfrozen = await sdpAdmin.post( `/v1/issuance/tokens/${tokenId}/unfreeze`, { accountAddress: holderAddress } diff --git a/apps/sdp-docs/content/docs/wallet-operations/balances.mdx b/apps/sdp-docs/content/docs/wallet-operations/balances.mdx index b76198e06..253c03d19 100644 --- a/apps/sdp-docs/content/docs/wallet-operations/balances.mdx +++ b/apps/sdp-docs/content/docs/wallet-operations/balances.mdx @@ -9,13 +9,13 @@ description: Per-wallet token balances with optional USD valuation via SDP. -```bash +```bash title="Terminal" curl https://api.solana.com/v1/payments/wallets/wal_abc123/balances \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="get-wallet-balances.ts" const response = await fetch( "https://api.solana.com/v1/payments/wallets/wal_abc123/balances", { headers: { Authorization: "Bearer sk_test_..." } } @@ -25,7 +25,7 @@ const { data } = await response.json(); ``` -```java +```java title="GetWalletBalances.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/payments/wallets/wal_abc123/balances")) .header("Authorization", "Bearer sk_test_...") @@ -39,7 +39,7 @@ HttpRequest request = HttpRequest.newBuilder() The payload is wrapped in the standard `data` / `meta` envelope, with the balances under a `walletBalances` key: -```json +```json title="response.json" { "data": { "walletBalances": { diff --git a/apps/sdp-docs/content/docs/wallet-operations/index.mdx b/apps/sdp-docs/content/docs/wallet-operations/index.mdx index 28a44e299..097a3efec 100644 --- a/apps/sdp-docs/content/docs/wallet-operations/index.mdx +++ b/apps/sdp-docs/content/docs/wallet-operations/index.mdx @@ -18,4 +18,4 @@ The current wallet-policy and balance endpoints remain under `/v1/payments/walle - [Set up wallets](/docs/guides/setup-wallets) — provision the wallet before configuring controls. - [Payments](/docs/payments) — payment and ramp flows that can use a policy-controlled wallet. -- [Manage API keys](/docs/guides/manage-api-keys) — configure caller permissions separately from wallet operation permissions. +- [Manage API keys](/docs/developing-with-sdp/manage-api-keys) — configure caller permissions separately from wallet operation permissions. diff --git a/apps/sdp-docs/content/docs/wallet-operations/policies.mdx b/apps/sdp-docs/content/docs/wallet-operations/policies.mdx index ad9dd727d..7b3ec738c 100644 --- a/apps/sdp-docs/content/docs/wallet-operations/policies.mdx +++ b/apps/sdp-docs/content/docs/wallet-operations/policies.mdx @@ -11,13 +11,13 @@ Use policies for treasury wallets, automated payout wallets, or any custody wall -```bash +```bash title="Terminal" curl https://api.solana.com/v1/payments/wallets/wal_abc123/policies \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="get-wallet-policies.ts" const response = await fetch( "https://api.solana.com/v1/payments/wallets/wal_abc123/policies", { headers: { Authorization: "Bearer sk_test_..." } } @@ -34,7 +34,7 @@ const { data } = await response.json(); The payload is wrapped in the standard `data` / `meta` envelope, with the policy under a `policy` key: -```json +```json title="response.json" { "data": { "policy": { @@ -115,7 +115,7 @@ Wallet policies can match operations at two levels: - **Operation family** rules apply to every evaluated operation in a broad product area. - **Operation type** rules match one exact operation identifier and take precedence when you need a narrower decision. -These are wallet-policy selectors, not API-key permissions. API-key permissions such as `payments:write` and `tokens:write` determine whether a caller may invoke an endpoint; operation permissions determine what the selected wallet may do after the request is authorized. See [Manage API keys](/docs/guides/manage-api-keys) for caller permissions. +These are wallet-policy selectors, not API-key permissions. API-key permissions such as `payments:write` and `tokens:write` determine whether a caller may invoke an endpoint; operation permissions determine what the selected wallet may do after the request is authorized. See [Manage API keys](/docs/developing-with-sdp/manage-api-keys) for caller permissions. ### Operation families @@ -185,7 +185,7 @@ Set both for defense in depth: a per-transaction cap that catches obvious mistak -```bash +```bash title="Terminal" curl -X PUT https://api.solana.com/v1/payments/wallets/wal_abc123/policies \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -200,7 +200,7 @@ curl -X PUT https://api.solana.com/v1/payments/wallets/wal_abc123/policies \ ``` -```typescript +```typescript title="update-wallet-policies.ts" await fetch( "https://api.solana.com/v1/payments/wallets/wal_abc123/policies", { diff --git a/apps/sdp-docs/mdx-components.tsx b/apps/sdp-docs/mdx-components.tsx index 8b74762a9..000f29897 100644 --- a/apps/sdp-docs/mdx-components.tsx +++ b/apps/sdp-docs/mdx-components.tsx @@ -1,5 +1,6 @@ import { Tab, Tabs } from "fumadocs-ui/components/tabs"; import defaultMdxComponents from "fumadocs-ui/mdx"; +import { ArrowUpRightIcon } from "lucide-react"; import type React from "react"; import { DocsHome } from "@/components/docs-shell/home"; import { HowItWorks, Step, StepPanel } from "@/components/docs-shell/how-it-works"; @@ -39,8 +40,29 @@ function MDXParagraph({ className, ...props }: React.ComponentPropsWithoutRef<"p return

; } -function MDXLink({ className, ...props }: React.ComponentPropsWithoutRef<"a">) { - return ; +function MDXLink({ className, href, children, ...props }: React.ComponentPropsWithoutRef<"a">) { + const isExternal = typeof href === "string" && href.startsWith("http"); + + if (isExternal) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); } function MDXUnorderedList({ className, ...props }: React.ComponentPropsWithoutRef<"ul">) { diff --git a/apps/sdp-docs/public/images/partners/alchemy.svg b/apps/sdp-docs/public/images/partners/alchemy.svg new file mode 100644 index 000000000..083f868bf --- /dev/null +++ b/apps/sdp-docs/public/images/partners/alchemy.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/anchorage.svg b/apps/sdp-docs/public/images/partners/anchorage.svg new file mode 100644 index 000000000..977a9f4fc --- /dev/null +++ b/apps/sdp-docs/public/images/partners/anchorage.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/bvnk.svg b/apps/sdp-docs/public/images/partners/bvnk.svg new file mode 100644 index 000000000..bf1f9d611 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/bvnk.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/chainalysis.svg b/apps/sdp-docs/public/images/partners/chainalysis.svg new file mode 100644 index 000000000..cee0f453f --- /dev/null +++ b/apps/sdp-docs/public/images/partners/chainalysis.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/coinbase.svg b/apps/sdp-docs/public/images/partners/coinbase.svg new file mode 100644 index 000000000..56e45e255 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/coinbase.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/dfns.svg b/apps/sdp-docs/public/images/partners/dfns.svg new file mode 100644 index 000000000..9a8e7f44a --- /dev/null +++ b/apps/sdp-docs/public/images/partners/dfns.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/sdp-docs/public/images/partners/elliptic.svg b/apps/sdp-docs/public/images/partners/elliptic.svg new file mode 100644 index 000000000..fa4c58734 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/elliptic.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/fireblocks.svg b/apps/sdp-docs/public/images/partners/fireblocks.svg new file mode 100644 index 000000000..9300e8756 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/fireblocks.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/helius.svg b/apps/sdp-docs/public/images/partners/helius.svg new file mode 100644 index 000000000..96506ab02 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/helius.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/ibm-haven.svg b/apps/sdp-docs/public/images/partners/ibm-haven.svg new file mode 100644 index 000000000..d88e299a6 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/ibm-haven.svg @@ -0,0 +1 @@ + diff --git a/apps/sdp-docs/public/images/partners/lightspark.svg b/apps/sdp-docs/public/images/partners/lightspark.svg new file mode 100644 index 000000000..ad540a5f9 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/lightspark.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/moneygram.svg b/apps/sdp-docs/public/images/partners/moneygram.svg new file mode 100644 index 000000000..cc6d8d5fb --- /dev/null +++ b/apps/sdp-docs/public/images/partners/moneygram.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/sdp-docs/public/images/partners/moonpay.svg b/apps/sdp-docs/public/images/partners/moonpay.svg new file mode 100644 index 000000000..03e16bc21 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/moonpay.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/muralpay.svg b/apps/sdp-docs/public/images/partners/muralpay.svg new file mode 100644 index 000000000..3529a533b --- /dev/null +++ b/apps/sdp-docs/public/images/partners/muralpay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/sdp-docs/public/images/partners/para.svg b/apps/sdp-docs/public/images/partners/para.svg new file mode 100644 index 000000000..0899eaa1f --- /dev/null +++ b/apps/sdp-docs/public/images/partners/para.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/privy.svg b/apps/sdp-docs/public/images/partners/privy.svg new file mode 100644 index 000000000..6d37a6f70 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/privy.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/quicknode.svg b/apps/sdp-docs/public/images/partners/quicknode.svg new file mode 100644 index 000000000..e0fa586f1 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/quicknode.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/range.svg b/apps/sdp-docs/public/images/partners/range.svg new file mode 100644 index 000000000..ec61dc0c3 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/range.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/stripe.svg b/apps/sdp-docs/public/images/partners/stripe.svg new file mode 100644 index 000000000..af09b91c0 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/stripe.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/triton.svg b/apps/sdp-docs/public/images/partners/triton.svg new file mode 100644 index 000000000..fa32ddff7 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/triton.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/trm.svg b/apps/sdp-docs/public/images/partners/trm.svg new file mode 100644 index 000000000..d8f40cf80 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/trm.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/turnkey.svg b/apps/sdp-docs/public/images/partners/turnkey.svg new file mode 100644 index 000000000..af8fe8a1b --- /dev/null +++ b/apps/sdp-docs/public/images/partners/turnkey.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/apps/sdp-docs/public/images/partners/utila.svg b/apps/sdp-docs/public/images/partners/utila.svg new file mode 100644 index 000000000..62a007246 --- /dev/null +++ b/apps/sdp-docs/public/images/partners/utila.svg @@ -0,0 +1,155 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/sdp-docs/public/llms-full.txt b/apps/sdp-docs/public/llms-full.txt index f91ca3184..2f589f97a 100644 --- a/apps/sdp-docs/public/llms-full.txt +++ b/apps/sdp-docs/public/llms-full.txt @@ -7,329 +7,199 @@ - API: https://api.solana.com - Interactive API docs: https://api.solana.com/docs - OpenAPI: https://api.solana.com/openapi.json -- AI guide: https://platform.solana.com/docs/reference/ai-consumption +- AI guide: https://platform.solana.com/docs/reference/docs-for-ai ## Docs -### Solana Developer Platform Docs -Source: https://platform.solana.com/docs/home - -> A dashboard and REST API for real-world asset issuance, payments, and markets on Solana — with built-in compliance controls. - - - ---- - -### Introduction +### What is SDP? Source: https://platform.solana.com/docs/introduction -> What SDP is, what it provides, and how it works. - -Solana Developer Platform (SDP) is a dashboard and REST API for real-world asset (RWA) issuance, payments, and markets on Solana. It handles wallet custody, onchain transaction construction, and compliance operations so your team can bring regulated assets onchain without managing keys or setting up Solana infrastructure. - -## What you get - -**Token lifecycle management** — Create tokens from pre-built templates (stablecoin, tokenized security) or configure your own using Token-2022 extensions. Deploy, mint, burn, and manage supply through the dashboard or API. +> Putting institutional-grade capital markets at your fingertips. Issue, move, and manage regulated assets on Solana. -**Wallet custody** — Provision signing wallets through integrated custody providers. SDP manages the signing infrastructure so your team never handles private keys directly. +## Background -**Projects and scoped access** — Organize integrations by project, assign members, and issue project-scoped API keys so teams or environments do not share the same wallet and token surface. +Institutional capital is moving onchain. As of July 2026, tokenized real-world assets total roughly [$35 billion across public blockchains](https://app.rwa.xyz/), alongside nearly $300 billion in stablecoins. On Solana that includes about [$3.2 billion in tokenized assets](https://app.rwa.xyz/networks/solana) and a $15.8 billion stablecoin supply that settled over [$560 billion in transfers](https://solana.com/data) in the past 30 days. -**Compliance operations** — Freeze individual accounts, pause token activity, seize tokens, and manage allowlists for regulated issuance flows. Selected compliance actions accept reason or memo fields for audit trails. +Tokenized stocks are the fastest-growing slice of that market. Onchain equities now total [roughly $1.9 billion](https://app.rwa.xyz/stocks), and Solana settles [about 95% of all tokenized stock trading](https://cryptobriefing.com/solana-tokenized-stocks-volume-surges-h1-2026/), with $4.9 billion in volume in the first half of 2026 alone. Names like Micron, SanDisk, Tesla, and Apple already trade onchain, alongside tokenized ETFs like the iShares Core S&P 500. -**Two signing modes** — SDP supports both modes for transaction-building flows such as issuance, payments, and compliance actions: +Assets at this scale need the same guarantees traditional markets provide: regulated custody, enforceable compliance, and auditable operational controls. Building that from raw Solana primitives means standing up wallet infrastructure, transaction construction, and compliance tooling before shipping anything. -- **Execute** — SDP builds, signs, and submits the transaction in one API call -- **Prepare** — SDP builds the transaction and returns it unsigned. You sign with your own infrastructure (hardware wallet, multisig, internal HSM) and submit it yourself. - -**Transaction simulation** — Prepare endpoints accept a `simulate: true` option that dry-runs the transaction before you sign, returning compute units consumed, program logs, and any errors. - -**Scoped API keys** — Generate keys with specific permission sets (`tokens:read`, `tokens:write`, `tokens:admin`, etc.) and rotate them without downtime. - -## Architecture +And there is more to be done: most of the world's capital still sits offchain. SDP aims to attract it by providing simplified programming models through a unified application layer for issuance, ramps, transfers, and compliance operations, so you can start without needing to maintain a huge team of Solana developers. -SDP exposes two interfaces that connect to the same backend: - -- **Dashboard** — browser-based UI for setup, token management, and compliance operations. Sign in with email, Google, or GitHub. -- **REST API** — programmatic access for backend integrations. Authenticate with an API key in the `Authorization` header. +## Core Features -## Environments - -| Environment | API key prefix | Solana network | Purpose | -| ----------- | -------------- | -------------- | ------------------------------------------- | -| Sandbox | `sk_test_` | devnet | Development and testing with no real assets | -| Production | `sk_live_` | mainnet-beta | Live operations with real assets | - -Both environments expose identical APIs. Develop and test against sandbox, then switch to production by swapping your API key. - ---- - -## Getting Started - -### Set Up Your Organization -Source: https://platform.solana.com/docs/guides/setup-organization - -> Create and configure your SDP organization through the dashboard. + + +### Unified Programming Interface -An organization is the top-level container for all your SDP resources — projects, wallets, API keys, and tokens. Every API request is scoped to an organization. +SDP puts one stable API in front of the Solana ecosystem's providers, so you integrate once and swap or add providers without changing your code. The same onramp call works for every ramp provider. The `provider` field is the only thing that changes. -### 1. Sign up + + +```javascript title="onramp-quote.js" rotate-providers +const response = await fetch( + "https://api.solana.com/v1/payments/ramps/onramp/quote", + { + method: "POST", + headers: { + Authorization: "Bearer sk_test_...", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + provider: "moonpay", + counterpartyId: "cp_...", + destinationWallet: "wal_...", + cryptoToken: "USDC", + fiatCurrency: "USD", + fiatAmount: "100.00", + }), + }, +); +const quote = await response.json(); +``` + + +```python title="onramp_quote.py" rotate-providers + +response = requests.post( + "https://api.solana.com/v1/payments/ramps/onramp/quote", + headers={"Authorization": "Bearer sk_test_..."}, + json={ + "provider": "moonpay", + "counterpartyId": "cp_...", + "destinationWallet": "wal_...", + "cryptoToken": "USDC", + "fiatCurrency": "USD", + "fiatAmount": "100.00", + }, +) +quote = response.json() +``` + + +```bash title="Terminal" rotate-providers +curl -X POST https://api.solana.com/v1/payments/ramps/onramp/quote \ + -H "Authorization: Bearer sk_test_..." \ + -H "Content-Type: application/json" \ + -d '{ + "provider": "moonpay", + "counterpartyId": "cp_...", + "destinationWallet": "wal_...", + "cryptoToken": "USDC", + "fiatCurrency": "USD", + "fiatAmount": "100.00" + }' +``` + + + + +### Wallet Custody -Go to [platform.solana.com](https://platform.solana.com) and click **Dashboard**. On the sign-in screen, click **Sign up** to create a new account. +Provision signing wallets through integrated custody providers, so your team never handles private keys. Hosted keys never leave the provider's MPC or TEE infrastructure and SDP only handles the signed transaction. Local development keys are encrypted at rest with `AES-256-GCM` using per-organization derived keys, and every signing request passes policy and permission checks before it executes. -![SDP sign-in screen with Sign up link](/images/getting-started/sign-in.png) +1. **Built on `@solana/keychain`** -Enter your email address and a password to create your account. + The unified Solana signing interface. Every custody backend is a keychain adapter behind the same signing API, which is what makes providers interchangeable. -![Create your account screen](/images/getting-started/create-account.png) +2. **[Wallet policies](/docs/wallet-operations/policies)** -### 2. Create or select an organization + Constrain how a wallet can move funds. Destination controls, amount limits, operation permissions, approval requirements, and audit visibility, enforced before any operation executes. -After signing in you'll be prompted to set up your organization. Upload a logo and enter your organization name, then click **Continue**. +3. **Scoped custody configuration** -![Setup your organization screen](/images/getting-started/setup-organization.png) + Custody settings resolve per project, then organization, then environment, so different teams can run different providers side by side. + + +### Audit Trail -If you already have an organization, the **Organization Switcher** in the top-left of the sidebar lets you switch between them or create a new one. +Every API key lifecycle event and every wallet signing operation, from token deploys and mints to freezes and transfers, is recorded automatically. Each entry captures the acting user or API key, client IP, request id, and redacted metadata, and is queryable per organization. + + +### Compliance operations -Organization Switcher dropdown +Freeze individual accounts, pause token activity, seize tokens, and manage allowlists for regulated issuance flows. Selected compliance actions accept reason or memo fields for audit trails. + + +### Two signing modes -### 3. Next steps +SDP supports both modes for transaction-building flows such as issuance, payments, and compliance actions: -- [Set up wallets](/docs/guides/setup-wallets) for signing transactions -- [Create API keys](/docs/guides/manage-api-keys) for programmatic access -- [Projects API reference](/docs/reference/api/projects) when you want to segment apps, tenants, or environments within the organization -- [Create your first token](/docs/guides/create-a-token) +- **Execute** — SDP builds, signs, and submits the transaction in one API call +- **Prepare** — SDP builds the transaction and returns it unsigned. You sign with your own infrastructure (hardware wallet, multisig, internal HSM) and submit it yourself. Prepare endpoints accept a `simulate: true` option that dry-runs the transaction before you sign, returning compute units consumed, program logs, and any errors. + + --- -### Set Up Wallets -Source: https://platform.solana.com/docs/guides/setup-wallets - -> Initialize a custody provider and create wallets for signing transactions. - -Wallets are Solana keypairs managed by a custody provider. SDP uses them to sign transactions for token deployment, minting, transfers, and other onchain operations. You must initialize a signing provider before creating tokens or executing transactions. - -## Supported providers - -| Provider | Description | -| --- | --- | -| **Privy** | Embedded wallet infrastructure platform | -| **Fireblocks** | Digital asset infrastructure company | -| **Coinbase CDP** | Embedded wallets for developers | -| **Para** | Wallet and authentication suite | -| **Turnkey** | Non-custodial wallet infrastructure platform | -| **DFNS** | Digital asset wallet infrastructure | -| **Anchorage** | Regulated institutional crypto custody | - - - - -### 1. Navigate to Wallets - -Open the sidebar and click **Wallets**. If you haven't linked your organization yet, complete the [organization setup](/docs/guides/setup-organization) first. - -### 2. Choose a custody provider +## Developing with SDP -The Wallets page shows all supported providers, each with a **New wallet** button. Pick the provider that fits your setup. +### Overview +Source: https://platform.solana.com/docs/developing-with-sdp -![Wallets page showing custody provider cards](/images/getting-started/wallet-providers.png) +> The interfaces, environments, and conventions you build against. -### 3. Create the wallet +SDP exposes two interfaces that connect to the same backend: -Click **New wallet** on your chosen provider. In the modal, enter a label for the wallet and click **Create wallet**. +- **Dashboard** — browser-based UI for setup, token management, and compliance operations. Sign in with email, Google, or GitHub. +- **REST API** — programmatic access for backend integrations. Authenticate with an API key in the `Authorization` header. -New wallet modal +Everything the dashboard does is available through the API. Build against the API for automation and integrations; use the dashboard to set up your organization, inspect state, and perform one-off operations. -### 4. View your wallet +## Environments -After provisioning, the wallet card appears on the Wallets page with its address, wallet ID, and balance. +| Environment | Network | API key prefix | +| ------------ | ------------ | -------------- | +| `sandbox` | devnet | `sk_test_` | +| `production` | mainnet-beta | `sk_live_` | -![Wallets page after wallet creation](/images/getting-started/wallet-created.png) +Both environments expose identical APIs. Develop and test against sandbox, then switch to production by swapping your API key. -To create additional wallets, click **Create Wallet** in the top right and repeat the process. +## Signing modes -### 5. Provider capabilities +Most transaction endpoints support Execute (SDP signs and submits) or Prepare (SDP builds the transaction, you sign). See [Prepare vs Execute](/docs/guides/prepare-vs-execute). -Each provider supports a different set of operations shown as capability chips: +--- -- **Issuance** — can deploy and mint tokens -- **Transfers** — can sign payment transactions -- **Compliance** — can sign freeze/unfreeze instructions +### Idempotency +Source: https://platform.solana.com/docs/developing-with-sdp/idempotency - - +> Prevent duplicate operations by sending an Idempotency-Key header on mutation requests. -#### 1. Initialize the signing provider +Include an `Idempotency-Key` header on mutation requests to prevent duplicates. If a request times out or your client retries, replaying the same key returns the original result instead of performing the operation twice. - - -```bash -curl -X POST https://api.solana.com/v1/wallets/initialize \ +```bash title="Terminal" +curl -X POST https://api.solana.com/v1/payments/transfers \ -H "Authorization: Bearer sk_test_..." \ + -H "Idempotency-Key: 8a2f6c1e-9d4b-4f3a-b7e5-2c8d9f0a1b3c" \ -H "Content-Type: application/json" \ - -d '{ - "provider": "privy", - "walletLabel": "Master wallet" - }' + -d '{ ... }' ``` - - -```typescript -const response = await fetch("https://api.solana.com/v1/wallets/initialize", { - method: "POST", - headers: { - "Authorization": "Bearer sk_test_...", - "Content-Type": "application/json", - }, - body: JSON.stringify({ - provider: "privy", - walletLabel: "Master wallet", - }), -}); -const { data } = await response.json(); -// data: { configId, publicKey, walletId } -``` - - -```java -HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://api.solana.com/v1/wallets/initialize")) - .header("Authorization", "Bearer sk_test_...") - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(""" - { - "provider": "privy", - "walletLabel": "Master wallet" - }""")) - .build(); -``` - - -#### 2. Create additional wallets +Use a unique key per logical operation (a UUID works well) and reuse the same key for retries of that operation. - - -```bash -curl -X POST https://api.solana.com/v1/wallets \ - -H "Authorization: Bearer sk_test_..." \ - -H "Content-Type: application/json" \ - -d '{ - "label": "Mint authority wallet", - "purpose": "mint_authority", - "setDefault": false - }' -``` - - -```typescript -const response = await fetch("https://api.solana.com/v1/wallets", { - method: "POST", - headers: { - "Authorization": "Bearer sk_test_...", - "Content-Type": "application/json", - }, - body: JSON.stringify({ - label: "Mint authority wallet", - purpose: "mint_authority", - setDefault: false, - }), -}); -``` - - -```java -HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://api.solana.com/v1/wallets")) - .header("Authorization", "Bearer sk_test_...") - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(""" - { - "label": "Mint authority wallet", - "purpose": "mint_authority", - "setDefault": false - }""")) - .build(); -``` - - +--- -The `purpose` field is optional metadata in the API. The current dashboard create-wallet flow does not expose wallet-purpose selection. +### Authentication +Source: https://platform.solana.com/docs/developing-with-sdp/authentication -#### 3. List wallets +> Authenticate API requests with project-scoped API keys. - - -```bash -curl "https://api.solana.com/v1/wallets?view=summary" \ +Every API request carries an API key in the `Authorization` header: + +```bash title="Terminal" +curl https://api.solana.com/v1/wallets \ -H "Authorization: Bearer sk_test_..." ``` - - -```typescript -const response = await fetch("https://api.solana.com/v1/wallets?view=summary", { - headers: { "Authorization": "Bearer sk_test_..." }, -}); -const { data } = await response.json(); -// data.wallets includes walletId, publicKey, provider, label, purpose, and status -``` - - -```java -HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://api.solana.com/v1/wallets?view=summary")) - .header("Authorization", "Bearer sk_test_...") - .GET() - .build(); -``` - - -Use the returned `walletId` when setting a default wallet or binding wallet-scoped API keys. - -#### 4. Set a default wallet - - - -```bash -curl -X POST https://api.solana.com/v1/wallets/default-wallet \ - -H "Authorization: Bearer sk_test_..." \ - -H "Content-Type: application/json" \ - -d '{ "walletId": "wal_xyz789" }' -``` - - -```typescript -await fetch("https://api.solana.com/v1/wallets/default-wallet", { - method: "POST", - headers: { - "Authorization": "Bearer sk_test_...", - "Content-Type": "application/json", - }, - body: JSON.stringify({ walletId: "wal_xyz789" }), -}); -``` - - -```java -HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://api.solana.com/v1/wallets/default-wallet")) - .header("Authorization", "Bearer sk_test_...") - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(""" - { "walletId": "wal_xyz789" }""")) - .build(); -``` - - +Key prefixes tell you which environment a key belongs to: `sk_test_` keys are sandbox, `sk_live_` keys are production. - - +Keys are project-scoped — a key can only reach the wallets, tokens, and payments of the project it was issued for. See [Manage API Keys](/docs/developing-with-sdp/manage-api-keys) for creating, rotating, and revoking keys. --- ### Manage API Keys -Source: https://platform.solana.com/docs/guides/manage-api-keys +Source: https://platform.solana.com/docs/developing-with-sdp/manage-api-keys > Create, rotate, and revoke API keys with role-based access and environment scoping. @@ -386,7 +256,7 @@ After dismissing, the key appears in the table with its prefix, role, environmen -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/api-keys \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -400,7 +270,7 @@ curl -X POST https://api.solana.com/v1/api-keys \ ``` -```typescript +```typescript title="create-api-key.ts" const response = await fetch("https://api.solana.com/v1/api-keys", { method: "POST", headers: { @@ -420,7 +290,7 @@ const { data } = await response.json(); ``` -```java +```java title="CreateApiKey.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/api-keys")) .header("Authorization", "Bearer sk_test_...") @@ -461,7 +331,7 @@ During the grace period both the old and new key are valid. The new key value ap -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/api-keys/key_abc123/rotate \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -469,7 +339,7 @@ curl -X POST https://api.solana.com/v1/api-keys/key_abc123/rotate \ ``` -```typescript +```typescript title="rotate-api-key.ts" const response = await fetch( "https://api.solana.com/v1/api-keys/key_abc123/rotate", { @@ -486,7 +356,7 @@ const { data } = await response.json(); ``` -```java +```java title="RotateApiKey.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/api-keys/key_abc123/rotate")) .header("Authorization", "Bearer sk_test_...") @@ -515,7 +385,7 @@ Open the **Actions** dropdown next to the key and click **Delete key**. The key -```bash +```bash title="Terminal" curl -X DELETE https://api.solana.com/v1/api-keys/key_abc123 \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -523,7 +393,7 @@ curl -X DELETE https://api.solana.com/v1/api-keys/key_abc123 \ ``` -```typescript +```typescript title="revoke-api-key.ts" await fetch("https://api.solana.com/v1/api-keys/key_abc123", { method: "DELETE", headers: { @@ -535,7 +405,7 @@ await fetch("https://api.solana.com/v1/api-keys/key_abc123", { ``` -```java +```java title="RevokeApiKey.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/api-keys/key_abc123")) .header("Authorization", "Bearer sk_test_...") @@ -554,6 +424,234 @@ The `confirmation` field must match the key's **name**. The key stops working im --- +## Getting Started + +### Set Up Wallets +Source: https://platform.solana.com/docs/guides/setup-wallets + +> Initialize a custody provider and create wallets for signing transactions. + +Wallets are Solana keypairs managed by a custody provider. SDP uses them to sign transactions for token deployment, minting, transfers, and other onchain operations. You must initialize a signing provider before creating tokens or executing transactions. + +## Supported providers + +| Provider | Description | +| --- | --- | +| **Privy** | Embedded wallet infrastructure platform | +| **Fireblocks** | Digital asset infrastructure company | +| **Coinbase CDP** | Embedded wallets for developers | +| **Para** | Wallet and authentication suite | +| **Turnkey** | Non-custodial wallet infrastructure platform | +| **DFNS** | Digital asset wallet infrastructure | +| **Anchorage** | Regulated institutional crypto custody | + + + + +### 1. Navigate to Wallets + +Open the sidebar and click **Wallets**. If you haven't linked your organization yet, complete the [organization setup](/docs/guides/setup-organization) first. + +### 2. Choose a custody provider + +The Wallets page shows all supported providers, each with a **New wallet** button. Pick the provider that fits your setup. + +![Wallets page showing custody provider cards](/images/getting-started/wallet-providers.png) + +### 3. Create the wallet + +Click **New wallet** on your chosen provider. In the modal, enter a label for the wallet and click **Create wallet**. + +New wallet modal + +### 4. View your wallet + +After provisioning, the wallet card appears on the Wallets page with its address, wallet ID, and balance. + +![Wallets page after wallet creation](/images/getting-started/wallet-created.png) + +To create additional wallets, click **Create Wallet** in the top right and repeat the process. + +### 5. Provider capabilities + +Each provider supports a different set of operations shown as capability chips: + +- **Issuance** — can deploy and mint tokens +- **Transfers** — can sign payment transactions +- **Compliance** — can sign freeze/unfreeze instructions + + + + +#### 1. Initialize the signing provider + + + +```bash title="Terminal" +curl -X POST https://api.solana.com/v1/wallets/initialize \ + -H "Authorization: Bearer sk_test_..." \ + -H "Content-Type: application/json" \ + -d '{ + "provider": "privy", + "walletLabel": "Master wallet" + }' +``` + + +```typescript title="initialize-wallet-provider.ts" +const response = await fetch("https://api.solana.com/v1/wallets/initialize", { + method: "POST", + headers: { + "Authorization": "Bearer sk_test_...", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + provider: "privy", + walletLabel: "Master wallet", + }), +}); +const { data } = await response.json(); +// data: { configId, publicKey, walletId } +``` + + +```java title="InitializeWalletProvider.java" +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://api.solana.com/v1/wallets/initialize")) + .header("Authorization", "Bearer sk_test_...") + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(""" + { + "provider": "privy", + "walletLabel": "Master wallet" + }""")) + .build(); +``` + + + +#### 2. Create additional wallets + + + +```bash title="Terminal" +curl -X POST https://api.solana.com/v1/wallets \ + -H "Authorization: Bearer sk_test_..." \ + -H "Content-Type: application/json" \ + -d '{ + "label": "Mint authority wallet", + "purpose": "mint_authority", + "setDefault": false + }' +``` + + +```typescript title="create-wallet.ts" +const response = await fetch("https://api.solana.com/v1/wallets", { + method: "POST", + headers: { + "Authorization": "Bearer sk_test_...", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + label: "Mint authority wallet", + purpose: "mint_authority", + setDefault: false, + }), +}); +``` + + +```java title="CreateWallet.java" +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://api.solana.com/v1/wallets")) + .header("Authorization", "Bearer sk_test_...") + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(""" + { + "label": "Mint authority wallet", + "purpose": "mint_authority", + "setDefault": false + }""")) + .build(); +``` + + + +The `purpose` field is optional metadata in the API. The current dashboard create-wallet flow does not expose wallet-purpose selection. + +#### 3. List wallets + + + +```bash title="Terminal" +curl "https://api.solana.com/v1/wallets?view=summary" \ + -H "Authorization: Bearer sk_test_..." +``` + + +```typescript title="list-wallets.ts" +const response = await fetch("https://api.solana.com/v1/wallets?view=summary", { + headers: { "Authorization": "Bearer sk_test_..." }, +}); +const { data } = await response.json(); +// data.wallets includes walletId, publicKey, provider, label, purpose, and status +``` + + +```java title="ListWallets.java" +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://api.solana.com/v1/wallets?view=summary")) + .header("Authorization", "Bearer sk_test_...") + .GET() + .build(); +``` + + + +Use the returned `walletId` when setting a default wallet or binding wallet-scoped API keys. + +#### 4. Set a default wallet + + + +```bash title="Terminal" +curl -X POST https://api.solana.com/v1/wallets/default-wallet \ + -H "Authorization: Bearer sk_test_..." \ + -H "Content-Type: application/json" \ + -d '{ "walletId": "wal_xyz789" }' +``` + + +```typescript title="set-default-wallet.ts" +await fetch("https://api.solana.com/v1/wallets/default-wallet", { + method: "POST", + headers: { + "Authorization": "Bearer sk_test_...", + "Content-Type": "application/json", + }, + body: JSON.stringify({ walletId: "wal_xyz789" }), +}); +``` + + +```java title="SetDefaultWallet.java" +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://api.solana.com/v1/wallets/default-wallet")) + .header("Authorization", "Bearer sk_test_...") + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(""" + { "walletId": "wal_xyz789" }""")) + .build(); +``` + + + + + + +--- + ## Tokens ### Tokenize an Asset @@ -580,9 +678,9 @@ Use SDP-controlled custody wallets for your main authorities when you expect SDP Each step links to its own guide with UI and API instructions: -1. [Set Up Your Organization](/docs/guides/setup-organization) +1. [Set Up Your Organization & Projects](/docs/guides/setup-organization) 2. [Set Up Wallets](/docs/guides/setup-wallets) -3. [Manage API Keys](/docs/guides/manage-api-keys) +3. [Manage API Keys](/docs/developing-with-sdp/manage-api-keys) 4. [Create a Token](/docs/tokens/create-a-token) 5. [Deploy a Token](/docs/tokens/deploy-a-token) 6. [Mint and Burn](/docs/tokens/mint-and-burn) @@ -615,7 +713,7 @@ See the full schema in the [Issuance API reference](/docs/reference/api/issuance ## For AI agents -If you are driving SDP through an agent workflow, start from [AI Consumption](/docs/reference/ai-consumption). That page links the machine-readable SDP discovery files and keeps AI-facing guidance grounded in the supported public API surface. +If you are driving SDP through an agent workflow, start from [Documentation for AI](/docs/reference/docs-for-ai). That page links the machine-readable SDP discovery files and keeps AI-facing guidance grounded in the supported public API surface. --- @@ -665,12 +763,12 @@ Select a template and click **Continue**. List available templates to see what fields each one pre-configures: -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/templates \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="list-templates.ts" const res = await fetch("https://api.solana.com/v1/issuance/templates", { headers: { Authorization: "Bearer sk_test_..." }, }); @@ -708,7 +806,7 @@ Click **Continue** when ready. Pass identity fields in the `POST /v1/issuance/tokens` body: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -749,7 +847,7 @@ Click **Create token** to save the draft. Add operational fields to the same body: -```typescript +```typescript title="create-token.ts" const res = await fetch("https://api.solana.com/v1/issuance/tokens", { method: "POST", headers: { @@ -771,7 +869,7 @@ const res = await fetch("https://api.solana.com/v1/issuance/tokens", { For a custom token, omit `template` or set it to `"custom"` and pass `overrides.extensions`: -```typescript +```typescript title="create-custom-token.ts" body: JSON.stringify({ name: "Game Coin", symbol: "GAME", @@ -815,7 +913,7 @@ Open the token to see its detail page. The status shows **Not deployed** and a * A successful response returns the new token object: -```typescript +```typescript title="create-token.ts" const { data } = await res.json(); // data.token.id — save this; used in all subsequent calls // data.token.status — "pending" @@ -868,7 +966,7 @@ The **Deploy** button appears in the token header and in the fund management pan You need the `tokenId` returned when you created the token. If you don't have it, list your tokens: -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/tokens \ -H "Authorization: Bearer sk_test_..." ``` @@ -904,13 +1002,13 @@ SDP submits the transaction to Solana and polls for confirmation. The page updat **Execute mode** — SDP signs and submits: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/deploy \ -H "Authorization: Bearer sk_test_..." \ -H "Idempotency-Key: deploy-acme-001" ``` -```typescript +```typescript title="deploy-token.ts" const res = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/deploy", { @@ -927,7 +1025,7 @@ const { data } = await res.json(); **Prepare mode** — returns an unsigned transaction: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/deploy/prepare \ -H "Authorization: Bearer sk_test_..." ``` @@ -959,14 +1057,14 @@ The token status badge changes from `Not deployed` to the onchain mint address. The deploy response includes the confirmed state: -```typescript +```typescript title="deploy-token.ts" // data.token.status — "active" // data.token.mintAddress — e.g., "AcMeXYZ..." ``` Fetch the token at any time to verify: -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/tokens/tok_abc123 \ -H "Authorization: Bearer sk_test_..." ``` @@ -1016,7 +1114,7 @@ The **Fund management** panel shows the current supply with two primary actions: All supply operations share this URL pattern: -``` +```text title="Endpoints" POST /v1/issuance/tokens/{tokenId}/mint POST /v1/issuance/tokens/{tokenId}/burn POST /v1/issuance/tokens/{tokenId}/force-burn @@ -1061,7 +1159,7 @@ Once confirmed, the mint appears in the Transactions table with a `confirmed` st -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/mint \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -1075,7 +1173,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/mint \ }' ``` -```typescript +```typescript title="mint-tokens.ts" const res = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/mint", { @@ -1139,7 +1237,7 @@ For **Force burn** (requires `tokens:admin`), use the Compliance tab instead of **Burn from a controlled account:** -```typescript +```typescript title="burn-tokens.ts" await fetch("https://api.solana.com/v1/issuance/tokens/tok_abc123/burn", { method: "POST", headers: { @@ -1155,7 +1253,7 @@ await fetch("https://api.solana.com/v1/issuance/tokens/tok_abc123/burn", { **Force-burn from any holder** (requires `tokens:admin`): -```typescript +```typescript title="force-burn-tokens.ts" await fetch("https://api.solana.com/v1/issuance/tokens/tok_abc123/force-burn", { method: "POST", headers: { @@ -1194,7 +1292,7 @@ Reload the token detail page if the displayed supply value appears stale. -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/supply/refresh \ -H "Authorization: Bearer sk_test_..." ``` @@ -1248,7 +1346,7 @@ The Compliance tab on the token detail page shows the allowlist management inter Set `requiresAllowlist: true` when creating the token: -```typescript +```typescript title="enable-allowlist.ts" body: JSON.stringify({ name: "Acme Security", symbol: "ACMES", @@ -1289,7 +1387,7 @@ After adding, the entry appears below the form with a **Remove entry** button. -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -1299,7 +1397,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist \ }' ``` -```typescript +```typescript title="add-allowlist-entry.ts" const res = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist", { @@ -1335,12 +1433,12 @@ Use the search field to find a specific address by label or public key. -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="list-allowlist.ts" const { data, meta } = await res.json(); // meta: { total, page, pageSize, hasMore } ``` @@ -1366,7 +1464,7 @@ In the **Compliance → Allowlist** tab, click **Remove entry** next to the addr -```bash +```bash title="Terminal" curl -X DELETE \ https://api.solana.com/v1/issuance/tokens/tok_abc123/allowlist/alw_abc123 \ -H "Authorization: Bearer sk_test_..." @@ -1424,7 +1522,7 @@ Enter the wallet address and select the intent (e.g., transfer destination or al -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/compliance/address-screenings \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -1435,7 +1533,7 @@ curl -X POST https://api.solana.com/v1/compliance/address-screenings \ }' ``` -```typescript +```typescript title="screen-address.ts" const { data } = await res.json(); // data.screening.providers[] // → { provider, status, riskScore, riskLevel, message, evaluatedAt } @@ -1486,7 +1584,7 @@ The "Unfreeze transaction finalized." toast appears and the Frozen Accounts coun **Freeze:** -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/freeze \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -1500,7 +1598,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/freeze \ **Unfreeze:** -```typescript +```typescript title="unfreeze-account.ts" await fetch("https://api.solana.com/v1/issuance/tokens/tok_abc123/unfreeze", { method: "POST", headers: { Authorization: "Bearer sk_test_...", "Content-Type": "application/json" }, @@ -1510,7 +1608,7 @@ await fetch("https://api.solana.com/v1/issuance/tokens/tok_abc123/unfreeze", { **List frozen accounts:** -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/tokens/tok_abc123/frozen \ -H "Authorization: Bearer sk_test_..." ``` @@ -1548,7 +1646,7 @@ To resume, click **Unpause token** and confirm. -```bash +```bash title="Terminal" # Pause curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/pause \ -H "Authorization: Bearer sk_test_..." @@ -1579,7 +1677,7 @@ In the **Compliance** tab, click **Seize tokens**. Provide the source, destinati -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/seize \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -1654,12 +1752,12 @@ The **Extensions** tab shows the template and each operational flag — allowlis Fetch the current token state: -```bash +```bash title="Terminal" curl https://api.solana.com/v1/issuance/tokens/tok_abc123 \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="get-token-settings.ts" const { data } = await res.json(); // data.token.authorities — { mintAuthority, freezeAuthority, permanentDelegate } // data.token.extensions — active extensions on the token @@ -1691,7 +1789,7 @@ On the token detail page, go to **Permissions**. Click **Edit** next to the auth **Execute mode** — SDP signs the authority rotation: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/authority \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -1706,7 +1804,7 @@ curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/authority \ **Prepare mode** — returns unsigned transaction for external signing: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/issuance/tokens/tok_abc123/authority/prepare \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -1741,7 +1839,7 @@ In **Settings → Authorities**, click **Revoke** next to the authority and conf Set `newAuthority` to `null` to revoke: -```typescript +```typescript title="revoke-authority.ts" body: JSON.stringify({ authority: { type: "mintAuthority", @@ -1778,7 +1876,7 @@ Call a mutation endpoint without the `/prepare` suffix and SDP handles everythin 3. Submits it to Solana 4. Returns the confirmed result with a transaction signature -```typescript +```typescript title="mint-execute.ts" const res = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/mint", { @@ -1809,7 +1907,7 @@ Issuance `amount` fields use **UI units** (decimal strings such as `"1000"` for Add `/prepare` to a mutation endpoint and SDP builds the transaction but returns it unsigned: -```typescript +```typescript title="mint-prepare.ts" const res = await fetch( "https://api.solana.com/v1/issuance/tokens/tok_abc123/mint/prepare", { @@ -1897,7 +1995,7 @@ The current wallet-policy and balance endpoints remain under `/v1/payments/walle - [Set up wallets](/docs/guides/setup-wallets) — provision the wallet before configuring controls. - [Payments](/docs/payments) — payment and ramp flows that can use a policy-controlled wallet. -- [Manage API keys](/docs/guides/manage-api-keys) — configure caller permissions separately from wallet operation permissions. +- [Manage API keys](/docs/developing-with-sdp/manage-api-keys) — configure caller permissions separately from wallet operation permissions. --- @@ -1914,13 +2012,13 @@ Use policies for treasury wallets, automated payout wallets, or any custody wall -```bash +```bash title="Terminal" curl https://api.solana.com/v1/payments/wallets/wal_abc123/policies \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="get-wallet-policies.ts" const response = await fetch( "https://api.solana.com/v1/payments/wallets/wal_abc123/policies", { headers: { Authorization: "Bearer sk_test_..." } } @@ -1937,7 +2035,7 @@ const { data } = await response.json(); The payload is wrapped in the standard `data` / `meta` envelope, with the policy under a `policy` key: -```json +```json title="response.json" { "data": { "policy": { @@ -2018,7 +2116,7 @@ Wallet policies can match operations at two levels: - **Operation family** rules apply to every evaluated operation in a broad product area. - **Operation type** rules match one exact operation identifier and take precedence when you need a narrower decision. -These are wallet-policy selectors, not API-key permissions. API-key permissions such as `payments:write` and `tokens:write` determine whether a caller may invoke an endpoint; operation permissions determine what the selected wallet may do after the request is authorized. See [Manage API keys](/docs/guides/manage-api-keys) for caller permissions. +These are wallet-policy selectors, not API-key permissions. API-key permissions such as `payments:write` and `tokens:write` determine whether a caller may invoke an endpoint; operation permissions determine what the selected wallet may do after the request is authorized. See [Manage API keys](/docs/developing-with-sdp/manage-api-keys) for caller permissions. ### Operation families @@ -2088,7 +2186,7 @@ Set both for defense in depth: a per-transaction cap that catches obvious mistak -```bash +```bash title="Terminal" curl -X PUT https://api.solana.com/v1/payments/wallets/wal_abc123/policies \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -2103,7 +2201,7 @@ curl -X PUT https://api.solana.com/v1/payments/wallets/wal_abc123/policies \ ``` -```typescript +```typescript title="update-wallet-policies.ts" await fetch( "https://api.solana.com/v1/payments/wallets/wal_abc123/policies", { @@ -2150,13 +2248,13 @@ Source: https://platform.solana.com/docs/wallet-operations/balances -```bash +```bash title="Terminal" curl https://api.solana.com/v1/payments/wallets/wal_abc123/balances \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="get-wallet-balances.ts" const response = await fetch( "https://api.solana.com/v1/payments/wallets/wal_abc123/balances", { headers: { Authorization: "Bearer sk_test_..." } } @@ -2166,7 +2264,7 @@ const { data } = await response.json(); ``` -```java +```java title="GetWalletBalances.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/payments/wallets/wal_abc123/balances")) .header("Authorization", "Bearer sk_test_...") @@ -2180,7 +2278,7 @@ HttpRequest request = HttpRequest.newBuilder() The payload is wrapped in the standard `data` / `meta` envelope, with the balances under a `walletBalances` key: -```json +```json title="response.json" { "data": { "walletBalances": { @@ -2413,7 +2511,7 @@ Fill in: Build the request body: -```typescript +```typescript title="transfer-request.ts" const body = { source: "privy_wallet_123", // `walletId` from GET /v1/wallets destination: "7xKXz...9fGh", // recipient wallet (owner) address @@ -2448,7 +2546,7 @@ The transfer status updates to `Confirmed` once the transaction is finalized. -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/payments/transfers \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -2462,7 +2560,7 @@ curl -X POST https://api.solana.com/v1/payments/transfers \ ``` -```typescript +```typescript title="execute-transfer.ts" const response = await fetch( "https://api.solana.com/v1/payments/transfers", { @@ -2485,7 +2583,7 @@ const { data } = await response.json(); ``` -```java +```java title="ExecuteTransfer.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/payments/transfers")) .header("Authorization", "Bearer sk_test_...") @@ -2526,14 +2624,14 @@ Click the record to see full details including the Solana Explorer link. -```typescript +```typescript title="verify-transfer.ts" // data.transfer.status — "confirmed" // data.transfer.signature — onchain tx signature ``` Poll status for async confirmations: -```bash +```bash title="Terminal" curl https://api.solana.com/v1/payments/transfers/txn_abc123 \ -H "Authorization: Bearer sk_test_..." ``` @@ -2586,7 +2684,7 @@ Memos are stored by SDP. They are **not** automatically attached as a Solana mem -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/payments/transfers \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -2600,7 +2698,7 @@ curl -X POST https://api.solana.com/v1/payments/transfers \ ``` -```typescript +```typescript title="send-payment-with-memo.ts" const response = await fetch( "https://api.solana.com/v1/payments/transfers", { @@ -2622,7 +2720,7 @@ const { data } = await response.json(); ``` -```java +```java title="SendPaymentWithMemo.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/payments/transfers")) .header("Authorization", "Bearer sk_test_...") @@ -2642,7 +2740,7 @@ HttpRequest request = HttpRequest.newBuilder() The memo is preserved on the transfer record: -```json +```json title="response.json" { "id": "xfr_...", "memo": "order_2026-05-14_4837", @@ -2706,7 +2804,7 @@ If your retry path replays a transfer with identical inputs but a fresh request, -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/payments/transfers \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -2720,7 +2818,7 @@ curl -X POST https://api.solana.com/v1/payments/transfers \ ``` -```typescript +```typescript title="payout-recipient.ts" async function payoutRecipient(row: PayoutRow): Promise { if (row.transferId) return row.transferId; // already submitted @@ -2750,7 +2848,7 @@ async function payoutRecipient(row: PayoutRow): Promise { ``` -```java +```java title="PayoutRecipient.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/payments/transfers")) .header("Authorization", "Bearer sk_test_...") @@ -2774,13 +2872,13 @@ After the loop finishes, list all outbound transfers in the run's time window an -```bash +```bash title="Terminal" curl "https://api.solana.com/v1/payments/transfers?direction=outbound&from=2026-05-14T00:00:00Z&to=2026-05-14T23:59:59Z&pageSize=100" \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="list-outbound-transfers.ts" async function* listOutboundTransfers(from: string, to: string) { let page = 1; while (true) { @@ -2971,13 +3069,13 @@ The minimal inbound-only request: -```bash +```bash title="Terminal" curl "https://api.solana.com/v1/payments/transfers?direction=inbound&pageSize=50" \ -H "Authorization: Bearer sk_test_..." ``` -```typescript +```typescript title="list-inbound-transfers.ts" const url = new URL("https://api.solana.com/v1/payments/transfers"); url.searchParams.set("direction", "inbound"); url.searchParams.set("pageSize", "50"); @@ -3021,7 +3119,7 @@ Customer-initiated inbound transfers do **not** populate `Transfer.memo` (memo-p The typical inbound match cycle: 1. **Read** the recent inbound transfers for the relevant token, using the **org-scoped path** (no `wallet`/`walletAddress` filter) so `from`/`to` are honored: - ``` + ```text title="Request" GET /v1/payments/transfers ?direction=inbound &token= @@ -3045,7 +3143,7 @@ A reconciliation worker that wakes on a tick and asks "what is new since last ti -```typescript +```typescript title="reconcile-tick.ts" const OVERLAP_MS = 5_000; // re-query the last 5s on each tick to catch boundary arrivals async function reconcileTick(state: WorkerState) { @@ -3123,7 +3221,7 @@ The end-user journey: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/payments/ramps/onramp/execute \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -3138,7 +3236,7 @@ curl -X POST https://api.solana.com/v1/payments/ramps/onramp/execute \ ``` -```typescript +```typescript title="onramp-execute.ts" const response = await fetch( "https://api.solana.com/v1/payments/ramps/onramp/execute", { @@ -3163,7 +3261,7 @@ const { data } = await response.json(); ``` -```java +```java title="OnrampExecute.java" HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.solana.com/v1/payments/ramps/onramp/execute")) .header("Authorization", "Bearer sk_test_...") @@ -3201,7 +3299,7 @@ The mirror image — convert from crypto to fiat from a wallet you control: -```bash +```bash title="Terminal" curl -X POST https://api.solana.com/v1/payments/ramps/offramp/execute \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -3215,7 +3313,7 @@ curl -X POST https://api.solana.com/v1/payments/ramps/offramp/execute \ ``` -```typescript +```typescript title="offramp-execute.ts" const response = await fetch( "https://api.solana.com/v1/payments/ramps/offramp/execute", { @@ -3333,7 +3431,7 @@ BVNK uses Hawk authentication. Bearer-token auth is not currently supported. BVNK is the only provider that currently accepts the `bvnkCompliance` field on the ramp request — an object of the form `{ "partyDetails": [...] }` carrying compliance party records used to satisfy travel-rule and KYC sharing requirements. The exact shape of each party record is provider-defined; see BVNK's compliance docs for the field schema. -```json +```json title="request.json" { "provider": "bvnk", "destinationWallet": "wal_...", @@ -3422,7 +3520,7 @@ From the dashboard, mint an org-scoped API key with **Role: Admin**, **Environme With the Admin key, call `GET /v1/projects` and pick the project with slug `default-sandbox`. Every organization is provisioned with exactly one sandbox and one production project automatically — isolation between workloads comes from separate organizations, not additional projects. The project ID is what you'll need in the next step: -```json +```json title="response.json" { "data": { "projects": [ @@ -3449,7 +3547,7 @@ With the Admin key, call `GET /v1/projects` and pick the project with slug `defa Call `POST /v1/projects/{projectId}/api-keys` twice to mint two project-scoped keys: one with `role: "api_developer"` for routine create, mint, and burn calls, and one with `role: "api_admin"` for compliance operations (screening, freeze, seize, force-burn). Each response includes the full key value exactly once: -```json +```json title="response.json" { "data": { "apiKey": { @@ -3507,7 +3605,7 @@ The canonical SDP API URL is `https://api.solana.com`. Use this hostname for eve Wire two HTTP clients (one per project-scoped key) using a thin wrapper around `fetch`: -```javascript +```javascript title="sdp-client.js" const BASE = "https://api.solana.com"; class SdpClient { @@ -3542,14 +3640,14 @@ Use `sdp` for routine operations (create, mint, burn) and `sdpAdmin` for complia Now confirm the setup by listing the wallets bound to your project: -```javascript +```javascript title="list-wallets.js" const { data } = await sdp.get("/v1/wallets"); console.log(`Custody wallets configured: ${data.wallets.length}`); ``` A working setup prints: -``` +```text title="Output" Custody wallets configured: 2 ``` @@ -3595,7 +3693,7 @@ You can delegate or revoke any of these later via `POST /v1/issuance/tokens/{id} Section 4 takes one input: the configuration object representing the design choices above. For the Treasury Pilot USD example this tutorial uses end-to-end: -```javascript +```javascript title="token-config.js" const tokenConfig = { template: "stablecoin", name: "Treasury Pilot USD", @@ -3618,7 +3716,7 @@ Three API calls take a design from object literal to a deployed regulated stable Every state-mutating execute endpoint in SDP (deploy, mint, burn, seize, force-burn, authority) supports an `Idempotency-Key` HTTP header. A retry with the same key replays the original response. A retry with a different key is a new request. The header is how you make network-level retries safe under partial failure. Replace the `SdpClient` definition from Section 2 with this version: -```javascript +```javascript title="sdp-client.js" class SdpClient { constructor(apiKey, baseUrl = BASE) { this.apiKey = apiKey; @@ -3657,14 +3755,14 @@ Generate one key per logical operation, not per retry. Create the key once befor The first call writes the token's metadata to SDP's database. Nothing happens on Solana yet. The token starts in `status: "pending"` with no `mintAddress`: -```javascript +```javascript title="create-token.js" const created = await sdp.post("/v1/issuance/tokens", tokenConfig); const tokenId = created.data.token.id; ``` A successful response: -```json +```json title="response.json" { "data": { "token": { @@ -3689,7 +3787,7 @@ Save the `tok_*` ID; the deploy call uses it. The deploy call submits a Token-2022 mint creation transaction to Solana via the custody signer, and updates the token record with the resulting on-chain addresses: -```javascript +```javascript title="deploy-token.js" const deployKey = idempotencyKey("deploy"); const deployed = await sdp.post( `/v1/issuance/tokens/${tokenId}/deploy`, @@ -3701,7 +3799,7 @@ const mintAddress = deployed.data.token.mintAddress; A successful response carries the deployed token record with `status: "active"` and the on-chain `mintAddress` populated: -```json +```json title="response.json" { "data": { "token": { @@ -3729,7 +3827,7 @@ The `mintAuthority`, `freezeAuthority`, and `metadataAuthority` all resolve to t Deploy can return a bare `500 INTERNAL_ERROR` with no `details` field. The most common cause on devnet is an unfunded custody wallet (Section 2 covered the airdrop fix), but any downstream Solana RPC failure surfaces this way. The SDP team is closing this gap so deploy will return the underlying Solana error directly; until that ships, the diagnostic move is to call the `/prepare` variant of the same endpoint. -```javascript +```javascript title="deploy-prepare.js" const prepared = await sdp.post( `/v1/issuance/tokens/${tokenId}/deploy/prepare`, {} @@ -3739,7 +3837,7 @@ console.log(prepared.data.simulation); `/prepare` builds the transaction and runs simulation against Solana without submitting. Its response surfaces the real error in `simulation.error`: -```json +```json title="response.json" { "data": { "preparedTransaction": { "serialized": "...", "blockhash": "..." }, @@ -3759,7 +3857,7 @@ This prepare-as-diagnostic pattern works for every endpoint that has a `/prepare Before moving to Section 5, confirm the on-chain state matches the record: -```javascript +```javascript title="confirm-deploy.js" const { data } = await sdp.get(`/v1/issuance/tokens/${tokenId}`); console.log(`Status: ${data.token.status}`); console.log(`Mint: ${data.token.mintAddress}`); @@ -3767,7 +3865,7 @@ console.log(`Mint: ${data.token.mintAddress}`); A working deploy prints: -``` +```text title="Output" Status: active Mint: 6V5bTuMsmXyhdY2Hj6VWQZsuacugACBmPqNtQgGtar8L ``` @@ -3788,7 +3886,7 @@ Section 4(a)(5) of the GENIUS Act treats a permitted payment stablecoin issuer a Every transfer destination and every allowlist entry gets screened before your application acts on it. One API call, one response. -```javascript +```javascript title="screen-address.js" const response = await sdpAdmin.post("/v1/compliance/address-screenings", { address: destinationAddress, network: "solana", @@ -3810,7 +3908,7 @@ On the sandbox response this tutorial cites, Range and Elliptic return numeric s Freezing halts one holder's account without touching any other holder. The freeze is on-chain, enforced by the Token-2022 program. SDP signs the freeze instruction with the freeze authority that was set at deploy; the on-chain program does the enforcement. -```javascript +```javascript title="freeze-account.js" const holderFreezeKey = idempotencyKey("freeze"); await sdpAdmin.post( `/v1/issuance/tokens/${tokenId}/freeze`, @@ -3832,7 +3930,7 @@ Section 7 demonstrates the resulting error: an HTTP 502 `SOLANA_RPC_ERROR` from Seize moves tokens out of a holder's account without their signature, via the **permanent delegate** authority set at deploy. The permanent delegate is a Token-2022 extension that grants a named authority the right to transfer or burn any holder's tokens regardless of holder signature. This is the technological capability the GENIUS Act requires you to have ready for lawful orders. The institutional use case is a court-ordered transfer or a sanctions matter where you must recover specific tokens to a controlled wallet. -```javascript +```javascript title="seize-tokens.js" const seizeKey = idempotencyKey("seize"); await sdpAdmin.post( `/v1/issuance/tokens/${tokenId}/seize`, @@ -3854,7 +3952,7 @@ The memo persists on the transaction record. It's the audit attribution your com Force-burn destroys tokens at a holder's account. Same authority as seize, different effect. Reach for it only when the tokens cannot be recovered to a controlled wallet, for example a self-custodied address whose key you cannot obtain. -```javascript +```javascript title="force-burn.js" const forceBurnKey = idempotencyKey("force-burn"); await sdpAdmin.post( `/v1/issuance/tokens/${tokenId}/force-burn`, @@ -3886,7 +3984,7 @@ Minting is how new supply enters circulation. For a regulated stablecoin, every Minting requires the `tokens:write` permission, so the Developer key (`sdp`) is the right credential: -```javascript +```javascript title="mint-tokens.js" const mintKey = idempotencyKey("mint"); const minted = await sdp.post( `/v1/issuance/tokens/${tokenId}/mint`, @@ -3911,7 +4009,7 @@ Three field rules to keep clear: A successful mint returns the transaction record and the token account that received the tokens: -```json +```json title="response.json" { "data": { "transaction": { @@ -3948,14 +4046,14 @@ The pattern that does not work is mint memos that don't reconcile cleanly to a r ### 4. Confirm the supply -```javascript +```javascript title="confirm-supply.js" const { data } = await sdp.get(`/v1/issuance/tokens/${tokenId}`); console.log(`Total supply: ${data.token.totalSupply}`); ``` Prints: -``` +```text title="Output" Total supply: 1000000 ``` @@ -3969,7 +4067,7 @@ Section 5's compliance controls only matter if they enforce. This section moves Transfers run on the `/v1/payments/transfers` endpoint family, separate from the issuance family that Section 4 used. The required permissions are `payments:write` and `wallets:read`, both of which the Developer key (`sdp`) carries: -```javascript +```javascript title="transfer-tokens.js" const { data } = await sdp.get("/v1/wallets"); const sourceWalletId = data.wallets[0].walletId; @@ -4002,7 +4100,7 @@ Three field rules to keep clear: A successful response includes the on-chain signature once the transaction confirms: -```json +```json title="response.json" { "data": { "transfer": { @@ -4025,7 +4123,7 @@ A successful response includes the on-chain signature once the transaction confi To demonstrate the on-chain block, freeze the destination's TPUSD account using the Admin key. Freeze requires `tokens:admin`, which the Developer key does not carry: -```javascript +```javascript title="freeze-destination.js" const transferFreezeKey = idempotencyKey("freeze"); await sdpAdmin.post( `/v1/issuance/tokens/${tokenId}/freeze`, @@ -4045,7 +4143,7 @@ The freeze records a frozen account in SDP's audit trail and applies the Token-2 Run the same transfer call. This time it fails: -```javascript +```javascript title="transfer-blocked.js" try { await sdp.post("/v1/payments/transfers", { source: sourceWalletId, @@ -4061,7 +4159,7 @@ try { The error envelope SDP returns: -```json +```json title="response.json" { "error": { "code": "SOLANA_RPC_ERROR", @@ -4078,7 +4176,7 @@ The outer error code is `SOLANA_RPC_ERROR`, but it is upstream-call diagnostic, The institutional pattern is to write your error handler against the wrapped on-chain code rather than the outer SDP code. The outer code tells you something went wrong upstream; the wrapped code tells you what. -```javascript +```javascript title="parse-error.js" function isOnChainCompliance(error) { return error.message?.includes("custom program error: 0x11"); } @@ -4101,7 +4199,7 @@ Token-2022's `0x11` is `AccountFrozen` (decimal 17). Treat it as a compliance ve For a production environment, unfreezing happens only after the underlying compliance review resolves. For sandbox testing, you can unfreeze immediately: -```javascript +```javascript title="unfreeze-account.js" await sdpAdmin.post(`/v1/issuance/tokens/${tokenId}/unfreeze`, { accountAddress: destinationAddress, }); @@ -4258,7 +4356,7 @@ Docker daemon is running. The install script downloads `compose.yml` and `.env.example` for the latest release into `~/sdp` and verifies them against the release checksums. -```bash +```bash title="Terminal" curl -fsSL https://github.com/solana-foundation/solana-developer-platform/releases/latest/download/install.sh | bash ``` @@ -4281,7 +4379,7 @@ the app secrets, and validates your answers. answer the prompts, and download the `.env` into `~/sdp`. - **In your terminal** — run the configurator from the API image: - ```bash + ```bash title="Terminal" docker run --rm -it -v "$HOME/sdp:/out" \ ghcr.io/solana-foundation/sdp/sdp-api:latest \ node configure.js --out /out/.env @@ -4295,7 +4393,7 @@ variable means and which are required. ## 3. Bring up the stack -```bash +```bash title="Terminal" cd ~/sdp docker compose up -d ``` @@ -4306,7 +4404,7 @@ minutes. ## 4. Verify -```bash +```bash title="Terminal" docker compose ps docker compose logs -f sdp-api ``` @@ -4357,7 +4455,7 @@ devnet RPC endpoint from your provider before treating this flow as reliable. From the directory where the installer placed `compose.yml` and `.env`: -```bash +```bash title="Terminal" cd ~/sdp docker compose up -d docker compose ps @@ -4409,7 +4507,7 @@ The signing step generates a local Solana signer in the browser and shows the public key to fund on devnet. Save the generated file as `~/sdp/.env`, then restart the stack: -```bash +```bash title="Terminal" cd ~/sdp docker compose up -d ``` @@ -4432,7 +4530,7 @@ show the organization and project context instead of onboarding errors. If the dashboard loads but organization state does not appear, check the API logs: -```bash +```bash title="Terminal" docker compose logs -f sdp-api ``` @@ -4450,7 +4548,7 @@ named `Devnet local wallet`. If you prefer the API path, use an admin key from the dashboard: -```bash +```bash title="Terminal" curl -X POST http://localhost:8787/v1/wallets/initialize \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ @@ -4483,7 +4581,7 @@ Save the full key when the dashboard shows it. SDP only displays it once. Use the new key against your self-hosted API: -```bash +```bash title="Terminal" export SDP_API_KEY="sk_test_..." curl "http://localhost:8787/v1/wallets?view=summary" \ @@ -4646,7 +4744,7 @@ A release ships new container images plus an updated `compose.yml`. To upgrade: checksums) and leaves any existing `.env` and `.env.example` in place, printing a link to the new release's `.env.example` so you can check for new variables. - ```bash + ```bash title="Terminal" curl -fsSL https://github.com/solana-foundation/solana-developer-platform/releases/latest/download/install.sh | bash ``` @@ -4656,7 +4754,7 @@ A release ships new container images plus an updated `compose.yml`. To upgrade: 3. **Pull and restart.** - ```bash + ```bash title="Terminal" cd ~/sdp docker compose pull docker compose up -d @@ -4695,7 +4793,7 @@ Take a logical dump with `pg_dump` from the running container. Running it throug `sh -c` lets `pg_dump` read `POSTGRES_USER` and `POSTGRES_DB` from inside the container, so custom values are honored: -```bash +```bash title="Terminal" cd ~/sdp docker compose exec -T postgres \ sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > sdp-backup.sql @@ -4710,7 +4808,7 @@ into an **empty** database and let the stack start on top of it. Do not restore over an already-migrated database, or the dump's schema collides with the one `sdp-migrate` created on the previous `up`. -```bash +```bash title="Terminal" cd ~/sdp docker compose down # stop every service docker volume rm sdp_sdp-postgres-data # discard the current database (destructive) @@ -4742,7 +4840,7 @@ Source: https://platform.solana.com/docs/self-hosting/troubleshooting Most self-hosting problems surface at startup. Start by looking at the service state and logs: -```bash +```bash title="Terminal" cd ~/sdp docker compose ps docker compose logs -f sdp-api @@ -4786,7 +4884,7 @@ process holds one of them, the service won't bind. Override the host port in `sdp-migrate` runs once before the API starts; if it fails, the API stays down. Inspect it with: -```bash +```bash title="Terminal" docker compose logs sdp-migrate ``` @@ -5024,8 +5122,8 @@ Open the pull request after intake is complete, or after the SDP team has create --- -### AI Consumption -Source: https://platform.solana.com/docs/reference/ai-consumption +### Documentation for AI +Source: https://platform.solana.com/docs/reference/docs-for-ai > Public machine-readable entry points and guidance for agents and AI systems consuming SDP docs and APIs. diff --git a/apps/sdp-docs/public/llms.txt b/apps/sdp-docs/public/llms.txt index b52edce70..db93133bf 100644 --- a/apps/sdp-docs/public/llms.txt +++ b/apps/sdp-docs/public/llms.txt @@ -7,7 +7,7 @@ - API: https://api.solana.com - Interactive API docs: https://api.solana.com/docs - OpenAPI: https://api.solana.com/openapi.json -- AI guide: https://platform.solana.com/docs/reference/ai-consumption +- AI guide: https://platform.solana.com/docs/reference/docs-for-ai ## Supported surfaces - Wallets and custody @@ -19,10 +19,10 @@ - Asset profiles and public token metadata ## Start here -- [Introduction](https://platform.solana.com/docs/introduction): What SDP is, what it provides, and how it works. -- [Set Up Your Organization](https://platform.solana.com/docs/guides/setup-organization): Create and configure your SDP organization through the dashboard. +- [What is SDP?](https://platform.solana.com/docs/introduction): Putting institutional-grade capital markets at your fingertips. Issue, move, and manage regulated assets on Solana. +- [Set Up Your Organization & Projects](https://platform.solana.com/docs/guides/setup-organization): Create your SDP organization and organize your work into projects. - [Set Up Wallets](https://platform.solana.com/docs/guides/setup-wallets): Initialize a custody provider and create wallets for signing transactions. -- [Manage API Keys](https://platform.solana.com/docs/guides/manage-api-keys): Create, rotate, and revoke API keys with role-based access and environment scoping. +- [Manage API Keys](https://platform.solana.com/docs/developing-with-sdp/manage-api-keys): Create, rotate, and revoke API keys with role-based access and environment scoping. - [Tokenize an Asset](https://platform.solana.com/docs/guides/tokenize-an-asset): Map a real-world tokenization use case to the right SDP template, authorities, and API flow. - [Create a Token](https://platform.solana.com/docs/guides/create-a-token): Define a new token using templates or custom configuration. - [Deploy a Token](https://platform.solana.com/docs/guides/deploy-a-token): Deploy a created token to the Solana blockchain. @@ -32,7 +32,7 @@ - [Issuance Token Types](https://platform.solana.com/docs/reference/issuance-token-types): How to choose a token template, and the default controls each one applies. - [End-to-end Payment Flow](https://platform.solana.com/docs/tutorials/end-to-end-payment-flow): A high-level payment flow from quote to settlement. - [Infrastructure Provider Onboarding](https://platform.solana.com/docs/reference/provider-onboarding): Add an infrastructure provider integration to SDP through the self-service contribution process. -- [AI Consumption](https://platform.solana.com/docs/reference/ai-consumption): Public machine-readable entry points and guidance for agents and AI systems consuming SDP docs and APIs. +- [Documentation for AI](https://platform.solana.com/docs/reference/docs-for-ai): Public machine-readable entry points and guidance for agents and AI systems consuming SDP docs and APIs. - [Postman Collection](https://platform.solana.com/docs/reference/postman-collection): Download the public SDP API Postman collection generated from the OpenAPI contract. - [API Reference](https://platform.solana.com/docs/reference/api): Endpoint index from the repository OpenAPI spec. - [Health](https://platform.solana.com/docs/reference/api/health): Service health and readiness endpoints. @@ -45,7 +45,7 @@ - [Asset Profiles](https://platform.solana.com/docs/reference/api/asset-profiles): Issued-asset identity and metadata profiles, plus the public token metadata URI. ## AI guide -- [AI Consumption](https://platform.solana.com/docs/reference/ai-consumption): Human-readable landing page for machine-readable SDP docs resources, usage guidance, and public AI scope. +- [Docs for AI](https://platform.solana.com/docs/reference/docs-for-ai): Human-readable landing page for machine-readable SDP docs resources, usage guidance, and public AI scope. ## Machine-readable resources - [llms.txt](https://platform.solana.com/docs/ai/llms.txt) diff --git a/apps/sdp-docs/scripts/generate-ai-resources.ts b/apps/sdp-docs/scripts/generate-ai-resources.ts index 3cc1edce9..09e28e42c 100644 --- a/apps/sdp-docs/scripts/generate-ai-resources.ts +++ b/apps/sdp-docs/scripts/generate-ai-resources.ts @@ -56,7 +56,7 @@ const KEY_PAGE_SLUGS = [ "introduction", "guides/setup-organization", "guides/setup-wallets", - "guides/manage-api-keys", + "developing-with-sdp/manage-api-keys", "guides/tokenize-an-asset", "guides/create-a-token", "guides/deploy-a-token", @@ -66,7 +66,7 @@ const KEY_PAGE_SLUGS = [ "reference/issuance-token-types", "tutorials/end-to-end-payment-flow", "reference/provider-onboarding", - "reference/ai-consumption", + "reference/docs-for-ai", "reference/postman-collection", "reference/api/index", "reference/api/health", @@ -275,7 +275,7 @@ function renderLlms(keyPages: DocsPage[]): string { ...keyPages.map(renderLink), "", "## AI guide", - `- [AI Consumption](${aiGuideUrl}): Human-readable landing page for machine-readable SDP docs resources, usage guidance, and public AI scope.`, + `- [Docs for AI](${aiGuideUrl}): Human-readable landing page for machine-readable SDP docs resources, usage guidance, and public AI scope.`, "", "## Machine-readable resources", `- [llms.txt](${aiLlmsUrl})`, diff --git a/apps/sdp-docs/scripts/generate-api-docs.mjs b/apps/sdp-docs/scripts/generate-api-docs.mjs index ac1b9d3b9..d69171c59 100644 --- a/apps/sdp-docs/scripts/generate-api-docs.mjs +++ b/apps/sdp-docs/scripts/generate-api-docs.mjs @@ -316,7 +316,7 @@ const run = async () => { "tutorials/end-to-end-payment-flow", "---Integrations---", "reference/provider-onboarding", - "reference/ai-consumption", + "reference/docs-for-ai", "reference/postman-collection", "---API---", ...apiPages, diff --git a/apps/sdp-docs/source.config.ts b/apps/sdp-docs/source.config.ts index f6de4be9a..87cd31f64 100644 --- a/apps/sdp-docs/source.config.ts +++ b/apps/sdp-docs/source.config.ts @@ -1,4 +1,10 @@ -import { defineDocs, frontmatterSchema } from "fumadocs-mdx/config"; +import type { RampProviderId } from "@sdp/types"; +import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins"; +import { defineConfig, defineDocs, frontmatterSchema } from "fumadocs-mdx/config"; + +type ShikiTransformer = NonNullable[number]; +type Element = Parameters>[0]; +type ElementContent = Element["children"][number]; export const docs = defineDocs({ dir: "content/docs", @@ -7,4 +13,125 @@ export const docs = defineDocs({ }, }); -export default docs; +/** + * Mirrors RAMP_PROVIDERS from @sdp/types (typed against it), duplicated as a + * literal because fumadocs-mdx loads this config in a plain Node context that + * cannot resolve the workspace package's TS source at runtime. + */ +const ROTATOR_PROVIDERS = [ + "moonpay", + "lightspark", + "bvnk", + "moneygram", + "coinbase", + "mural", + "stripe", +] as const satisfies readonly RampProviderId[]; + +type Rotation = { flag: string; token: string; prefix: string; values: readonly string[] }; + +/** + * Each rotation swaps `token` in fences tagged with `flag` for a carousel of + * `values`, with `prefix` re-emitted as plain text before the carousel (so a + * leading `/` can sit outside the chip). Every values list must have exactly + * 7 entries — the CSS keyframes in ramp-rotator.css step through 7 rows. + */ +const ROTATIONS = [ + { + flag: "rotate-providers", + token: '"moonpay"', + prefix: "", + values: ROTATOR_PROVIDERS.map((provider) => `"${provider}"`), + }, + { + flag: "rotate-token-ops", + token: "/freeze", + prefix: "/", + values: ["freeze", "unfreeze", "pause", "seize", "force-burn", "mint", "burn"], + }, +] as const satisfies readonly Rotation[]; + +/** + * Builds one carousel row. The lead row carries the value as real text (so + * copy/paste and screen readers see exactly one value); looping rows render + * their value via CSS `content: attr(data-name)` so they never pollute the + * copied code. + */ +function rotatorItem(value: string, lead: boolean): Element { + if (lead) { + return { + type: "element", + tagName: "span", + properties: { className: ["rpr-item"] }, + children: [{ type: "text", value }], + }; + } + + return { + type: "element", + tagName: "span", + properties: { + className: ["rpr-item"], + "data-name": value, + "aria-hidden": "true", + }, + children: [], + }; +} + +/** + * Replaces a rotation's token in fences tagged with its flag with a vertical + * carousel cycling through the rotation's values (see ROTATIONS). + */ +const rampProviderRotator: ShikiTransformer = { + name: "ramp-provider-rotator", + span(node) { + const meta = this.options.meta?.__raw; + if (!meta) return; + const rotation = ROTATIONS.find((candidate) => meta.includes(candidate.flag)); + if (!rotation) return; + if (node.children.length !== 1) return; + const child = node.children[0]; + if (child.type !== "text" || !child.value.includes(rotation.token)) return; + + const [before, after] = child.value.split(rotation.token); + const [leadValue, ...restValues] = rotation.values; + const items: ElementContent[] = [ + rotatorItem(leadValue, true), + ...restValues.map((value) => rotatorItem(value, false)), + rotatorItem(leadValue, false), + ]; + + node.children = [ + { type: "text", value: before + rotation.prefix }, + { + type: "element", + tagName: "span", + properties: { className: ["rpr"] }, + children: [ + { + type: "element", + tagName: "span", + properties: { className: ["rpr-track"] }, + children: items, + }, + ], + }, + { type: "text", value: after }, + ]; + }, +}; + +const defaultTransformers = rehypeCodeDefaultOptions.transformers; +if (!defaultTransformers) { + throw new Error("fumadocs rehype-code default transformers missing"); +} + +export default defineConfig({ + mdxOptions: { + rehypeCodeOptions: { + ...rehypeCodeDefaultOptions, + transformers: [...defaultTransformers, rampProviderRotator], + }, + }, +}); diff --git a/apps/sdp-docs/src/app/api/search/route.ts b/apps/sdp-docs/src/app/api/search/route.ts new file mode 100644 index 000000000..e8aa2e34b --- /dev/null +++ b/apps/sdp-docs/src/app/api/search/route.ts @@ -0,0 +1,4 @@ +import { createFromSource } from "fumadocs-core/search/server"; +import { source } from "@/lib/source"; + +export const { GET } = createFromSource(source); diff --git a/apps/sdp-docs/src/app/docs/[[...slug]]/page.tsx b/apps/sdp-docs/src/app/docs/[[...slug]]/page.tsx index 8b90e9f75..87912b911 100644 --- a/apps/sdp-docs/src/app/docs/[[...slug]]/page.tsx +++ b/apps/sdp-docs/src/app/docs/[[...slug]]/page.tsx @@ -3,7 +3,7 @@ import { Tab, Tabs } from "fumadocs-ui/components/tabs"; import type { Metadata } from "next"; import { notFound, redirect } from "next/navigation"; import type { ComponentType } from "react"; -import { HOME_TOC } from "@/components/docs-shell/home"; +import { HOME_TOC, ProviderCallout } from "@/components/docs-shell/home"; import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "@/components/docs-shell/page"; import { getDocsPagePath } from "@/lib/site"; import { source } from "@/lib/source"; @@ -36,7 +36,8 @@ type ResolvedPage = { function resolvePage(slug?: string[]): ResolvedPage | null { if (!slug || slug.length === 0) { - return null; + const rootPage = source.getPage([]); + return rootPage ? { page: rootPage, pageSlug: [] } : null; } const directPage = source.getPage(slug); @@ -56,8 +57,8 @@ function resolvePage(slug?: string[]): ResolvedPage | null { export default async function Page({ params }: DocsPageProps) { const { slug } = await params; - if (!slug || slug.length === 0) { - redirect("/docs/home"); + if (slug?.join("/") === "home") { + redirect("/docs"); } const resolvedPage = resolvePage(slug); @@ -81,7 +82,7 @@ export default async function Page({ params }: DocsPageProps) { ? { name: String(neighbours.next.name), url: neighbours.next.url } : undefined; - const isHome = resolvedPage.pageSlug.join("/") === "home"; + const isHome = resolvedPage.pageSlug.length === 0; const baseToc = isHome ? HOME_TOC : (data.toc ?? []); const steps = data.steps ?? []; let toc = baseToc; @@ -99,7 +100,7 @@ export default async function Page({ params }: DocsPageProps) { } return ( - + : undefined}> {!data.hideTitle && {data.title}} {!data.hideTitle && {data.description}} @@ -125,7 +126,8 @@ export async function generateMetadata({ params }: DocsPageProps): Promise + + {process.env.NODE_ENV === "development" && ( +