diff --git a/.gitignore b/.gitignore index 29608a3fb..6afa1f4eb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ node_modules/ # Build output dist/ +.next/ +next-env.d.ts # Environment .env diff --git a/apps/cli/package.json b/apps/cli/package.json index 91ad3b04d..717811c6a 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -26,6 +26,7 @@ "dependencies": { "@antseed/api-adapter": "workspace:*", "@antseed/ant-agent": "workspace:*", + "@antseed/connect-core": "workspace:*", "@antseed/payments": "workspace:*", "@antseed/node": "workspace:*", "@antseed/provider-core": "workspace:*", @@ -37,6 +38,7 @@ "ora": "^9.3.0" }, "devDependencies": { + "@antseed/service-auto-deposit": "workspace:*", "@types/node": "^20.11.0", "tsx": "^4.7.0", "typescript": "^5.3.0" diff --git a/apps/cli/src/cli/commands/connect.ts b/apps/cli/src/cli/commands/connect.ts new file mode 100644 index 000000000..7b26f9cc1 --- /dev/null +++ b/apps/cli/src/cli/commands/connect.ts @@ -0,0 +1,118 @@ +import type { Command } from 'commander'; +import chalk from 'chalk'; +import open from 'open'; +import { createInterface } from 'node:readline/promises'; +import { + parseRequestLink, + resolveScopeValues, + signConnectResponse, + parseManifest, + SCOPES, + type ConnectManifest, + type AutoDepositState, +} from '@antseed/connect-core'; +import { getGlobalOptions } from './types.js'; +import { loadCryptoContext, requireCryptoConfig } from '../payment-utils.js'; +import { loadConfig } from '../../config/loader.js'; +import { loadAutoDepositConnectStateReader } from '../../plugins/service-state.js'; + +const MANIFEST_TIMEOUT_MS = 1500; + +/** Resolve the auto-deposit status to share over Connect: consent from config, + * delegation read on-chain. Best-effort: any failure reports off + undelegated. */ +async function autoDepositStateForConnect(configPath: string, address: string): Promise { + let enabled = false; + try { + const config = await loadConfig(configPath); + enabled = config.buyer?.services?.['auto-deposit']?.enabled ?? false; + const crypto = requireCryptoConfig(config); + const readAutoDepositConnectState = await loadAutoDepositConnectStateReader(); + if (!readAutoDepositConnectState) { + return { enabled, delegated: false }; + } + return await readAutoDepositConnectState({ + chainId: crypto.chainId, + rpcUrl: crypto.rpcUrl, + address, + enabled, + }); + } catch { + return { enabled, delegated: false }; + } +} + +/** Best-effort, display-only manifest fetch. Never a security input. */ +async function fetchManifest(origin: string): Promise { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), MANIFEST_TIMEOUT_MS); + try { + const res = await fetch(`${origin}/.well-known/antseed-connect.json`, { + signal: controller.signal, + redirect: 'error', + }); + if (!res.ok) return null; + return parseManifest(await res.text(), origin); + } catch { + return null; + } finally { + clearTimeout(timer); + } +} + +export function registerConnectCommand(program: Command): void { + program + .command('connect') + .description('Respond to an AntSeed Connect request link, sharing signed account info on consent') + .argument('', 'the antseed://connect (or https) request link') + .option('--yes', 'skip the consent prompt (non-interactive approval)', false) + .option('--print', 'print the redirect URL instead of opening a browser', false) + .action(async (link: string, options: { yes: boolean; print: boolean }) => { + try { + const globalOpts = getGlobalOptions(program); + + const request = parseRequestLink(link); + const { wallet } = await loadCryptoContext(globalOpts.dataDir); + const account = request.scopes.includes('auto-deposit') + ? { address: wallet.address, autoDeposit: await autoDepositStateForConnect(globalOpts.config, wallet.address) } + : wallet; + const values = resolveScopeValues(request, account); + + const manifest = await fetchManifest(request.origin); + + console.log(''); + if (manifest) { + console.log(`${chalk.bold('App:')} ${manifest.name}`); + } + console.log(`${chalk.bold('Origin:')} ${chalk.cyan(request.origin)}`); + console.log(`${chalk.bold('Request:')} Share the following with this app:`); + for (const scope of request.scopes) { + const def = SCOPES[scope]; + console.log(` ${chalk.bold(def.label)}: ${values[scope]}`); + console.log(` ${chalk.dim(def.description)}`); + } + console.log(''); + + if (!options.yes) { + const rl = createInterface({ input: process.stdin, output: process.stdout }); + const answer = await rl.question('Approve? [y/N]: '); + rl.close(); + const normalized = answer.trim().toLowerCase(); + if (normalized !== 'y' && normalized !== 'yes') { + console.log(chalk.yellow('Declined. Nothing shared.')); + return; + } + } + + const { fragmentUrl } = await signConnectResponse(wallet, request, values); + + console.log(chalk.green('Approved. Delivering signed response to:')); + console.log(fragmentUrl); + if (!options.print) { + await open(fragmentUrl); + } + } catch (err) { + console.error(chalk.red(`Error: ${(err as Error).message}`)); + process.exitCode = 1; + } + }); +} diff --git a/apps/cli/src/cli/index.ts b/apps/cli/src/cli/index.ts index ae46c5680..f6a87647b 100755 --- a/apps/cli/src/cli/index.ts +++ b/apps/cli/src/cli/index.ts @@ -12,6 +12,7 @@ import { registerDevCommand } from './commands/dev.js'; import { registerPaymentsCommand } from './commands/payments.js'; import { registerMetricsCommand } from './commands/metrics.js'; import { registerWrappedToolCommands } from './commands/wrapped-tools.js'; +import { registerConnectCommand } from './commands/connect.js'; loadEnvFromFiles(); @@ -38,5 +39,6 @@ registerAgentCommand(program); registerPaymentsCommand(program); registerMetricsCommand(program); registerWrappedToolCommands(program); +registerConnectCommand(program); program.parse(process.argv); diff --git a/apps/cli/src/plugins/service-state.ts b/apps/cli/src/plugins/service-state.ts new file mode 100644 index 000000000..898bf174f --- /dev/null +++ b/apps/cli/src/plugins/service-state.ts @@ -0,0 +1,32 @@ +import { existsSync } from 'node:fs' +import path, { join } from 'node:path' +import { pathToFileURL } from 'node:url' +import { getPluginsDir } from './manager.js' + +const AUTO_DEPOSIT_PACKAGE = '@antseed/service-auto-deposit' + +type ReadAutoDepositConnectState = + typeof import('@antseed/service-auto-deposit').readAutoDepositConnectState + +/** + * Resolve the auto-deposit connect-state reader from the installed plugin the + * same way loader.ts resolves any plugin: from the plugins dir, not a hard + * dependency. Returns null when the optional plugin is absent or fails to load, + * so `antseed connect` keeps working whether or not auto-deposit is installed. + */ +export async function loadAutoDepositConnectStateReader(): Promise { + const pluginsDir = getPluginsDir() + const resolved = path.resolve( + join(pluginsDir, 'node_modules', AUTO_DEPOSIT_PACKAGE, 'dist', 'index.js'), + ) + if (!resolved.startsWith(path.resolve(pluginsDir))) return null + if (!existsSync(resolved)) return null + try { + const mod = (await import(pathToFileURL(resolved).href)) as { + readAutoDepositConnectState?: ReadAutoDepositConnectState + } + return mod.readAutoDepositConnectState ?? null + } catch { + return null + } +} diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 3b92453cc..9650ba652 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -38,8 +38,10 @@ }, "dependencies": { "@antseed/api-adapter": "workspace:*", + "@antseed/connect-core": "workspace:*", "@antseed/node": "workspace:*", "@antseed/payments": "workspace:*", + "@antseed/service-auto-deposit": "workspace:*", "@antseed/ui": "workspace:*", "@fastify/cors": "^10.0.0", "@fastify/static": "^9.0.0", diff --git a/apps/desktop/src/main/connect.ts b/apps/desktop/src/main/connect.ts new file mode 100644 index 000000000..ef75ebdc9 --- /dev/null +++ b/apps/desktop/src/main/connect.ts @@ -0,0 +1,170 @@ +// AntSeed Connect deep-link handler, macOS. +// Parsing, manifest fetch, and signing all happen in the main process. +// The renderer only shows the request and returns the approval. + +import { app, shell, ipcMain, type BrowserWindow } from 'electron'; +import { randomUUID } from 'node:crypto'; +import { + parseRequestLink, + resolveScopeValues, + signConnectResponse, + parseManifest, + SCOPES, + type ConnectRequest, + type ScopeAccount, + type AutoDepositState, +} from '@antseed/connect-core'; +import type { Identity } from '@antseed/node'; + +export interface ConnectDeps { + getMainWindow: () => BrowserWindow | null; + ensureWindow: () => void; + ensureIdentity: () => Promise; + getIdentity: () => Identity | null; + getAutoDepositState?: (address: string) => Promise; + log?: (line: string) => void; +} + +const MANIFEST_TIMEOUT_MS = 1500; + +interface PendingConnect { + request: ConnectRequest; + values: Record; +} + +let ready = false; +let pendingUrl: string | null = null; +const pendingRequests = new Map(); + +/** Best-effort, display-only manifest fetch. Never a security input. */ +async function fetchManifest(origin: string): Promise<{ name: string; icon?: string } | null> { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), MANIFEST_TIMEOUT_MS); + try { + const res = await fetch(`${origin}/.well-known/antseed-connect.json`, { + signal: controller.signal, + redirect: 'error', + }); + if (!res.ok) return null; + const manifest = parseManifest(await res.text(), origin); + return { name: manifest.name, ...(manifest.icon ? { icon: manifest.icon } : {}) }; + } catch { + return null; + } finally { + clearTimeout(timer); + } +} + +async function handleConnectUrl(url: string, deps: ConnectDeps) { + let request: ConnectRequest; + try { + request = parseRequestLink(url); + } catch (err) { + deps.log?.(`[connect] rejected request link: ${(err as Error).message}`); + return; + } + + deps.ensureWindow(); + const win = deps.getMainWindow(); + if (!win) { + deps.log?.('[connect] no window available; cannot show consent prompt'); + return; + } + win.show(); + win.focus(); + + await deps.ensureIdentity(); + const identity = deps.getIdentity(); + if (!identity) { + deps.log?.('[connect] identity unavailable; cannot answer request'); + return; + } + + let account: ScopeAccount = identity.wallet; + if (request.scopes.includes('auto-deposit') && deps.getAutoDepositState) { + const autoDeposit = await deps.getAutoDepositState(identity.wallet.address); + if (autoDeposit) account = { address: identity.wallet.address, autoDeposit }; + } + const values = resolveScopeValues(request, account); + const manifest = await fetchManifest(request.origin); + + const id = randomUUID(); + pendingRequests.set(id, { request, values }); + // Don't leak the pending entry if the window goes away before the user decides. + win.webContents.once('destroyed', () => pendingRequests.delete(id)); + + win.webContents.send('connect:request', { + id, + origin: request.origin, + appName: manifest?.name ?? null, + appIcon: manifest?.icon ?? null, + scopes: request.scopes.map((scope) => ({ + id: scope, + label: SCOPES[scope].label, + description: SCOPES[scope].description, + value: values[scope], + })), + }); +} + +async function respondToConnect( + payload: { id: string; approved: boolean }, + deps: ConnectDeps, +): Promise<{ ok: boolean; delivered: boolean; error?: string }> { + const pending = pendingRequests.get(payload.id); + if (!pending) { + return { ok: false, delivered: false, error: 'unknown request' }; + } + pendingRequests.delete(payload.id); + + if (!payload.approved) { + return { ok: true, delivered: false }; + } + + const identity = deps.getIdentity(); + if (!identity) { + return { ok: false, delivered: false, error: 'identity unavailable' }; + } + + try { + const { fragmentUrl } = await signConnectResponse(identity.wallet, pending.request, pending.values); + await shell.openExternal(fragmentUrl); + return { ok: true, delivered: true }; + } catch (err) { + deps.log?.(`[connect] failed to deliver response: ${(err as Error).message}`); + return { ok: false, delivered: false, error: (err as Error).message }; + } +} + +/** + * Register the deep-link handler. Call at module init (before app ready) so the + * `open-url` listener is in place to catch a cold-start launch. Links that + * arrive before {@link markConnectReady} are buffered. + */ +export function initConnectDeepLink(deps: ConnectDeps): void { + app.setAsDefaultProtocolClient('antseed'); + + app.on('open-url', (event, url) => { + event.preventDefault(); + if (!ready) { + // open-url can fire before the app is ready on macOS cold start; hold it. + pendingUrl = url; + return; + } + void handleConnectUrl(url, deps); + }); + + ipcMain.handle('connect:respond', (_event, payload: { id: string; approved: boolean }) => + respondToConnect(payload, deps), + ); +} + +/** Flush any links that arrived before the window/identity were ready. */ +export function markConnectReady(deps: ConnectDeps): void { + ready = true; + if (pendingUrl !== null) { + const url = pendingUrl; + pendingUrl = null; + void handleConnectUrl(url, deps); + } +} diff --git a/apps/desktop/src/main/main.ts b/apps/desktop/src/main/main.ts index 709605efa..1afea8a7f 100644 --- a/apps/desktop/src/main/main.ts +++ b/apps/desktop/src/main/main.ts @@ -20,6 +20,7 @@ import { } from './process-manager.js'; import { registerPiChatHandlers, invalidateOnChainEnrichmentCache } from './pi-chat-engine.js'; import { ensureSecureIdentity, secureIdentityEnv, getSecureIdentity } from './identity.js'; +import { initConnectDeepLink, markConnectReady, type ConnectDeps } from './connect.js'; import { DepositsClient, signSpendingAuth, makeChannelsDomain, resolveChainConfig, formatUsdc, peerIdToAddress } from '@antseed/node'; import { createServer as createPaymentsServer } from '@antseed/payments'; import type { LogEvent, RuntimeActivityEvent } from './log-parser.js'; @@ -1001,6 +1002,46 @@ ipcMain.handle('runtime:scan-network', async () => { } }); +const connectDeps: ConnectDeps = { + getMainWindow, + ensureWindow: () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow({ appName: APP_NAME, appIconPath: APP_ICON_PATH, isDev, rendererUrl }); + } + }, + ensureIdentity: ensureSecureIdentity, + getIdentity: getSecureIdentity, + getAutoDepositState: async (address: string) => { + let enabled = false; + try { + const config = await readConfig(ACTIVE_CONFIG_PATH); + const crypto = asRecord(asRecord(config.payments).crypto); + // Use the configured chain as-is (matches the CLI); an unset/unsupported + // chain just yields delegated:false rather than assuming base-mainnet. + const chainId = asString(crypto.chainId, ''); + const services = asRecord(asRecord(config.buyer).services); + enabled = asRecord(services['auto-deposit']).enabled === true; + const cryptoConfig = await loadCachedCryptoConfig(); + // Optional plugin: loaded lazily so a missing or broken copy degrades to + // delegated:false instead of crashing the main process at startup. + const { readAutoDepositConnectState } = await import('@antseed/service-auto-deposit'); + return await readAutoDepositConnectState({ + chainId, + rpcUrl: cryptoConfig?.rpcUrl ?? '', + address, + enabled, + }); + } catch { + return { enabled, delegated: false }; + } + }, + log: (line) => appendLog('connect', 'system', line), +}; + +// Register the antseed:// open-url listener before app ready so a cold-start +// launch from a deep link is caught and buffered. +initConnectDeepLink(connectDeps); + app.whenReady().then(async () => { installAttachmentProtocol(); app.setName(APP_NAME); @@ -1022,6 +1063,10 @@ app.whenReady().then(async () => { createWindow({ appName: APP_NAME, appIconPath: APP_ICON_PATH, isDev, rendererUrl }); + // Window exists and identity preload is kicked off below: flush any deep links + // that arrived during cold start. + markConnectReady(connectDeps); + // Pre-load identity from encrypted store so it's ready before the first CLI spawn. void ensureSecureIdentity().catch(() => { // Failure is logged inside ensureSecureIdentity; CLI falls back to file-based identity. diff --git a/apps/desktop/src/main/preload.cts b/apps/desktop/src/main/preload.cts index 91a958869..d59aa5177 100644 --- a/apps/desktop/src/main/preload.cts +++ b/apps/desktop/src/main/preload.cts @@ -445,6 +445,32 @@ const api = { creditsGetInfo() { return ipcRenderer.invoke('credits:get-info'); }, + // AntSeed Connect: consent-based account info sharing + onConnectRequest( + handler: (data: { + id: string; + origin: string; + appName: string | null; + appIcon: string | null; + scopes: { id: string; label: string; description: string; value: string }[]; + }) => void, + ): () => void { + const listener = ( + _: unknown, + data: { + id: string; + origin: string; + appName: string | null; + appIcon: string | null; + scopes: { id: string; label: string; description: string; value: string }[]; + }, + ) => handler(data); + ipcRenderer.on('connect:request', listener); + return () => ipcRenderer.off('connect:request', listener); + }, + connectRespond(id: string, approved: boolean): Promise<{ ok: boolean; delivered: boolean; error?: string }> { + return ipcRenderer.invoke('connect:respond', { id, approved }) as Promise<{ ok: boolean; delivered: boolean; error?: string }>; + }, paymentsSignSpendingAuth: (params: unknown) => ipcRenderer.invoke('payments:sign-spending-auth', params), paymentsGetPeerInfo: (peerId: string) => ipcRenderer.invoke('payments:get-peer-info', peerId), paymentsOpenPortal: (tab?: string) => ipcRenderer.invoke('payments:open-portal', tab), diff --git a/apps/desktop/src/renderer/global.scss b/apps/desktop/src/renderer/global.scss index 20e2c8f16..f354faadc 100644 --- a/apps/desktop/src/renderer/global.scss +++ b/apps/desktop/src/renderer/global.scss @@ -81,6 +81,22 @@ body.dark-theme { --drop-overlay-bg: rgba(28, 28, 30, 0.82); --scrollbar-thumb: rgba(255, 255, 255, 0.1); --scrollbar-thumb-hover: rgba(255, 255, 255, 0.18); + + // @antseed/ui maps its --as-* tokens off our base tokens, but it declares them + // on :root, so they capture the light values there and don't see this dark + // override (which lives on body.dark-theme). Re-map them here so primitives + // like Modal recompute with the dark palette. Without this, every as-modal + // renders light-on-dark. + --as-bg-primary: var(--bg-primary); + --as-bg-surface: var(--bg-card); + --as-bg-muted: var(--bg-surface); + --as-bg-hover: var(--bg-hover); + --as-text-primary: var(--text-primary); + --as-text-secondary: var(--text-secondary); + --as-text-muted: var(--text-muted); + --as-border: var(--border); + --as-border-strong: var(--border-strong); + --as-danger: var(--danger); } /* =================================================================== diff --git a/apps/desktop/src/renderer/types/bridge.ts b/apps/desktop/src/renderer/types/bridge.ts index dd4ad0717..2f75d2236 100644 --- a/apps/desktop/src/renderer/types/bridge.ts +++ b/apps/desktop/src/renderer/types/bridge.ts @@ -262,4 +262,15 @@ export type DesktopBridge = { }>; paymentsOpenPortal?: (tab?: string) => Promise<{ ok: boolean; url?: string; error?: string }>; + + onConnectRequest?: ( + handler: (data: { + id: string; + origin: string; + appName: string | null; + appIcon: string | null; + scopes: { id: string; label: string; description: string; value: string }[]; + }) => void, + ) => () => void; + connectRespond?: (id: string, approved: boolean) => Promise<{ ok: boolean; delivered: boolean; error?: string }>; }; diff --git a/apps/desktop/src/renderer/ui/AppShell.tsx b/apps/desktop/src/renderer/ui/AppShell.tsx index 9a1d1c454..68247e77b 100644 --- a/apps/desktop/src/renderer/ui/AppShell.tsx +++ b/apps/desktop/src/renderer/ui/AppShell.tsx @@ -5,6 +5,7 @@ import { TitleBar } from './components/TitleBar'; import { ViewHost } from './components/ViewHost'; import { DiscoverWelcome } from './components/chat/DiscoverWelcome'; import { SetupScreen } from './components/SetupScreen'; +import { ConnectConsentDialog } from './components/ConnectConsentDialog'; import { useUiSnapshot } from './hooks/useUiSnapshot'; import { useActions } from './hooks/useActions'; import type { ViewName } from './types'; @@ -122,6 +123,7 @@ export function AppShell() { + ); } diff --git a/apps/desktop/src/renderer/ui/components/ConnectConsentDialog.module.scss b/apps/desktop/src/renderer/ui/components/ConnectConsentDialog.module.scss new file mode 100644 index 000000000..5a45f11ed --- /dev/null +++ b/apps/desktop/src/renderer/ui/components/ConnectConsentDialog.module.scss @@ -0,0 +1,88 @@ +.body { + display: flex; + flex-direction: column; + gap: 12px; +} + +.header { + display: flex; + flex-direction: column; + gap: 2px; +} + +.appRow { + display: flex; + align-items: center; + gap: 8px; +} + +.icon { + width: 24px; + height: 24px; + border-radius: 6px; +} + +.appName { + font-weight: 600; + color: var(--text-primary); +} + +.origin { + margin: 0; + font-family: var(--font-mono, monospace); + color: var(--text-secondary); + word-break: break-all; + line-height: 1.4; +} + +.prompt { + margin: 0; + color: var(--text-secondary); + line-height: 1.4; +} + +.scopes { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 16px; + + li { + display: flex; + flex-direction: column; + gap: 2px; + } +} + +.scopeLabel { + font-weight: 600; + color: var(--text-primary); + line-height: 1.4; +} + +.scopeValue { + display: block; + font-family: var(--font-mono, monospace); + background: var(--bg-surface); + color: var(--text-primary); + border: 1px solid var(--border); + border-radius: 6px; + padding: 6px 8px; + word-break: break-all; + line-height: 1.4; +} + +.scopeDescription { + font-size: 0.85em; + color: var(--text-muted); + line-height: 1.4; +} + +.actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 8px; +} diff --git a/apps/desktop/src/renderer/ui/components/ConnectConsentDialog.tsx b/apps/desktop/src/renderer/ui/components/ConnectConsentDialog.tsx new file mode 100644 index 000000000..ae6d14334 --- /dev/null +++ b/apps/desktop/src/renderer/ui/components/ConnectConsentDialog.tsx @@ -0,0 +1,81 @@ +import { useEffect, useState } from 'react'; +import { Button, Modal } from '@antseed/ui'; +import styles from './ConnectConsentDialog.module.scss'; + +type ConnectScope = { + id: string; + label: string; + description: string; + value: string; +}; + +type ConnectRequestData = { + id: string; + origin: string; + appName: string | null; + appIcon: string | null; + scopes: ConnectScope[]; +}; + +/** + * Consent prompt for an AntSeed Connect request. Display and decision only. + * The wallet and signing stay in the main process. + */ +export function ConnectConsentDialog() { + const [request, setRequest] = useState(null); + const [busy, setBusy] = useState(false); + + useEffect(() => window.antseedDesktop?.onConnectRequest?.((data) => setRequest(data)), []); + + const respond = async (approved: boolean) => { + if (!request || busy) return; + setBusy(true); + try { + await window.antseedDesktop?.connectRespond?.(request.id, approved); + } finally { + setBusy(false); + setRequest(null); + } + }; + + return ( + void respond(false)} + size="md" + title="Share account info?" + > + {request && ( +
+
+ {request.appName && ( +
+ {request.appIcon && } + {request.appName} +
+ )} +

