From 47c9a6987186bb07eed8c3c91760fde855f73919 Mon Sep 17 00:00:00 2001 From: SVS Protocol Date: Tue, 30 Jun 2026 22:59:27 -0700 Subject: [PATCH] Add SVS verified action example --- examples/svs-verified-action/.env.example | 10 ++ examples/svs-verified-action/README.md | 95 +++++++++++ examples/svs-verified-action/package.json | 15 ++ .../svs-verified-action.mjs | 155 ++++++++++++++++++ examples/svs-verified-action/validate.mjs | 119 ++++++++++++++ 5 files changed, 394 insertions(+) create mode 100644 examples/svs-verified-action/.env.example create mode 100644 examples/svs-verified-action/README.md create mode 100644 examples/svs-verified-action/package.json create mode 100644 examples/svs-verified-action/svs-verified-action.mjs create mode 100644 examples/svs-verified-action/validate.mjs diff --git a/examples/svs-verified-action/.env.example b/examples/svs-verified-action/.env.example new file mode 100644 index 000000000000..17ab916d804a --- /dev/null +++ b/examples/svs-verified-action/.env.example @@ -0,0 +1,10 @@ +# Placeholder values only. Do not commit real API keys or signing secrets. +SVS_SERVER_URL=https://app.svsprotocol.com +SVS_BOT_ID=replace-with-dashboard-bot-id +SVS_BOT_POLICY_ID=memo-human-approved-v1 +SVS_BOT_API_KEY=replace-with-dashboard-issued-api-key +SVS_BOT_REQUEST_SIGNING_SECRET=replace-with-dashboard-issued-request-signing-secret +SVS_BOT_EXPECTED_INTEGRATION_CONTRACT_HASH=replace-with-current-contract-hash +SOLANA_RPC_URL=https://api.devnet.solana.com +SVS_SERIALIZED_TRANSACTION_BASE64=replace-with-prepared-serialized-transaction +SVS_RUN_LIVE_SUBMIT=false diff --git a/examples/svs-verified-action/README.md b/examples/svs-verified-action/README.md new file mode 100644 index 000000000000..558129c33b7b --- /dev/null +++ b/examples/svs-verified-action/README.md @@ -0,0 +1,95 @@ +# SVS Verified Action Example For Vercel AI SDK + +This directory is a PR-ready example package for the Vercel AI SDK ecosystem. +It is intentionally small so a framework maintainer can review it without the +private SVS implementation details. + +## What The Example Shows + +The example adds one Vercel AI SDK compatible tool: + +```text +svsVerifyAndSubmitSolanaAction +``` + +The tool is created from the public SVS package export: + +```js +import { createSvsVercelAiSdkTools } from "@svsprotocol/solana/vercel-ai"; +``` + +It routes a prepared Solana action through SVS so the action is not trusted +until SVS checks signed bot identity, production readiness, human wallet +approval, registry proof, and portable verification. + +## Proposed Upstream Placement + +Suggested destination in a Vercel AI SDK docs or examples PR: + +```text +examples/svs-verified-action/ +``` + +The package is safe to copy because it uses only public dependencies and +placeholder environment values. + +## Local Review + +From this directory: + +```sh +npm install +npm run validate +``` + +## Protocol Or Wallet Gate + +Protocols and wallets can reject unverified automation by requiring the hosted +SVS registry before accepting an agent action: + +```js +import { createHostedVerifiedAgentRegistryMiddleware } from "@svsprotocol/solana/protocol"; + +const requireVerifiedAgent = createHostedVerifiedAgentRegistryMiddleware({ + registryUrl: "https://registry.svsprotocol.com/registry.json", + expectedRegistryHash: "PINNED_REGISTRY_HASH", + trustPolicy: "high-trust" +}); + +await requireVerifiedAgent({ agent: { botId: "svs-demo-devnet-agent" } }); +``` + +## Public References + +- Docs: https://svsprotocol.com/docs +- Registry: https://registry.svsprotocol.com/registry.json +- Verified Agent Standard: https://svsprotocol.com/docs#verified-agent-standard +- Verifier: https://svsprotocol.com/verify + +## Run Modes + +The default run is import validation only. It does not require SVS credentials, +read your config, or submit an action: + +```sh +node ./svs-verified-action.mjs +``` + +Run `npm run validate` to check the package files and placeholder env template +before opening the upstream PR. + +Live submission is opt-in: + +```sh +SVS_RUN_LIVE_SUBMIT=true node ./svs-verified-action.mjs +``` + +Live mode requires a real SVS dashboard, bot API key, request-signing secret, +controller wallet, current integration-contract hash, and a prepared serialized +transaction. Do not commit those values. + +## Boundary + +This package must contain no API keys, request-signing secrets, wallet +keypairs, local `data/` evidence, private dashboard source, admin endpoints, or +private operator artifacts. diff --git a/examples/svs-verified-action/package.json b/examples/svs-verified-action/package.json new file mode 100644 index 000000000000..c6097abae2ca --- /dev/null +++ b/examples/svs-verified-action/package.json @@ -0,0 +1,15 @@ +{ + "name": "svs-vercel-ai-verified-action-example", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Reviewable Vercel AI SDK example that routes Solana tool calls through SVS human approval and proof verification.", + "scripts": { + "validate": "node ./validate.mjs" + }, + "dependencies": { + "@svsprotocol/solana": "^0.2.0", + "ai": "^6.0.202", + "zod": "^4.4.3" + } +} diff --git a/examples/svs-verified-action/svs-verified-action.mjs b/examples/svs-verified-action/svs-verified-action.mjs new file mode 100644 index 000000000000..3ec029e345b7 --- /dev/null +++ b/examples/svs-verified-action/svs-verified-action.mjs @@ -0,0 +1,155 @@ +import { realpathSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { tool } from "ai"; +import { z } from "zod"; +import { + VERCEL_AI_SDK_ADAPTER_VERSION, + VERCEL_AI_SDK_TOOL_NAME, + createSvsVercelAiSdkTools +} from "@svsprotocol/solana/vercel-ai"; + +const REQUIRED_ENV = [ + "SVS_SERVER_URL", + "SVS_BOT_ID", + "SVS_BOT_POLICY_ID", + "SVS_BOT_API_KEY", + "SVS_BOT_REQUEST_SIGNING_SECRET", + "SVS_BOT_EXPECTED_INTEGRATION_CONTRACT_HASH", + "SOLANA_RPC_URL" +]; + +const LIVE_SUBMIT_REQUIRED_ENV = [ + ...REQUIRED_ENV, + "SVS_SERIALIZED_TRANSACTION_BASE64" +]; + +export function createSvsVerifiedVercelAiSdkTools(env = process.env) { + assertRequiredEnv(env); + + return createSvsVercelAiSdkTools({ + tool, + z, + baseUrl: env.SVS_SERVER_URL, + apiKey: env.SVS_BOT_API_KEY, + requestSigningSecret: env.SVS_BOT_REQUEST_SIGNING_SECRET, + expectedIntegrationContractHash: env.SVS_BOT_EXPECTED_INTEGRATION_CONTRACT_HASH, + botId: env.SVS_BOT_ID, + policyId: env.SVS_BOT_POLICY_ID, + rpcUrl: env.SOLANA_RPC_URL, + waitForProof: env.SVS_WAIT_FOR_PROOF === "true", + fetchProof: true, + checkReceiptRegistryChain: true + }); +} + +export async function requireSvsVerifiedVercelAiSdkReady(env = process.env) { + const svs = createSvsVerifiedVercelAiSdkTools(env); + + await svs.requireSvsProductionReady({ + requireNoExpiredPreviousSigningSecrets: true + }); + + return { + ok: true, + toolName: VERCEL_AI_SDK_TOOL_NAME + }; +} + +export async function submitSvsVerifiedVercelAiSdkAction({ + requestId, + intent, + serializedTransaction, + simulation, + metadata = {} +}, env = process.env) { + const svs = createSvsVerifiedVercelAiSdkTools(env); + + await svs.requireSvsProductionReady({ + requireNoExpiredPreviousSigningSecrets: true + }); + + return svs.verifyAndSubmitSolanaAction({ + requestId, + idempotencyKey: requestId, + intent, + serializedTransaction, + simulation, + metadata, + source: { + agentFramework: "vercel-ai-sdk", + adapter: VERCEL_AI_SDK_ADAPTER_VERSION, + example: "svs-verified-action" + } + }); +} + +export function getSvsVercelAiSdkExampleInfo() { + return { + ok: true, + runtime: "Vercel AI SDK", + hostPackageImported: typeof tool === "function", + adapterVersion: VERCEL_AI_SDK_ADAPTER_VERSION, + toolName: VERCEL_AI_SDK_TOOL_NAME, + packageExport: "@svsprotocol/solana/vercel-ai", + liveSubmitOptIn: "SVS_RUN_LIVE_SUBMIT=true" + }; +} + +function assertRequiredEnv(env) { + const missing = REQUIRED_ENV.filter((name) => !env[name]); + + if (missing.length > 0) { + throw new Error(`Missing required SVS env values: ${missing.join(", ")}`); + } +} + +function assertLiveSubmitEnv(env) { + const missing = LIVE_SUBMIT_REQUIRED_ENV.filter((name) => !env[name]); + + if (missing.length > 0) { + throw new Error(`Missing required SVS live-submit env values: ${missing.join(", ")}`); + } +} + +function isDirectRun(metaUrl, argvPath = process.argv[1]) { + if (!argvPath) { + return false; + } + + try { + return realpathSync(fileURLToPath(metaUrl)) === realpathSync(argvPath); + } catch { + return fileURLToPath(metaUrl) === argvPath; + } +} + +if (isDirectRun(import.meta.url)) { + const info = getSvsVercelAiSdkExampleInfo(); + + if (process.env.SVS_RUN_LIVE_SUBMIT !== "true") { + console.log(JSON.stringify({ + ...info, + status: "dry_run", + nextAction: "Set SVS_RUN_LIVE_SUBMIT=true only after filling real SVS credentials and a prepared transaction." + }, null, 2)); + process.exit(0); + } + + assertLiveSubmitEnv(process.env); + + const result = await submitSvsVerifiedVercelAiSdkAction({ + requestId: `svs-vercel-ai-${Date.now()}`, + intent: { + botId: process.env.SVS_BOT_ID, + type: "memo", + summary: "Submit a Vercel AI SDK Solana tool call through SVS human approval." + }, + serializedTransaction: process.env.SVS_SERIALIZED_TRANSACTION_BASE64, + simulation: { + ok: true, + source: "provided-by-agent" + } + }); + + console.log(JSON.stringify(result, null, 2)); +} diff --git a/examples/svs-verified-action/validate.mjs b/examples/svs-verified-action/validate.mjs new file mode 100644 index 000000000000..29dc972b4409 --- /dev/null +++ b/examples/svs-verified-action/validate.mjs @@ -0,0 +1,119 @@ +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { readFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; + +const files = { + readme: await readFile(new URL("./README.md", import.meta.url), "utf8"), + packageJson: JSON.parse(await readFile(new URL("./package.json", import.meta.url), "utf8")), + envExample: await readFile(new URL("./.env.example", import.meta.url), "utf8"), + example: await readFile(new URL("./svs-verified-action.mjs", import.meta.url), "utf8") +}; + +assert.equal(files.packageJson.private, true); +assert.equal(files.packageJson.type, "module"); +assert.equal(files.packageJson.dependencies["@svsprotocol/solana"], "^0.2.0"); +assert.equal(files.packageJson.dependencies.ai, "^6.0.202"); +assert.equal(files.packageJson.dependencies.zod, "^4.4.3"); +assert.equal(files.packageJson.scripts.validate, "node ./validate.mjs"); + +for (const required of [ + "Vercel AI SDK", + "examples/svs-verified-action/", + "Protocol Or Wallet Gate", + "createHostedVerifiedAgentRegistryMiddleware", + "https://svsprotocol.com/docs", + "https://registry.svsprotocol.com/registry.json", + "https://svsprotocol.com/docs#verified-agent-standard", + "https://svsprotocol.com/verify", + "SVS_RUN_LIVE_SUBMIT=true", + "no API keys" +]) { + assert.match(files.readme, new RegExp(escapeRegExp(required))); +} + +assert.match(files.readme, /The default run is import validation only/); +assert.doesNotMatch(files.readme, /import\/config validation only/); +assert.doesNotMatch(files.readme, /private SVS operator repository/); +assert.doesNotMatch(files.readme, /From the SVS repository/); + +for (const required of [ + "SVS_SERVER_URL", + "SVS_BOT_ID", + "SVS_BOT_POLICY_ID", + "SVS_BOT_API_KEY", + "SVS_BOT_REQUEST_SIGNING_SECRET", + "SVS_BOT_EXPECTED_INTEGRATION_CONTRACT_HASH", + "SOLANA_RPC_URL", + "SVS_SERIALIZED_TRANSACTION_BASE64", + "SVS_RUN_LIVE_SUBMIT=false" +]) { + assert.match(files.envExample, new RegExp(escapeRegExp(required))); +} + +for (const required of [ + "ai", + "zod", + "@svsprotocol/solana/vercel-ai", + "VERCEL_AI_SDK_ADAPTER_VERSION", + "VERCEL_AI_SDK_TOOL_NAME", + "createSvsVercelAiSdkTools", + "requireSvsProductionReady", + "verifyAndSubmitSolanaAction", + "assertLiveSubmitEnv", + "isDirectRun", + "fileURLToPath", + "SVS_SERIALIZED_TRANSACTION_BASE64", + "SVS_RUN_LIVE_SUBMIT" +]) { + assert.match(files.example, new RegExp(escapeRegExp(required))); +} + +for (const [name, text] of Object.entries({ + readme: files.readme, + envExample: files.envExample, + example: files.example +})) { + assertNoCommittedSecret(name, text); +} + +const dryRun = JSON.parse(execFileSync(process.execPath, [ + fileURLToPath(new URL("./svs-verified-action.mjs", import.meta.url)) +], { + encoding: "utf8" +})); + +assert.equal(dryRun.ok, true); +assert.equal(dryRun.status, "dry_run"); +assert.equal(dryRun.packageExport, "@svsprotocol/solana/vercel-ai"); +assert.equal(dryRun.toolName, "svsVerifyAndSubmitSolanaAction"); + +console.log(JSON.stringify({ + ok: true, + package: files.packageJson.name, + target: "vercel-ai-sdk", + packageExport: "@svsprotocol/solana/vercel-ai", + toolName: "svsVerifyAndSubmitSolanaAction", + dryRun: { + status: dryRun.status, + packageExport: dryRun.packageExport, + toolName: dryRun.toolName + } +}, null, 2)); + +function assertNoCommittedSecret(name, text) { + const secretPatterns = [ + /svs_live_[A-Za-z0-9_-]{8,}/, + /svs_req_live_[A-Za-z0-9_-]{8,}/, + /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/, + /\[[\d,\s]{80,}\]/ + ]; + + for (const pattern of secretPatterns) { + assert.doesNotMatch(text, pattern, `${name} includes secret-looking material`); + } +} + +function escapeRegExp(value) { + return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +}