Skip to content
LogoLogo

TypeScript API

Developer Preview

@navalabs/sdk is an ESM package with types. Use it when you want the verification flow inside your own service rather than through the CLI or the MCP server.

The package is restricted during Developer Preview. Get package access and a provisioned credential from Nava before installing it.

npm install @navalabs/sdk

Requires Node.js 18 or newer.

Verify one proposed action

import { NavaClient } from '@navalabs/sdk';
 
const nava = new NavaClient({
  apiKey: process.env.NAVA_API_KEY!,
  walletAddress: process.env.WALLET_ADDRESS!,
});
 
const created = await nava.requestVerification({
  prompt: 'Swap WETH for USDC on Uniswap',
  proposedTx: {
    protocol: 'uniswap',
    chainId: 11155111,
    to: '0x...',
    data: '0x...',
    value: '0',
  },
});
 
if (!created.requestHash) throw new Error('Guardian did not return a request hash');
 
const status = await nava.waitForVerification(created.requestHash);
const approved =
  status.status === 'APPROVED' &&
  status.canExecute === true &&
  status.verdict?.outcome === 'approved' &&
  status.verdict?.reasonCode === 'allowed';
 
if (!approved) throw new Error('Guardian did not approve this action');
// Your application now decides whether to sign and submit this exact action.

The field is prompt, not userPrompt, and the transaction is proposedTx, not tx. The prompt is audit context for people and records, not policy authority. Guardian evaluates the exact structured proposedTx against the active policy. Do not put secrets or personal data in either field. proposedTx.chainId is required and must be a chain the registry supports; requestVerification throws at the SDK boundary otherwise, before any request is sent.

checkVerificationStatus returns a status envelope. Treat it as approval only when status is "APPROVED", canExecute is true, verdict.outcome is "approved", and verdict.reasonCode is "allowed". Any mismatch blocks execution. An unknown hash returns status: "NONE" with canExecute: false. An EXECUTED, REJECTED, or FAILED envelope remains blocked even if it retains an earlier approved verdict.

NavaClient.waitForVerification polls for you, with the same 2000 ms interval and 30 attempt defaults the CLI uses: a ceiling of 58 seconds of waiting, since the loop sleeps only between polls. Exhausting the attempts throws NavaTimeoutError; it does not return a PENDING status. Catch it and treat it as neither a rejection nor an approval.

Manage agents and policies

import { bootstrapGuardian } from '@navalabs/sdk';
 
const context = await bootstrapGuardian({
  bearerToken: process.env.NAVA_MANAGEMENT_TOKEN!,
  agentName: 'Treasury Agent',
  walletAddress: process.env.WALLET_ADDRESS!,
  chainId: 11155111,
  policy: { presetId: 'spot-swapper' },
});

bootstrapGuardian provisions an agent, attaches its verification wallet, and activates a policy in one call. It needs a management credential, which is a different authority from the agent API key used for submissions; do not reuse one for the other.

Two clients sit underneath it, both exported from the package root:

  • GuardianClient: agent, wallet, API-key, policy, and verdict-history management. Management authority.
  • GuardianVerdictClient: submissions and verdict polling with an agent API key. Agent authority.

Related exports on the root: parsePolicyVerdict, isTerminalPolicyVerdict, extractPolicyVerdictFromCreateResponse, VERDICT_SCHEMA_VERSION, NavaFetchError, and NavaTimeoutError.

Subpath exports

Five import paths, and the two binaries, are the stable public contract. Each row below was checked against the built module, not only the package manifest.

ImportContents
@navalabs/sdkNavaClient, GuardianClient, GuardianVerdictClient, bootstrapGuardian, the verdict helpers. Start here.
@navalabs/sdk/escrowAgent-execution bootstrap: bootstrapAgentExecution, createAgentClient, signAgentTransaction, AgentNavaClient, the KeyStore adapters (EnvKeyStore, FileKeyStore, MemoryKeyStore), and the P-256 helpers.
@navalabs/sdk/mcpstartServer, createMCPServer, KNOWN_PROTOCOLS, parseProtocols, and the registrar plumbing. What nava-mcp runs.
@navalabs/sdk/clirunCli, runCliEntry, parseArgs, and the argv helpers (requiredStringArg, enumArg, intArg, requireEnv, …). What nava runs, and what an adapter's CLI module plugs into.
@navalabs/sdk/surfaceThe ProtocolSurface descriptor framework: defineProtocolSurface, defineOperation, createCliModule, registerProtocolTools.
import { NavaClient, bootstrapGuardian } from '@navalabs/sdk';
import { bootstrapAgentExecution, FileKeyStore } from '@navalabs/sdk/escrow';
import { startServer, parseProtocols } from '@navalabs/sdk/mcp';
import { runCli, requiredStringArg } from '@navalabs/sdk/cli';
import { defineProtocolSurface, defineOperation } from '@navalabs/sdk/surface';

The descriptor helpers on /surface are also re-exported from the package root, so defineProtocolSurface, defineOperation, createCliModule, and registerProtocolTools resolve either way.

Consumers still on moduleResolution: "node" reach the subpath types through typesVersions rather than exports.

Adapter subpaths

Adapters are ESM packages too, and each exposes its own operation surface.

import { createUniswapTools } from '@navalabs/uniswap-adapter';
import { registerUniswapTools } from '@navalabs/uniswap-adapter/mcp';
import { uniswapCliModule } from '@navalabs/uniswap-adapter/cli';
@navalabs/hyperliquid-adapterExports
package rootThe full surface: clients, tools, error types, the asset resolver.
/adaptercreateHyperliquidTools, createHyperliquidReadTools, createHyperliquidWriteTools, and the verification-mode helpers.
/actionsAction builders and hyperliquidActionHash: build and hash an action without executing it.
/mcpcreateProtocolRegistrarFactory and the shared field enums.
/clihyperliquidCliModule and the per-command parsers.
@navalabs/uniswap-adapterExports
package rootThe full surface: tool factories, pool and token registries, approval helpers.
/toolsThe six tool factories plus createUniswapTools.
/mcpcreateProtocolRegistrarFactory and the advertised field schemas.
/cliuniswapCliModule and the per-command parsers.
/adapterAdvertised but empty: exports nothing. Do not import it.

Uniswap's /adapter resolves and builds, so nothing warns you; it simply has no members. Its Hyperliquid namesake is a real module, which makes the asymmetry easy to trip over. Import from the package root or /tools instead.

The ProtocolSurface descriptor

Every adapter declares its public surface once, in a ProtocolSurface descriptor. The CLI commands and the MCP tool registrations are both derived from it, so an operation cannot exist on one transport and not the other by accident.

defineProtocolSurface validates the declarations when the module loads, before either transport is wired. It throws unless the operation name is kebab-case and unique, every operation exposes at least one transport, cli.name equals the operation name, mcp.name is exactly <protocol>.<name>, and every build operation supplies an output schema. A camelCase alias cannot be introduced.

import { defineOperation, defineProtocolSurface } from '@navalabs/sdk/surface';
 
export const exampleSurface = defineProtocolSurface({
  protocol: 'example',
  operations: [
    defineOperation({
      name: 'get-price',
      kind: 'read',
      description: 'Fetch a mid price.',
      cli: { name: 'get-price', parse: parseGetPrice },
      mcp: { name: 'example.get-price', inputSchema: getPriceSchema },
      handler: (tools, input) => tools.getPrice(input),
    }),
  ],
  capabilityViews: (tools) => ({
    read: tools,
    build: tools,
    'verified-write': tools,
    operational: tools,
  }),
});

parseGetPrice, getPriceSchema, and the tool factory come from the adapter. createCliModule(surface, createTools) produces the CLI module, while registerProtocolTools(server, surface, tools) registers the MCP tools. Both derive their operation names from the same surface descriptor.

Four operation kinds exist, and the kind decides how a result is reported:

KindMeaningisError
readFetches state. No signing.Always false unless the call threw.
buildReturns unsigned calldata. No signing, no broadcast.Always false unless the call threw.
verified-writeSubmits the venue-native action for verification, then may sign and send.Derived from the payload's success.
operationalThe base verification operations.Set by hand, and it does not always track success. A REJECTED await-verification verdict is isError: false with success: false.

That isError split matters when you call these over MCP; the consequences are spelled out on the MCP page.

Keep verification and execution separate

A terminal approving verdict permits the exact evaluated action under the returned policy version. It does not execute anything, and it is not evidence that the action was broadcast, filled, or confirmed. Your application owns signing and venue submission, and a materially changed action needs its own verification request.

Never retry a rejected action unchanged, and never split or reshape an action to get around a policy boundary. Read Handle Nava Guardian verdicts for the outcome states and Execution safety for the handoff from verdict to execution.