{request.origin}

+

wants to read your AntSeed account info:

+
+
    + {request.scopes.map((scope) => ( +
  • +
    {scope.label}
    + {scope.value} +
    {scope.description}
    +
  • + ))} +
+
+ + +
+
+ )} +
+ ); +} diff --git a/package.json b/package.json index 4a047a2e9..6505c6186 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "packageManager": "pnpm@9.15.4", "scripts": { "build": "pnpm run build:tier0 && pnpm run build:tier1 && pnpm run build:tier2 && pnpm run build:tier3 && pnpm run build:tier4 && pnpm run build:standalone", - "build:tier0": "pnpm --filter=@antseed/ui run build && pnpm --filter=@antseed/api-adapter run build && pnpm --filter=@antseed/node run build", + "build:tier0": "pnpm --filter=@antseed/ui run build && pnpm --filter=@antseed/api-adapter run build && pnpm --filter=@antseed/connect-core run build && pnpm --filter=@antseed/node run build", "build:tier1": "pnpm --filter=@antseed/provider-core --filter=@antseed/router-core --filter=@antseed/ant-agent --filter=@antseed/service-core run build", "build:tier2": "pnpm --filter \"./plugins/*\" run build", "build:tier3": "pnpm --filter=@antseed/payments run build", @@ -27,10 +27,10 @@ "test": "pnpm -r run test", "test:e2e": "pnpm --filter=@antseed/e2e run test", "typecheck": "pnpm -r run typecheck", - "clean": "node ./scripts/remove-paths.mjs apps/cli/dist apps/desktop/dist apps/diem-staking/dist apps/network-stats/dist apps/website/build e2e/dist packages/api-adapter/dist packages/service-core/dist packages/bound-agent/dist packages/node/dist packages/provider-core/dist packages/router-core/dist plugins/provider-anthropic/dist plugins/provider-claude-code/dist plugins/provider-claude-oauth/dist plugins/provider-local-llm/dist plugins/provider-openai/dist plugins/provider-openai-responses/dist plugins/router-local/dist plugins/service-auto-deposit/dist", + "clean": "node ./scripts/remove-paths.mjs apps/cli/dist apps/desktop/dist apps/diem-staking/dist apps/network-stats/dist apps/website/build e2e/dist packages/api-adapter/dist packages/connect-core/dist packages/service-core/dist packages/bound-agent/dist packages/node/dist packages/provider-core/dist packages/router-core/dist plugins/provider-anthropic/dist plugins/provider-claude-code/dist plugins/provider-claude-oauth/dist plugins/provider-local-llm/dist plugins/provider-openai/dist plugins/provider-openai-responses/dist plugins/router-local/dist plugins/service-auto-deposit/dist", "publish:all": "pnpm run build && pnpm -r publish --no-git-checks", "publish:dry": "pnpm run build && pnpm -r publish --no-git-checks --dry-run", - "clean:all": "node ./scripts/remove-paths.mjs node_modules apps/cli/node_modules apps/desktop/node_modules apps/diem-staking/node_modules apps/network-stats/node_modules apps/website/node_modules e2e/node_modules packages/api-adapter/node_modules packages/service-core/node_modules packages/bound-agent/node_modules packages/node/node_modules packages/provider-core/node_modules packages/router-core/node_modules plugins/provider-anthropic/node_modules plugins/provider-claude-code/node_modules plugins/provider-claude-oauth/node_modules plugins/provider-local-llm/node_modules plugins/provider-openai/node_modules plugins/provider-openai-responses/node_modules plugins/router-local/node_modules plugins/service-auto-deposit/node_modules", + "clean:all": "node ./scripts/remove-paths.mjs node_modules apps/cli/node_modules apps/desktop/node_modules apps/diem-staking/node_modules apps/network-stats/node_modules apps/website/node_modules e2e/node_modules packages/api-adapter/node_modules packages/connect-core/node_modules packages/service-core/node_modules packages/bound-agent/node_modules packages/node/node_modules packages/provider-core/node_modules packages/router-core/node_modules plugins/provider-anthropic/node_modules plugins/provider-claude-code/node_modules plugins/provider-claude-oauth/node_modules plugins/provider-local-llm/node_modules plugins/provider-openai/node_modules plugins/provider-openai-responses/node_modules plugins/router-local/node_modules plugins/service-auto-deposit/node_modules", "dev:website": "pnpm --filter=@antseed/website run dev", "dev:desktop": "pnpm run build:tier0 && pnpm run build:tier1 && pnpm run build:tier2 && pnpm run build:tier3 && pnpm --filter=@antseed/cli run build && pnpm --filter=@antseed/desktop run dev" diff --git a/packages/connect-core/package.json b/packages/connect-core/package.json new file mode 100644 index 000000000..76e6a3979 --- /dev/null +++ b/packages/connect-core/package.json @@ -0,0 +1,24 @@ +{ + "name": "@antseed/connect-core", + "version": "0.1.0", + "description": "AntSeed Connect protocol core: parse, sign, and verify consent-based account info sharing", + "type": "module", + "files": [ + "dist" + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "prebuild": "node ../../scripts/remove-paths.mjs dist", + "build": "tsc", + "test": "vitest run", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "ethers": "~6.16.0" + }, + "devDependencies": { + "typescript": "^5.5.0", + "vitest": "^2.0.0" + } +} diff --git a/packages/connect-core/src/fragment.ts b/packages/connect-core/src/fragment.ts new file mode 100644 index 000000000..3d98aa66d --- /dev/null +++ b/packages/connect-core/src/fragment.ts @@ -0,0 +1,19 @@ +import type { ConnectResponse } from './types.js'; + +export function encodeResponseFragment(response: ConnectResponse): string { + const json = JSON.stringify(response); + return Buffer.from(json, 'utf8').toString('base64url'); +} + +export function decodeResponseFragment(encoded: string): unknown { + const json = Buffer.from(encoded, 'base64url').toString('utf8'); + return JSON.parse(json); +} + +/** + * Build the delivery URL: the redirect URL with the response in the fragment, so + * it is never sent to the server or leaked through referrers. + */ +export function buildFragmentUrl(redirect: string, response: ConnectResponse): string { + return `${redirect}#result=${encodeResponseFragment(response)}`; +} diff --git a/packages/connect-core/src/index.ts b/packages/connect-core/src/index.ts new file mode 100644 index 000000000..90dbf5966 --- /dev/null +++ b/packages/connect-core/src/index.ts @@ -0,0 +1,25 @@ +export { + CONNECT_VERSION, + type ScopeId, + type ConnectRequest, + type ConnectResponse, + type ConnectManifest, + type ConnectSigner, + type ScopeAccount, + type AutoDepositState, +} from './types.js'; +export { SCOPES, isScopeId, type ScopeDef } from './scopes.js'; +export { parseRequestLink, ConnectRequestError } from './request-link.js'; +export { buildSignedMessage } from './message.js'; +export { + signConnectResponse, + verifyConnectResponse, + resolveScopeValues, + ConnectResponseError, +} from './response.js'; +export { parseManifest, ConnectManifestError } from './manifest.js'; +export { + encodeResponseFragment, + decodeResponseFragment, + buildFragmentUrl, +} from './fragment.js'; diff --git a/packages/connect-core/src/manifest.test.ts b/packages/connect-core/src/manifest.test.ts new file mode 100644 index 000000000..a16ed5916 --- /dev/null +++ b/packages/connect-core/src/manifest.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect } from 'vitest'; +import { parseManifest, ConnectManifestError } from './manifest.js'; + +const ORIGIN = 'https://app.example'; + +function manifest(overrides: Record = {}): string { + return JSON.stringify({ + version: 1, + kind: 'antseed.connect.manifest', + name: 'Example App', + homepage: 'https://app.example', + icon: 'https://app.example/icon.png', + ...overrides, + }); +} + +describe('parseManifest', () => { + it('parses a valid same-origin manifest', () => { + const m = parseManifest(manifest(), ORIGIN); + expect(m.name).toBe('Example App'); + expect(m.homepage).toBe('https://app.example'); + expect(m.icon).toBe('https://app.example/icon.png'); + }); + + it('treats icon as optional', () => { + const m = parseManifest(manifest({ icon: undefined }), ORIGIN); + expect(m.icon).toBeUndefined(); + }); + + it('rejects invalid JSON', () => { + expect(() => parseManifest('{not json', ORIGIN)).toThrow(ConnectManifestError); + }); + + it('rejects a bad version', () => { + expect(() => parseManifest(manifest({ version: 2 }), ORIGIN)).toThrow(/version/); + }); + + it('rejects a bad kind', () => { + expect(() => parseManifest(manifest({ kind: 'other' }), ORIGIN)).toThrow(/kind/); + }); + + it('rejects a missing name', () => { + expect(() => parseManifest(manifest({ name: '' }), ORIGIN)).toThrow(/name/); + }); + + it('rejects a cross-origin homepage', () => { + expect(() => parseManifest(manifest({ homepage: 'https://evil.example' }), ORIGIN)).toThrow( + /same-origin/, + ); + }); + + it('rejects a cross-origin icon', () => { + expect(() => + parseManifest(manifest({ icon: 'https://cdn.other.example/icon.png' }), ORIGIN), + ).toThrow(/same-origin/); + }); + + it('rejects a non-HTTPS homepage', () => { + expect(() => + parseManifest(manifest({ homepage: 'http://app.example' }), 'http://app.example'), + ).toThrow(/HTTPS/); + }); +}); diff --git a/packages/connect-core/src/manifest.ts b/packages/connect-core/src/manifest.ts new file mode 100644 index 000000000..010f5d7c2 --- /dev/null +++ b/packages/connect-core/src/manifest.ts @@ -0,0 +1,79 @@ +import { CONNECT_VERSION, type ConnectManifest } from './types.js'; + +export class ConnectManifestError extends Error { + constructor(message: string) { + super(message); + this.name = 'ConnectManifestError'; + } +} + +const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']); + +function assertSameOriginHttps(url: string, origin: string, field: string) { + let parsed: URL; + try { + parsed = new URL(url); + } catch { + throw new ConnectManifestError(`manifest ${field} is not a valid URL`); + } + if (parsed.protocol !== 'https:') { + const loopback = parsed.protocol === 'http:' && LOOPBACK_HOSTS.has(parsed.hostname.toLowerCase()); + if (!loopback) { + throw new ConnectManifestError(`manifest ${field} must be HTTPS`); + } + } + if (parsed.origin !== origin) { + throw new ConnectManifestError(`manifest ${field} must be same-origin with ${origin}`); + } +} + +/** + * Parse and validate a web app manifest. The manifest is + * display-only: it never carries a security decision. `origin` is the request + * origin the manifest was fetched from. + * + * @throws {ConnectManifestError} on any failed check. + */ +export function parseManifest(jsonText: string, origin: string): ConnectManifest { + let parsed: unknown; + try { + parsed = JSON.parse(jsonText); + } catch { + throw new ConnectManifestError('manifest is not valid JSON'); + } + if (typeof parsed !== 'object' || parsed === null) { + throw new ConnectManifestError('manifest is not an object'); + } + const m = parsed as Record; + + if (m['version'] !== CONNECT_VERSION) { + throw new ConnectManifestError('unsupported manifest version'); + } + if (m['kind'] !== 'antseed.connect.manifest') { + throw new ConnectManifestError('unexpected manifest kind'); + } + if (typeof m['name'] !== 'string' || m['name'].length === 0) { + throw new ConnectManifestError('manifest name is required'); + } + if (typeof m['homepage'] !== 'string') { + throw new ConnectManifestError('manifest homepage is required'); + } + assertSameOriginHttps(m['homepage'], origin, 'homepage'); + + const manifest: ConnectManifest = { + version: CONNECT_VERSION, + kind: 'antseed.connect.manifest', + name: m['name'], + homepage: m['homepage'], + }; + + if (m['icon'] !== undefined) { + if (typeof m['icon'] !== 'string') { + throw new ConnectManifestError('manifest icon must be a string'); + } + assertSameOriginHttps(m['icon'], origin, 'icon'); + manifest.icon = m['icon']; + } + + return manifest; +} diff --git a/packages/connect-core/src/message.ts b/packages/connect-core/src/message.ts new file mode 100644 index 000000000..1d6bfd3a3 --- /dev/null +++ b/packages/connect-core/src/message.ts @@ -0,0 +1,28 @@ +import type { ConnectRequest } from './types.js'; + +/** + * Build the exact UTF-8 message that is signed with EIP-191 personal_sign. + * The first line is the only domain separator and MUST be reproduced exactly. + * Line endings are LF with no trailing blank line. One value line follows + * `scopes:` for each shared scope, in request order. + */ +export function buildSignedMessage( + req: ConnectRequest, + values: Record, +): string { + const lines = [ + 'AntSeed Connect', + `version: ${req.version}`, + `redirect: ${req.redirect}`, + `challenge: ${req.challenge}`, + `scopes: ${req.scopes.join(',')}`, + ]; + for (const scope of req.scopes) { + const value = values[scope]; + if (value === undefined) { + throw new Error(`missing value for scope: ${scope}`); + } + lines.push(`${scope}: ${value}`); + } + return lines.join('\n'); +} diff --git a/packages/connect-core/src/request-link.test.ts b/packages/connect-core/src/request-link.test.ts new file mode 100644 index 000000000..46c7b4707 --- /dev/null +++ b/packages/connect-core/src/request-link.test.ts @@ -0,0 +1,118 @@ +import { describe, it, expect } from 'vitest'; +import { parseRequestLink, ConnectRequestError } from './request-link.js'; + +const CHALLENGE = 'kJ8s9fK2mNpQrStUvWxYz0123456789AbCdEfGhIjKl'; + +function link(params: Record): string { + const qs = new URLSearchParams(params).toString(); + return `antseed://connect?${qs}`; +} + +const valid: Record = { + version: '1', + redirect: 'https://app.example/connect/cb', + scopes: 'address', + challenge: CHALLENGE, +}; + +describe('parseRequestLink', () => { + it('parses a valid antseed:// link and derives origin from redirect', () => { + const req = parseRequestLink(link(valid)); + expect(req.version).toBe(1); + expect(req.redirect).toBe('https://app.example/connect/cb'); + expect(req.origin).toBe('https://app.example'); + expect(req.scopes).toEqual(['address']); + expect(req.challenge).toBe(CHALLENGE); + }); + + it('accepts the https-scheme equivalent carrying the same query', () => { + const qs = new URLSearchParams(valid).toString(); + const req = parseRequestLink(`https://app.example/launch?${qs}`); + expect(req.origin).toBe('https://app.example'); + }); + + it('derives origin including a non-default port', () => { + const req = parseRequestLink( + link({ ...valid, redirect: 'https://app.example:8443/cb' }), + ); + expect(req.origin).toBe('https://app.example:8443'); + }); + + it('allows http loopback redirects for development', () => { + for (const host of ['127.0.0.1', 'localhost', '[::1]']) { + const req = parseRequestLink( + link({ ...valid, redirect: `http://${host}:3000/cb` }), + ); + expect(req.origin).toContain(host === '[::1]' ? '[::1]' : host); + } + }); + + it('rejects a non-1 version', () => { + expect(() => parseRequestLink(link({ ...valid, version: '2' }))).toThrow(ConnectRequestError); + }); + + it('rejects a missing version', () => { + const { version: _v, ...rest } = valid; + expect(() => parseRequestLink(link(rest))).toThrow(/version/); + }); + + it('rejects a missing redirect', () => { + const { redirect: _r, ...rest } = valid; + expect(() => parseRequestLink(link(rest))).toThrow(/redirect/); + }); + + it('rejects a non-HTTPS non-loopback redirect', () => { + expect(() => parseRequestLink(link({ ...valid, redirect: 'http://evil.example/cb' }))).toThrow( + /loopback/, + ); + }); + + it('rejects a redirect with userinfo', () => { + expect(() => + parseRequestLink(link({ ...valid, redirect: 'https://user:pass@app.example/cb' })), + ).toThrow(/username or password/); + }); + + it('rejects a redirect with a fragment', () => { + expect(() => + parseRequestLink(link({ ...valid, redirect: 'https://app.example/cb#frag' })), + ).toThrow(/fragment/); + }); + + it('rejects an unknown scope', () => { + expect(() => parseRequestLink(link({ ...valid, scopes: 'secrets' }))).toThrow(/unknown scope/); + }); + + it('rejects duplicate scopes', () => { + expect(() => parseRequestLink(link({ ...valid, scopes: 'address,address' }))).toThrow( + /duplicate scope/, + ); + }); + + it('rejects missing scopes', () => { + const { scopes: _s, ...rest } = valid; + expect(() => parseRequestLink(link(rest))).toThrow(/scopes/); + }); + + it('rejects a missing challenge', () => { + const { challenge: _c, ...rest } = valid; + expect(() => parseRequestLink(link(rest))).toThrow(/challenge/); + }); + + it('rejects a challenge with control characters (signed-message injection)', () => { + // URLSearchParams percent-encodes the newline; parseRequestLink decodes it back. + expect(() => + parseRequestLink(link({ ...valid, challenge: 'abc\naddress: 0xEVIL' })), + ).toThrow(/control characters/); + }); + + it('rejects a redirect with control characters', () => { + expect(() => + parseRequestLink(link({ ...valid, redirect: 'https://app.example/cb\nx' })), + ).toThrow(/control characters/); + }); + + it('rejects a non-URL input', () => { + expect(() => parseRequestLink('not a url')).toThrow(ConnectRequestError); + }); +}); diff --git a/packages/connect-core/src/request-link.ts b/packages/connect-core/src/request-link.ts new file mode 100644 index 000000000..b5f654941 --- /dev/null +++ b/packages/connect-core/src/request-link.ts @@ -0,0 +1,116 @@ +import { CONNECT_VERSION, type ConnectRequest, type ScopeId } from './types.js'; +import { isScopeId } from './scopes.js'; + +export class ConnectRequestError extends Error { + constructor(message: string) { + super(message); + this.name = 'ConnectRequestError'; + } +} + +const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']); + +// These params are copied verbatim into the EIP-191 signed message, so an ASCII +// control char (newline/tab/etc.) could inject an extra line and make the signed +// bytes ambiguous. Reject anything below U+0020 or DEL. +function hasControlChar(value: string): boolean { + for (let index = 0; index < value.length; index++) { + const code = value.charCodeAt(index); + if (code < 0x20 || code === 0x7f) return true; + } + return false; +} + +function isLoopback(url: URL) { + return LOOPBACK_HOSTS.has(url.hostname.toLowerCase()); +} + +/** + * Parse and validate a request link. Accepts the `antseed://connect` + * deep link or its `https`-scheme equivalent carrying the same query. + * + * The requesting origin is derived solely from the redirect URL via the WHATWG + * URL parser, so it cannot be spoofed by a separate link parameter. + * + * @throws {ConnectRequestError} on any rejected link. + */ +export function parseRequestLink(input: string): ConnectRequest { + let link: URL; + try { + link = new URL(input); + } catch { + throw new ConnectRequestError('request link is not a valid URL'); + } + + const params = link.searchParams; + + const version = params.get('version'); + if (version !== String(CONNECT_VERSION)) { + throw new ConnectRequestError(`unsupported version: ${version ?? '(missing)'}`); + } + + const redirectRaw = params.get('redirect'); + if (!redirectRaw) { + throw new ConnectRequestError('missing redirect'); + } + if (hasControlChar(redirectRaw)) { + throw new ConnectRequestError('redirect must not contain control characters'); + } + let redirectUrl: URL; + try { + redirectUrl = new URL(redirectRaw); + } catch { + throw new ConnectRequestError('redirect is not a valid URL'); + } + + if (redirectUrl.username || redirectUrl.password) { + throw new ConnectRequestError('redirect must not contain a username or password'); + } + if (redirectUrl.hash) { + throw new ConnectRequestError('redirect must not contain a fragment'); + } + if (redirectUrl.protocol === 'http:') { + if (!isLoopback(redirectUrl)) { + throw new ConnectRequestError('non-HTTPS redirect is only allowed for loopback development'); + } + } else if (redirectUrl.protocol !== 'https:') { + throw new ConnectRequestError(`redirect must use https (got ${redirectUrl.protocol})`); + } + + const scopesRaw = params.get('scopes'); + if (!scopesRaw) { + throw new ConnectRequestError('missing scopes'); + } + const scopeIds = scopesRaw.split(','); + const scopes: ScopeId[] = []; + const seen = new Set(); + for (const id of scopeIds) { + if (!id) { + throw new ConnectRequestError('empty scope id'); + } + if (seen.has(id)) { + throw new ConnectRequestError(`duplicate scope: ${id}`); + } + if (!isScopeId(id)) { + throw new ConnectRequestError(`unknown scope: ${id}`); + } + seen.add(id); + scopes.push(id); + } + + const challenge = params.get('challenge'); + if (!challenge) { + throw new ConnectRequestError('missing challenge'); + } + if (hasControlChar(challenge)) { + throw new ConnectRequestError('challenge must not contain control characters'); + } + + return { + version: CONNECT_VERSION, + redirect: redirectRaw, + origin: redirectUrl.origin, + scopes, + challenge, + }; +} diff --git a/packages/connect-core/src/response.test.ts b/packages/connect-core/src/response.test.ts new file mode 100644 index 000000000..9a003267f --- /dev/null +++ b/packages/connect-core/src/response.test.ts @@ -0,0 +1,159 @@ +import { describe, it, expect } from 'vitest'; +import { Wallet } from 'ethers'; +import { parseRequestLink } from './request-link.js'; +import { buildSignedMessage } from './message.js'; +import { + signConnectResponse, + verifyConnectResponse, + resolveScopeValues, + ConnectResponseError, +} from './response.js'; +import { decodeResponseFragment } from './fragment.js'; +import type { ConnectRequest } from './types.js'; + +// Anvil account #1: deterministic for golden tests. +const PRIVATE_KEY = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'; +const ADDRESS = '0x70997970c51812dc3a010c7d01b50e0d17dc79c8'; +const CHALLENGE = 'kJ8s9fK2mNpQrStUvWxYz0123456789AbCdEfGhIjKl'; + +function makeRequest(): ConnectRequest { + const qs = new URLSearchParams({ + version: '1', + redirect: 'https://app.example/connect/cb', + scopes: 'address', + challenge: CHALLENGE, + }).toString(); + return parseRequestLink(`antseed://connect?${qs}`); +} + +describe('buildSignedMessage', () => { + it('produces the exact signed-message bytes', () => { + const req = makeRequest(); + const msg = buildSignedMessage(req, { address: ADDRESS }); + expect(msg).toBe( + [ + 'AntSeed Connect', + 'version: 1', + 'redirect: https://app.example/connect/cb', + `challenge: ${CHALLENGE}`, + 'scopes: address', + `address: ${ADDRESS}`, + ].join('\n'), + ); + expect(msg.endsWith('\n')).toBe(false); + }); +}); + +describe('signConnectResponse / verifyConnectResponse', () => { + const wallet = new Wallet(PRIVATE_KEY); + + it('resolves the address scope value from the account address', () => { + const req = makeRequest(); + expect(resolveScopeValues(req, wallet)).toEqual({ address: ADDRESS }); + }); + + it('signs and round-trips through verify, recovering the signer', async () => { + const req = makeRequest(); + const values = resolveScopeValues(req, wallet); + const { response, fragmentUrl } = await signConnectResponse(wallet, req, values); + + expect(response.kind).toBe('antseed.connect.response'); + expect(response.signatureScheme).toBe('eip191-personal-sign'); + expect(response.signature).toMatch(/^[0-9a-f]{130}$/); + expect(response.values).toEqual({ address: ADDRESS }); + + const recovered = verifyConnectResponse(response, req); + expect(recovered).toBe(ADDRESS); + + expect(fragmentUrl.startsWith('https://app.example/connect/cb#result=')).toBe(true); + const encoded = fragmentUrl.split('#result=')[1]!; + expect(decodeResponseFragment(encoded)).toEqual(response); + }); + + it('rejects an address value that is not the signer', async () => { + const req = makeRequest(); + await expect( + signConnectResponse(wallet, req, { address: '0x' + '11'.repeat(20) }), + ).rejects.toThrow(ConnectResponseError); + }); + + it('verify rejects a tampered value', async () => { + const req = makeRequest(); + const values = resolveScopeValues(req, wallet); + const { response } = await signConnectResponse(wallet, req, values); + const tampered = { ...response, values: { address: '0x' + '22'.repeat(20) } }; + expect(() => verifyConnectResponse(tampered, req)).toThrow(ConnectResponseError); + }); + + it('verify rejects a challenge mismatch', async () => { + const req = makeRequest(); + const values = resolveScopeValues(req, wallet); + const { response } = await signConnectResponse(wallet, req, values); + const otherReq = { ...req, challenge: 'different-challenge-value-000000000000000000' }; + expect(() => verifyConnectResponse(response, otherReq)).toThrow(/challenge/); + }); + + it('verify rejects a malformed signature with a ConnectResponseError', async () => { + const req = makeRequest(); + const values = resolveScopeValues(req, wallet); + const { response } = await signConnectResponse(wallet, req, values); + const broken = { ...response, signature: 'zz' }; + expect(() => verifyConnectResponse(broken, req)).toThrow(ConnectResponseError); + }); + + it('verify rejects an unrequested scope in the response', async () => { + const req = makeRequest(); + const values = resolveScopeValues(req, wallet); + const { response } = await signConnectResponse(wallet, req, values); + const extra = { ...response, values: { ...response.values, secrets: 'x' } }; + expect(() => verifyConnectResponse(extra, req)).toThrow(/unrequested scope/); + }); +}); + +describe('auto-deposit scope', () => { + const wallet = new Wallet(PRIVATE_KEY); + const account = { + address: wallet.address, + autoDeposit: { enabled: true, delegated: false }, + signMessage: (message: string) => wallet.signMessage(message), + }; + + function makeRequestWithAutoDeposit(): ConnectRequest { + const qs = new URLSearchParams({ + version: '1', + redirect: 'https://app.example/connect/cb', + scopes: 'address,auto-deposit', + challenge: CHALLENGE, + }).toString(); + return parseRequestLink(`antseed://connect?${qs}`); + } + + it('resolves to deterministic JSON with fixed key order', () => { + const req = makeRequestWithAutoDeposit(); + const values = resolveScopeValues(req, account); + expect(values['auto-deposit']).toBe('{"enabled":true,"delegated":false}'); + }); + + it('signs and round-trips, parsing back to the input state', async () => { + const req = makeRequestWithAutoDeposit(); + const values = resolveScopeValues(req, account); + const { response } = await signConnectResponse(account, req, values); + + const recovered = verifyConnectResponse(response, req); + expect(recovered).toBe(ADDRESS); + expect(JSON.parse(response.values['auto-deposit']!)).toEqual({ + enabled: true, + delegated: false, + }); + }); + + it('defaults to a disabled state when the account has no auto-deposit data', () => { + const req = makeRequestWithAutoDeposit(); + const bare = { + address: wallet.address, + signMessage: (message: string) => wallet.signMessage(message), + }; + const values = resolveScopeValues(req, bare); + expect(values['auto-deposit']).toBe('{"enabled":false,"delegated":false}'); + }); +}); diff --git a/packages/connect-core/src/response.ts b/packages/connect-core/src/response.ts new file mode 100644 index 000000000..10fa0c20a --- /dev/null +++ b/packages/connect-core/src/response.ts @@ -0,0 +1,150 @@ +import { verifyMessage } from 'ethers'; +import { + CONNECT_VERSION, + type ConnectRequest, + type ConnectResponse, + type ConnectSigner, + type ScopeAccount, + type ScopeId, +} from './types.js'; +import { SCOPES } from './scopes.js'; +import { buildSignedMessage } from './message.js'; +import { buildFragmentUrl } from './fragment.js'; + +export class ConnectResponseError extends Error { + constructor(message: string) { + super(message); + this.name = 'ConnectResponseError'; + } +} + +function normalizeSignature(sig: string) { + const lower = sig.toLowerCase(); + return lower.startsWith('0x') ? lower.slice(2) : lower; +} + +/** + * Resolve the value each requested scope would share, from the local account + * address. The client shows these to the user before approval. + */ +export function resolveScopeValues( + req: ConnectRequest, + account: ScopeAccount, +): Record { + const values = {} as Record; + for (const scope of req.scopes) { + values[scope] = SCOPES[scope].resolve(account); + } + return values; +} + +/** + * Sign a Connect response with the local identity after user approval. + * Validates every value before signing and binds the address scope to the + * signer. + */ +export async function signConnectResponse( + signer: ConnectSigner, + req: ConnectRequest, + values: Record, +): Promise<{ response: ConnectResponse; fragmentUrl: string }> { + const signerAddress = signer.address.toLowerCase(); + + for (const scope of req.scopes) { + if (values[scope] === undefined) { + throw new ConnectResponseError(`missing value for scope: ${scope}`); + } + } + if (req.scopes.includes('address') && values['address'] !== signerAddress) { + throw new ConnectResponseError('address scope value does not match the signer'); + } + + const message = buildSignedMessage(req, values); + const rawSig = await signer.signMessage(message); + + const response: ConnectResponse = { + version: CONNECT_VERSION, + kind: 'antseed.connect.response', + challenge: req.challenge, + values: pickScopeValues(req, values), + signatureScheme: 'eip191-personal-sign', + signature: normalizeSignature(rawSig), + }; + + return { response, fragmentUrl: buildFragmentUrl(req.redirect, response) }; +} + +function pickScopeValues(req: ConnectRequest, values: Record) { + const out: Record = {}; + for (const scope of req.scopes) { + out[scope] = values[scope]!; + } + return out; +} + +/** + * Verify a signed response against the request it answers (web-app side). + * Returns the recovered account address (lowercase). Lives here so a + * gateway can reuse it and so the tests can round-trip sign against verify. + * + * @throws {ConnectResponseError} on any failed check. + */ +export function verifyConnectResponse( + response: unknown, + req: ConnectRequest, +): string { + if (typeof response !== 'object' || response === null) { + throw new ConnectResponseError('response is not an object'); + } + const r = response as Record; + + if (r['version'] !== CONNECT_VERSION) { + throw new ConnectResponseError('unsupported response version'); + } + if (r['kind'] !== 'antseed.connect.response') { + throw new ConnectResponseError('unexpected response kind'); + } + if (r['signatureScheme'] !== 'eip191-personal-sign') { + throw new ConnectResponseError('unsupported signature scheme'); + } + if (r['challenge'] !== req.challenge) { + throw new ConnectResponseError('challenge mismatch'); + } + if (typeof r['signature'] !== 'string') { + throw new ConnectResponseError('missing signature'); + } + const values = r['values']; + if (typeof values !== 'object' || values === null) { + throw new ConnectResponseError('missing values'); + } + const valueMap = values as Record; + + const requested = new Set(req.scopes); + for (const key of Object.keys(valueMap)) { + if (!requested.has(key)) { + throw new ConnectResponseError(`response contains unrequested scope: ${key}`); + } + } + const flatValues: Record = {}; + for (const scope of req.scopes) { + const value = valueMap[scope]; + if (typeof value !== 'string') { + throw new ConnectResponseError(`missing value for scope: ${scope}`); + } + flatValues[scope] = value; + } + + const message = buildSignedMessage(req, flatValues); + let recovered: string; + try { + recovered = verifyMessage(message, '0x' + normalizeSignature(r['signature'])).toLowerCase(); + } catch { + throw new ConnectResponseError('signature recovery failed'); + } + + if (req.scopes.includes('address') && flatValues['address'] !== recovered) { + throw new ConnectResponseError('address scope value does not match the recovered signer'); + } + + return recovered; +} diff --git a/packages/connect-core/src/scopes.ts b/packages/connect-core/src/scopes.ts new file mode 100644 index 000000000..797916f64 --- /dev/null +++ b/packages/connect-core/src/scopes.ts @@ -0,0 +1,33 @@ +import type { ScopeId, ScopeAccount } from './types.js'; + +export interface ScopeDef { + id: ScopeId; + label: string; + description: string; + resolve(account: ScopeAccount): string; +} + +export const SCOPES: Record = { + address: { + id: 'address', + label: 'Account address', + description: + 'Your AntSeed account address on Base. It is already public on-chain, so sharing it reveals nothing secret.', + resolve: (account) => account.address.toLowerCase(), + }, + 'auto-deposit': { + id: 'auto-deposit', + label: 'Auto-deposit status', + description: + 'Whether auto-deposit is turned on in your AntSeed app and whether your wallet is set up for gasless deposits. Read-only status, reveals nothing secret.', + // Fixed key order so the signed bytes are reproducible on the verifying side. + resolve: (account) => { + const state = account.autoDeposit ?? { enabled: false, delegated: false }; + return JSON.stringify({ enabled: state.enabled, delegated: state.delegated }); + }, + }, +}; + +export function isScopeId(value: string): value is ScopeId { + return Object.prototype.hasOwnProperty.call(SCOPES, value); +} diff --git a/packages/connect-core/src/types.ts b/packages/connect-core/src/types.ts new file mode 100644 index 000000000..6d00f9727 --- /dev/null +++ b/packages/connect-core/src/types.ts @@ -0,0 +1,51 @@ +// Types for the AntSeed Connect protocol. + +export const CONNECT_VERSION = 1 as const; + +export type ScopeId = 'address' | 'auto-deposit'; + +/** Auto-deposit status the seed app reports to a connecting web app. */ +export interface AutoDepositState { + /** Consent flag; only the seed app knows it, so the portal cannot derive it on-chain. */ + enabled: boolean; + /** Wallet carries the EIP-7702 delegation that lets it deposit gaslessly. */ + delegated: boolean; +} + +export interface ConnectRequest { + version: typeof CONNECT_VERSION; + redirect: string; + /** Comes from the redirect URL, never a separate param. The only thing we trust. */ + origin: string; + /** Order matters: it sets the order of the value lines in the signed message. */ + scopes: ScopeId[]; + challenge: string; +} + +export interface ConnectResponse { + version: typeof CONNECT_VERSION; + kind: 'antseed.connect.response'; + challenge: string; + values: Record; + signatureScheme: 'eip191-personal-sign'; + /** Lowercase 65-byte secp256k1 hex, no 0x prefix. */ + signature: string; +} + +export interface ConnectManifest { + version: typeof CONNECT_VERSION; + kind: 'antseed.connect.manifest'; + name: string; + homepage: string; + icon?: string; +} + +export interface ScopeAccount { + readonly address: string; + /** Present only when the request asks for the auto-deposit scope. */ + readonly autoDeposit?: AutoDepositState; +} + +export interface ConnectSigner extends ScopeAccount { + signMessage(message: string): Promise; +} diff --git a/packages/connect-core/tsconfig.json b/packages/connect-core/tsconfig.json new file mode 100644 index 000000000..5edad813e --- /dev/null +++ b/packages/connect-core/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/plugins/service-auto-deposit/src/connect-state.ts b/plugins/service-auto-deposit/src/connect-state.ts new file mode 100644 index 000000000..4f3f51b36 --- /dev/null +++ b/plugins/service-auto-deposit/src/connect-state.ts @@ -0,0 +1,43 @@ +import { createPublicClient, http, getAddress } from 'viem'; +import { delegationTarget } from './codec.js'; +import { AUTO_DEPOSIT_CHAINS } from './chains.js'; + +/** Auto-deposit status shared over AntSeed Connect. Structurally matches + * connect-core's AutoDepositState, kept local to avoid a dependency edge. */ +export interface AutoDepositConnectState { + enabled: boolean; + delegated: boolean; +} + +export interface AutoDepositConnectStateInput { + /** AntSeed chain id (e.g. 'base-mainnet'). Keys into {@link AUTO_DEPOSIT_CHAINS}. */ + chainId: string; + rpcUrl: string; + address: string; + /** Consent flag the caller reads from config; only the seed app knows it. */ + enabled: boolean; +} + +/** + * Read the auto-deposit state to advertise over Connect. `delegated` is read + * on-chain the same way {@link AUTO_DEPOSIT_CHAINS}-backed deposits check it + * (eth_getCode → EIP-7702 designator → our delegate). Best-effort: an + * unsupported chain, a missing RPC, or any read failure yields `delegated: false` + * while preserving the caller's `enabled` value. + */ +export async function readAutoDepositConnectState( + input: AutoDepositConnectStateInput, +): Promise { + const chain = AUTO_DEPOSIT_CHAINS[input.chainId]; + if (!chain || !input.rpcUrl) return { enabled: input.enabled, delegated: false }; + + try { + const client = createPublicClient({ transport: http(input.rpcUrl) }); + const code = await client.getCode({ address: getAddress(input.address) }); + const target = delegationTarget(code ?? null); + const delegated = target !== null && target.toLowerCase() === chain.delegateAddress.toLowerCase(); + return { enabled: input.enabled, delegated }; + } catch { + return { enabled: input.enabled, delegated: false }; + } +} diff --git a/plugins/service-auto-deposit/src/index.ts b/plugins/service-auto-deposit/src/index.ts index dfa5d758a..ae043db9a 100644 --- a/plugins/service-auto-deposit/src/index.ts +++ b/plugins/service-auto-deposit/src/index.ts @@ -12,5 +12,10 @@ export { toServiceStatus, } from './factory.js'; export { AUTO_DEPOSIT_CHAINS, type AutoDepositChainConfig } from './chains.js'; +export { + readAutoDepositConnectState, + type AutoDepositConnectState, + type AutoDepositConnectStateInput, +} from './connect-state.js'; export type { FundingChainContext } from './chain-context.js'; export { autoDepositPlugin, default } from './plugin.js'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d3b22f6f..fab0ee04f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,9 @@ importers: '@antseed/api-adapter': specifier: workspace:* version: link:../../packages/api-adapter + '@antseed/connect-core': + specifier: workspace:* + version: link:../../packages/connect-core '@antseed/node': specifier: workspace:* version: link:../../packages/node @@ -47,6 +50,9 @@ importers: specifier: ^9.3.0 version: 9.3.0 devDependencies: + '@antseed/service-auto-deposit': + specifier: workspace:* + version: link:../../plugins/service-auto-deposit '@types/node': specifier: ^20.11.0 version: 20.19.33 @@ -62,12 +68,18 @@ importers: '@antseed/api-adapter': specifier: workspace:* version: link:../../packages/api-adapter + '@antseed/connect-core': + specifier: workspace:* + version: link:../../packages/connect-core '@antseed/node': specifier: workspace:* version: link:../../packages/node '@antseed/payments': specifier: workspace:* version: link:../payments + '@antseed/service-auto-deposit': + specifier: workspace:* + version: link:../../plugins/service-auto-deposit '@antseed/ui': specifier: workspace:* version: link:../../packages/ui @@ -82,10 +94,10 @@ importers: version: 11.2.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) '@mariozechner/pi-ai': specifier: ^0.64.0 - version: 0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@3.25.76) + version: 0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.4.3) '@mariozechner/pi-coding-agent': specifier: ^0.64.0 - version: 0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@3.25.76) + version: 0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.4.3) '@radix-ui/react-tooltip': specifier: ^1.2.8 version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -176,7 +188,7 @@ importers: version: 18.3.7(@types/react@18.3.28) '@vitejs/plugin-react': specifier: ^4.3.0 - version: 4.7.0(vite@6.4.1(@types/node@20.19.33)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.7.0(vite@6.4.1(@types/node@20.19.33)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) concurrently: specifier: ^9.0.1 version: 9.2.1 @@ -200,7 +212,7 @@ importers: version: 5.9.3 vite: specifier: ^6.0.0 - version: 6.4.1(@types/node@20.19.33)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + version: 6.4.1(@types/node@20.19.33)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) wait-on: specifier: ^8.0.1 version: 8.0.5 @@ -234,13 +246,13 @@ importers: version: 18.3.7(@types/react@18.3.28) '@vitejs/plugin-react': specifier: ^4.3.0 - version: 4.7.0(vite@6.4.1(@types/node@22.7.5)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.21)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) typescript: specifier: ^5.6.0 version: 5.9.3 vite: specifier: ^6.0.0 - version: 6.4.1(@types/node@22.7.5)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + version: 6.4.1(@types/node@22.19.21)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) apps/network-stats: dependencies: @@ -308,7 +320,7 @@ importers: version: 18.3.7(@types/react@18.3.28) '@vitejs/plugin-react': specifier: ^4.3.0 - version: 4.7.0(vite@6.4.1(@types/node@20.19.33)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.7.0(vite@6.4.1(@types/node@20.19.33)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) concurrently: specifier: ^9.0.1 version: 9.2.1 @@ -332,10 +344,10 @@ importers: version: 2.47.12(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) vite: specifier: ^6.0.0 - version: 6.4.1(@types/node@20.19.33)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + version: 6.4.1(@types/node@20.19.33)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) vitest: specifier: ^2.1.9 - version: 2.1.9(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) wagmi: specifier: ^2.14.0 version: 2.19.5(@tanstack/query-core@5.97.0)(@tanstack/react-query@5.97.0(react@18.3.1))(@types/react@18.3.28)(bufferutil@4.1.0)(encoding@0.1.13)(immer@11.1.4)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(viem@2.47.12(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(zod@3.25.76) @@ -344,16 +356,16 @@ importers: dependencies: '@docusaurus/core': specifier: ^3.7.0 - version: 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + version: 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/plugin-client-redirects': specifier: ^3.7.0 - version: 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + version: 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/preset-classic': specifier: ^3.7.0 - version: 3.9.2(@algolia/client-search@5.49.1)(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.3)(utf-8-validate@5.0.10) + version: 3.9.2(@algolia/client-search@5.49.1)(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(@types/react@19.2.17)(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.3)(utf-8-validate@5.0.10) '@mdx-js/react': specifier: ^3.0.0 - version: 3.1.1(@types/react@18.3.28)(react@18.3.1) + version: 3.1.1(@types/react@19.2.17)(react@18.3.1) clsx: specifier: ^2.1.0 version: 2.1.1 @@ -406,13 +418,13 @@ importers: version: 20.19.33 openai: specifier: ^6.27.0 - version: 6.27.0(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.3.6) + version: 6.27.0(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.4.3) typescript: specifier: ^5.3.0 version: 5.9.3 vitest: specifier: ^1.2.0 - version: 1.6.1(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0) + version: 1.6.1(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) packages/ant-agent: devDependencies: @@ -424,7 +436,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) packages/api-adapter: devDependencies: @@ -433,7 +445,20 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) + + packages/connect-core: + dependencies: + ethers: + specifier: ~6.16.0 + version: 6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) + devDependencies: + typescript: + specifier: ^5.5.0 + version: 5.9.3 + vitest: + specifier: ^2.0.0 + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) packages/node: dependencies: @@ -477,7 +502,7 @@ importers: version: 5.9.3 vitest: specifier: ^1.2.0 - version: 1.6.1(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0) + version: 1.6.1(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) packages/provider-core: dependencies: @@ -493,7 +518,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) packages/router-core: devDependencies: @@ -505,7 +530,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) packages/service-core: devDependencies: @@ -561,7 +586,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) plugins/provider-claude-code: dependencies: @@ -580,7 +605,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) plugins/provider-claude-oauth: dependencies: @@ -596,7 +621,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) plugins/provider-local-llm: dependencies: @@ -612,7 +637,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) plugins/provider-openai: dependencies: @@ -628,7 +653,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) plugins/provider-openai-responses: dependencies: @@ -644,7 +669,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) plugins/router-local: dependencies: @@ -660,7 +685,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) plugins/service-auto-deposit: dependencies: @@ -669,10 +694,10 @@ importers: version: link:../../packages/service-core permissionless: specifier: ^0.3.6 - version: 0.3.6(viem@2.52.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6)) + version: 0.3.6(viem@2.52.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.3)) viem: specifier: ^2.52.2 - version: 2.52.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6) + version: 2.52.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.3) devDependencies: '@antseed/node': specifier: workspace:* @@ -682,7 +707,7 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + version: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) packages: @@ -4593,6 +4618,9 @@ packages: '@types/node@20.19.33': resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==} + '@types/node@22.19.21': + resolution: {integrity: sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==} + '@types/node@22.7.5': resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} @@ -4628,6 +4656,9 @@ packages: '@types/react@18.3.28': resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} @@ -7547,6 +7578,10 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} @@ -7682,6 +7717,76 @@ packages: light-my-request@6.6.0: resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -11191,8 +11296,8 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} zustand@5.0.0: resolution: {integrity: sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==} @@ -11349,11 +11454,11 @@ snapshots: dependencies: '@algolia/client-common': 5.49.1 - '@anthropic-ai/sdk@0.73.0(zod@3.25.76)': + '@anthropic-ai/sdk@0.73.0(zod@4.4.3)': dependencies: json-schema-to-ts: 3.1.1 optionalDependencies: - zod: 3.25.76 + zod: 4.4.3 '@aws-crypto/crc32@5.2.0': dependencies: @@ -12938,21 +13043,21 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@docsearch/core@4.6.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docsearch/core@4.6.0(@types/react@19.2.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': optionalDependencies: - '@types/react': 18.3.28 + '@types/react': 19.2.17 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) '@docsearch/css@4.6.0': {} - '@docsearch/react@4.6.0(@algolia/client-search@5.49.1)(@types/react@18.3.28)(algoliasearch@5.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)': + '@docsearch/react@4.6.0(@algolia/client-search@5.49.1)(@types/react@19.2.17)(algoliasearch@5.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)': dependencies: '@algolia/autocomplete-core': 1.19.2(@algolia/client-search@5.49.1)(algoliasearch@5.49.1)(search-insights@2.17.3) - '@docsearch/core': 4.6.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docsearch/core': 4.6.0(@types/react@19.2.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docsearch/css': 4.6.0 optionalDependencies: - '@types/react': 18.3.28 + '@types/react': 19.2.17 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) search-insights: 2.17.3 @@ -13027,7 +13132,7 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: '@docusaurus/babel': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/bundler': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) @@ -13036,7 +13141,7 @@ snapshots: '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mdx-js/react': 3.1.1(@types/react@18.3.28)(react@18.3.1) + '@mdx-js/react': 3.1.1(@types/react@19.2.17)(react@18.3.1) boxen: 6.2.1 chalk: 4.1.2 chokidar: 3.6.0 @@ -13156,9 +13261,9 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/plugin-client-redirects@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-client-redirects@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.9.2 '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -13187,13 +13292,13 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-blog@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-content-blog@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.9.2 '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -13228,13 +13333,13 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.9.2 '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -13268,9 +13373,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-pages@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-content-pages@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -13298,9 +13403,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-css-cascade-layers@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-css-cascade-layers@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -13325,9 +13430,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-debug@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-debug@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) fs-extra: 11.3.3 @@ -13353,9 +13458,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-analytics@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-google-analytics@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -13379,9 +13484,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-gtag@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-google-gtag@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/gtag.js': 0.0.12 @@ -13406,9 +13511,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-google-tag-manager@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -13432,9 +13537,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-sitemap@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-sitemap@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.9.2 '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -13463,9 +13568,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-svgr@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-svgr@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -13493,22 +13598,22 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/preset-classic@3.9.2(@algolia/client-search@5.49.1)(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.3)(utf-8-validate@5.0.10)': - dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-css-cascade-layers': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-debug': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-google-analytics': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-google-gtag': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-google-tag-manager': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-sitemap': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-svgr': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-classic': 3.9.2(@types/react@18.3.28)(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/theme-search-algolia': 3.9.2(@algolia/client-search@5.49.1)(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/preset-classic@3.9.2(@algolia/client-search@5.49.1)(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(@types/react@19.2.17)(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.3)(utf-8-validate@5.0.10)': + dependencies: + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-css-cascade-layers': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-debug': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-google-analytics': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-google-gtag': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-google-tag-manager': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-sitemap': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-svgr': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/theme-classic': 3.9.2(@types/react@19.2.17)(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-search-algolia': 3.9.2(@algolia/client-search@5.49.1)(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(@types/react@19.2.17)(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -13538,22 +13643,22 @@ snapshots: '@types/react': 18.3.28 react: 18.3.1 - '@docusaurus/theme-classic@3.9.2(@types/react@18.3.28)(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/theme-classic@3.9.2(@types/react@19.2.17)(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.9.2 '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/theme-translations': 3.9.2 '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mdx-js/react': 3.1.1(@types/react@18.3.28)(react@18.3.1) + '@mdx-js/react': 3.1.1(@types/react@19.2.17)(react@18.3.1) clsx: 2.1.1 infima: 0.2.0-alpha.45 lodash: 4.17.23 @@ -13585,11 +13690,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/history': 4.7.11 @@ -13609,13 +13714,13 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/theme-search-algolia@3.9.2(@algolia/client-search@5.49.1)(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@docusaurus/theme-search-algolia@3.9.2(@algolia/client-search@5.49.1)(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(@types/react@19.2.17)(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: - '@docsearch/react': 4.6.0(@algolia/client-search@5.49.1)(@types/react@18.3.28)(algoliasearch@5.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docsearch/react': 4.6.0(@algolia/client-search@5.49.1)(@types/react@19.2.17)(algoliasearch@5.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.9.2 - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1))(bufferutil@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/theme-translations': 3.9.2 '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -14211,7 +14316,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.19.33 + '@types/node': 22.19.21 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -14438,9 +14543,9 @@ snapshots: std-env: 3.10.0 yoctocolors: 2.1.2 - '@mariozechner/pi-agent-core@0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@3.25.76)': + '@mariozechner/pi-agent-core@0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.4.3)': dependencies: - '@mariozechner/pi-ai': 0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@3.25.76) + '@mariozechner/pi-ai': 0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.4.3) transitivePeerDependencies: - '@modelcontextprotocol/sdk' - aws-crt @@ -14450,9 +14555,9 @@ snapshots: - ws - zod - '@mariozechner/pi-ai@0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@3.25.76)': + '@mariozechner/pi-ai@0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.4.3)': dependencies: - '@anthropic-ai/sdk': 0.73.0(zod@3.25.76) + '@anthropic-ai/sdk': 0.73.0(zod@4.4.3) '@aws-sdk/client-bedrock-runtime': 3.996.0 '@google/genai': 1.42.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) '@mistralai/mistralai': 1.14.1(bufferutil@4.1.0)(utf-8-validate@5.0.10) @@ -14460,11 +14565,11 @@ snapshots: ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) chalk: 5.6.2 - openai: 6.26.0(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@3.25.76) + openai: 6.26.0(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.4.3) partial-json: 0.1.7 proxy-agent: 6.5.0 undici: 7.22.0 - zod-to-json-schema: 3.25.1(zod@3.25.76) + zod-to-json-schema: 3.25.1(zod@4.4.3) transitivePeerDependencies: - '@modelcontextprotocol/sdk' - aws-crt @@ -14474,11 +14579,11 @@ snapshots: - ws - zod - '@mariozechner/pi-coding-agent@0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@3.25.76)': + '@mariozechner/pi-coding-agent@0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.4.3)': dependencies: '@mariozechner/jiti': 2.6.5 - '@mariozechner/pi-agent-core': 0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@3.25.76) - '@mariozechner/pi-ai': 0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@3.25.76) + '@mariozechner/pi-agent-core': 0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.4.3) + '@mariozechner/pi-ai': 0.64.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.4.3) '@mariozechner/pi-tui': 0.64.0 '@silvia-odwyer/photon-node': 0.3.4 ajv: 8.18.0 @@ -14547,10 +14652,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/react@3.1.1(@types/react@18.3.28)(react@18.3.1)': + '@mdx-js/react@3.1.1(@types/react@19.2.17)(react@18.3.1)': dependencies: '@types/mdx': 2.0.13 - '@types/react': 18.3.28 + '@types/react': 19.2.17 react: 18.3.1 '@metamask/eth-json-rpc-provider@1.0.1': @@ -14769,8 +14874,8 @@ snapshots: '@mistralai/mistralai@1.14.1(bufferutil@4.1.0)(utf-8-validate@5.0.10)': dependencies: ws: 8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - zod: 3.25.76 - zod-to-json-schema: 3.25.1(zod@3.25.76) + zod: 4.4.3 + zod-to-json-schema: 3.25.1(zod@4.4.3) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -16750,7 +16855,7 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 20.19.33 + '@types/node': 22.19.21 '@types/lodash@4.17.24': {} @@ -16774,6 +16879,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@22.19.21': + dependencies: + undici-types: 6.21.0 + '@types/node@22.7.5': dependencies: undici-types: 6.19.8 @@ -16818,6 +16927,10 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.2.3 + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + '@types/responselike@1.0.3': dependencies: '@types/node': 20.19.33 @@ -16880,7 +16993,7 @@ snapshots: '@types/ws@7.4.7': dependencies: - '@types/node': 20.19.33 + '@types/node': 22.19.21 '@types/ws@8.18.1': dependencies: @@ -16926,7 +17039,7 @@ snapshots: dependencies: '@vanilla-extract/css': 1.17.3 - '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@20.19.33)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@20.19.33)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -16934,11 +17047,11 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.4.1(@types/node@20.19.33)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 6.4.1(@types/node@20.19.33)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.7.5)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.21)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -16946,7 +17059,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.4.1(@types/node@22.7.5)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 6.4.1(@types/node@22.19.21)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -16963,21 +17076,21 @@ snapshots: chai: 5.3.3 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0))': + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0))': dependencies: '@vitest/spy': 2.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 5.4.21(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0) + vite: 5.4.21(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) - '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0))': + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0))': dependencies: '@vitest/spy': 2.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 5.4.21(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + vite: 5.4.21(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) '@vitest/pretty-format@2.1.9': dependencies: @@ -17740,10 +17853,10 @@ snapshots: typescript: 5.9.3 zod: 3.25.76 - abitype@1.2.3(typescript@5.9.3)(zod@4.3.6): + abitype@1.2.3(typescript@5.9.3)(zod@4.4.3): optionalDependencies: typescript: 5.9.3 - zod: 4.3.6 + zod: 4.4.3 abort-controller@3.0.0: dependencies: @@ -20743,7 +20856,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.19.33 + '@types/node': 22.19.21 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -20764,6 +20877,9 @@ snapshots: jiti@1.21.7: {} + jiti@2.7.0: + optional: true + joi@17.13.3: dependencies: '@hapi/hoek': 9.3.0 @@ -20918,6 +21034,56 @@ snapshots: process-warning: 4.0.1 set-cookie-parser: 2.7.2 + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + optional: true + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -21977,15 +22143,15 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openai@6.26.0(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@3.25.76): + openai@6.26.0(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.4.3): optionalDependencies: ws: 8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10) - zod: 3.25.76 + zod: 4.4.3 - openai@6.27.0(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.3.6): + openai@6.27.0(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10))(zod@4.4.3): optionalDependencies: ws: 8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10) - zod: 4.3.6 + zod: 4.4.3 openapi-fetch@0.13.8: dependencies: @@ -22065,7 +22231,7 @@ snapshots: transitivePeerDependencies: - zod - ox@0.14.29(typescript@5.9.3)(zod@4.3.6): + ox@0.14.29(typescript@5.9.3)(zod@4.4.3): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -22073,7 +22239,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@5.9.3)(zod@4.3.6) + abitype: 1.2.3(typescript@5.9.3)(zod@4.4.3) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.9.3 @@ -22108,7 +22274,7 @@ snapshots: transitivePeerDependencies: - zod - ox@0.9.17(typescript@5.9.3)(zod@4.3.6): + ox@0.9.17(typescript@5.9.3)(zod@4.4.3): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -22116,7 +22282,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@5.9.3)(zod@4.3.6) + abitype: 1.2.3(typescript@5.9.3)(zod@4.4.3) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.9.3 @@ -22311,9 +22477,9 @@ snapshots: pend@1.2.0: {} - permissionless@0.3.6(viem@2.52.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6)): + permissionless@0.3.6(viem@2.52.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.3)): dependencies: - viem: 2.52.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6) + viem: 2.52.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.3) picocolors@1.1.1: {} @@ -22401,9 +22567,9 @@ snapshots: hono: 4.12.12 idb-keyval: 6.2.2 mipd: 0.0.7(typescript@5.9.3) - ox: 0.9.17(typescript@5.9.3)(zod@4.3.6) + ox: 0.9.17(typescript@5.9.3)(zod@4.4.3) viem: 2.47.12(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) - zod: 4.3.6 + zod: 4.4.3 zustand: 5.0.3(@types/react@18.3.28)(immer@11.1.4)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) optionalDependencies: '@tanstack/react-query': 5.97.0(react@18.3.1) @@ -24505,15 +24671,15 @@ snapshots: - utf-8-validate - zod - viem@2.52.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6): + viem@2.52.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.3): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@5.9.3)(zod@4.3.6) + abitype: 1.2.3(typescript@5.9.3)(zod@4.4.3) isows: 1.0.7(ws@8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10)) - ox: 0.14.29(typescript@5.9.3)(zod@4.3.6) + ox: 0.14.29(typescript@5.9.3)(zod@4.4.3) ws: 8.20.1(bufferutil@4.1.0)(utf-8-validate@5.0.10) optionalDependencies: typescript: 5.9.3 @@ -24522,13 +24688,13 @@ snapshots: - utf-8-validate - zod - vite-node@1.6.1(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0): + vite-node@1.6.1(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0): dependencies: cac: 6.7.14 debug: 4.4.3 pathe: 1.1.2 picocolors: 1.1.1 - vite: 5.4.21(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0) + vite: 5.4.21(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) transitivePeerDependencies: - '@types/node' - less @@ -24540,13 +24706,13 @@ snapshots: - supports-color - terser - vite-node@2.1.9(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0): + vite-node@2.1.9(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 1.1.2 - vite: 5.4.21(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0) + vite: 5.4.21(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) transitivePeerDependencies: - '@types/node' - less @@ -24558,13 +24724,13 @@ snapshots: - supports-color - terser - vite-node@2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0): + vite-node@2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 1.1.2 - vite: 5.4.21(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + vite: 5.4.21(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) transitivePeerDependencies: - '@types/node' - less @@ -24576,7 +24742,7 @@ snapshots: - supports-color - terser - vite@5.4.21(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0): + vite@5.4.21(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0): dependencies: esbuild: 0.21.5 postcss: 8.5.6 @@ -24584,21 +24750,23 @@ snapshots: optionalDependencies: '@types/node': 20.19.33 fsevents: 2.3.3 + lightningcss: 1.32.0 sass: 1.97.3 terser: 5.46.0 - vite@5.4.21(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0): + vite@5.4.21(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0): dependencies: esbuild: 0.21.5 postcss: 8.5.6 rollup: 4.59.0 optionalDependencies: - '@types/node': 22.7.5 + '@types/node': 22.19.21 fsevents: 2.3.3 + lightningcss: 1.32.0 sass: 1.97.3 terser: 5.46.0 - vite@6.4.1(@types/node@20.19.33)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): + vite@6.4.1(@types/node@20.19.33)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -24609,13 +24777,14 @@ snapshots: optionalDependencies: '@types/node': 20.19.33 fsevents: 2.3.3 - jiti: 1.21.7 + jiti: 2.7.0 + lightningcss: 1.32.0 sass: 1.97.3 terser: 5.46.0 tsx: 4.21.0 yaml: 2.8.2 - vite@6.4.1(@types/node@22.7.5)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): + vite@6.4.1(@types/node@22.19.21)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -24624,15 +24793,16 @@ snapshots: rollup: 4.59.0 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.7.5 + '@types/node': 22.19.21 fsevents: 2.3.3 - jiti: 1.21.7 + jiti: 2.7.0 + lightningcss: 1.32.0 sass: 1.97.3 terser: 5.46.0 tsx: 4.21.0 yaml: 2.8.2 - vitest@1.6.1(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0): + vitest@1.6.1(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0): dependencies: '@vitest/expect': 1.6.1 '@vitest/runner': 1.6.1 @@ -24651,8 +24821,8 @@ snapshots: strip-literal: 2.1.1 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.4.21(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0) - vite-node: 1.6.1(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0) + vite: 5.4.21(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) + vite-node: 1.6.1(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.33 @@ -24666,10 +24836,10 @@ snapshots: - supports-color - terser - vitest@2.1.9(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0): + vitest@2.1.9(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0): dependencies: '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0)) + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)) '@vitest/pretty-format': 2.1.9 '@vitest/runner': 2.1.9 '@vitest/snapshot': 2.1.9 @@ -24685,8 +24855,8 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.1.1 tinyrainbow: 1.2.0 - vite: 5.4.21(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0) - vite-node: 2.1.9(@types/node@20.19.33)(sass@1.97.3)(terser@5.46.0) + vite: 5.4.21(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) + vite-node: 2.1.9(@types/node@20.19.33)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.33 @@ -24701,10 +24871,10 @@ snapshots: - supports-color - terser - vitest@2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0): + vitest@2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0): dependencies: '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0)) + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)) '@vitest/pretty-format': 2.1.9 '@vitest/runner': 2.1.9 '@vitest/snapshot': 2.1.9 @@ -24720,11 +24890,11 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.1.1 tinyrainbow: 1.2.0 - vite: 5.4.21(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) - vite-node: 2.1.9(@types/node@22.7.5)(sass@1.97.3)(terser@5.46.0) + vite: 5.4.21(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) + vite-node: 2.1.9(@types/node@22.19.21)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.7.5 + '@types/node': 22.19.21 transitivePeerDependencies: - less - lightningcss @@ -25143,15 +25313,15 @@ snapshots: zlibjs@0.3.1: {} - zod-to-json-schema@3.25.1(zod@3.25.76): + zod-to-json-schema@3.25.1(zod@4.4.3): dependencies: - zod: 3.25.76 + zod: 4.4.3 zod@3.22.4: {} zod@3.25.76: {} - zod@4.3.6: {} + zod@4.4.3: {} zustand@5.0.0(@types/react@18.3.28)(immer@11.1.4)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)): optionalDependencies: