@kova-sdk/wallet
v1.0.2
Published
A policy-constrained crypto wallet SDK for autonomous AI agents
Maintainers
Readme
AI agents need to transact on-chain. Without guardrails, a single hallucination or prompt injection can drain a wallet. kova sits between your agent and the blockchain, enforcing spending limits, allowlists, rate limits, time windows, and human approval gates on every transaction before it touches the network.
Agent → Intent → Policy Engine → Build Tx → Sign → Broadcast → Audit Log
↓ DENY
Circuit BreakerWhy kova
- Your agent says "send 100 SOL" but your policy caps it at 5 SOL per transaction — denied.
- Your agent tries to send funds to an unknown address — denied by allowlist.
- A prompt injection tricks your agent into rapid-fire transfers — denied by rate limit, then circuit breaker kicks in.
- A high-value transaction needs human sign-off — held until approved via webhook or custom callback.
- Every transaction, approved or denied, is recorded in a tamper-evident audit log.
Features
| Category | Details | |----------|---------| | Policy Engine | 5 composable rules, deny-by-default, fail-closed, two-phase evaluation | | Spending Limits | Per-transaction, daily, weekly, monthly caps (per-token and USD via Pyth oracle) | | Address Allowlist | Restrict transfers to approved addresses/programs; denylist support | | Rate Limiting | Max transactions per minute/hour with store-backed counters | | Time Windows | Restrict to business hours (timezone-aware, multiple windows) | | Approval Gates | Human-in-the-loop via webhook or custom callback for high-value transactions | | MCP Server | Agents interact via Model Context Protocol — framework-agnostic (Claude, OpenAI, LangChain, any MCP client) | | Signers | LocalSigner (dev), MpcSigner with Turnkey provider (production) | | Stores | MemoryStore (dev), SqliteStore (single server), RedisStore (multi-server) | | Solana | SOL transfers, SPL tokens with ATA creation, transaction simulation | | Oracles | Pyth on-chain price feeds, multi-oracle consensus provider | | Audit Log | SHA-256 hash-chained with HMAC per-entry integrity, checkpoint hashing | | Circuit Breaker | Auto-cooldown after consecutive denials, single-process lock enforcement | | Security | Store operation timeouts, timing side-channel resistance, error sanitization |
Important
ESM-only — kova is published as ES modules only. Use
import, notrequire(). Yourtsconfig.jsonshould have"module": "Node16"and"moduleResolution": "Node16".
Single-instance deployment — The SDK's mutex, circuit breaker, idempotency cache, and spending counters are designed for a single Node.js process per store. Running multiple processes against the same store without external distributed locking can cause TOCTOU races, spending limit bypasses, and audit log inconsistencies. For multi-process deployments, use
RedisStorewith per-instancestorePrefix, or implement external coordination (e.g., Redis Redlock).
Native dependencies are optional —
better-sqlite3(forSqliteStore) andioredis(forRedisStore) are optional peer dependencies. Install only what you need.better-sqlite3requires C++ build tools (see Installation). If you only needMemoryStorefor development, no native dependencies are required.
Quick Start
Install
npm install @kova-sdk/walletFor persistent storage (single server):
npm install better-sqlite3 # requires C++ build toolsFor multi-server deployments:
npm install ioredisMinimal example
import { Keypair } from "@solana/web3.js";
import {
AgentWallet, Policy, LocalSigner, MemoryStore, SolanaAdapter,
} from "@kova-sdk/wallet";
const signer = new LocalSigner(Keypair.generate());
const store = new MemoryStore();
const chain = new SolanaAdapter({ rpcUrl: "https://api.devnet.solana.com", network: "devnet" });
const policy = Policy.create("demo")
.spendingLimit({ perTransaction: { amount: "1", token: "SOL" }, daily: { amount: "5", token: "SOL" } })
.rateLimit({ maxTransactionsPerMinute: 5 })
.build();
const wallet = new AgentWallet({ signer, chain, policy, store });
const result = await wallet.execute({
type: "transfer",
chain: "solana",
params: { to: "GsbwXfJraMomNxBcjYLcG3mxkBUiyWXAB32fGbSQQRre", amount: "0.5", token: "SOL" },
});
console.log(result.status); // "confirmed" | "denied" | "pending" | "failed"Architecture
┌─────────────────────────────────────────────────────────────────┐
│ AgentWallet │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌─────────┐ ┌────────────┐ │
│ │ Signer │ │ Policy Engine │ │ Store │ │ Chain │ │
│ │ │ │ │ │ │ │ Adapter │ │
│ │ Local │ │ SpendingLimit│ │ Memory │ │ │ │
│ │ MPC │ │ Allowlist │ │ SQLite │ │ Solana │ │
│ │ Turnkey │ │ RateLimit │ │ Redis │ │ (more │ │
│ │ │ │ TimeWindow │ │ Prefixed│ │ coming) │ │
│ │ │ │ ApprovalGate │ │ │ │ │ │
│ └──────────┘ └──────────────┘ └─────────┘ └────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Audit Log │ │ Circuit │ │ MCP Server │ │
│ │ (hash-chain)│ │ Breaker │ │ (Model Context │ │
│ │ │ │ │ │ Protocol) │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Oracles: Pyth price feeds · Multi-oracle consensus │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘Every call to wallet.execute(intent) follows this pipeline:
- Validate the intent structure and parameters
- Check audit log integrity (fail-closed if tampered)
- Circuit breaker check (auto-cooldown after consecutive denials)
- Policy engine evaluates rules sequentially — first DENY stops execution
- Build transaction via the chain adapter
- Sign via the signer (key material never touches policy or chain layers)
- Broadcast to the network
- Log the result to the hash-chained audit trail
Policy Engine
Compose rules to match your risk profile. Rules are evaluated in order — the first DENY stops execution.
const policy = Policy.create("production")
.spendingLimit({
perTransaction: { amount: "10", token: "SOL" },
daily: { amount: "50", token: "SOL" },
})
.allowAddresses(["addr1", "addr2"])
.rateLimit({ maxTransactionsPerMinute: 5 })
.activeHours({
timezone: "America/New_York",
windows: [{ days: ["mon", "tue", "wed", "thu", "fri"], start: "09:00", end: "17:00" }],
})
.requireApproval({ above: { amount: "25", token: "SOL" } })
.build();| Rule | What it does | |------|-------------| | SpendingLimit | Per-transaction, daily, weekly, monthly caps. Rolling TTL windows with BigInt precision. USD limits via Pyth oracle. | | Allowlist | Restrict to approved addresses/programs/tokens. Separate denylist support. | | RateLimit | Max transactions per minute/hour. Store-backed counters. | | TimeWindow | Restrict to business hours. Timezone-aware, multiple windows per day. | | ApprovalGate | Require human approval above a threshold. Webhook or custom callback with intent hash binding. |
Policies use two-phase evaluation: a dry-run phase prevents counter inflation on denied transactions, so a denied spend doesn't eat into your rate limit or spending budget.
Policies are fully serializable:
const json = policy.toJSON(); // save as JSON
const loaded = Policy.fromJSON(json); // reconstruct later
const stricter = Policy.extend(policy, "strict").spendingLimit({ ... }).build(); // extendAI Integration
Agents interact with the wallet exclusively through an MCP (Model Context Protocol) server. Any AI framework that supports MCP — Claude, OpenAI, LangChain, or custom agents — can connect. Policy enforcement happens automatically on every tool call.
import { Keypair } from "@solana/web3.js";
import {
AgentWallet, Policy, LocalSigner, MemoryStore, SolanaAdapter,
createMcpServer,
} from "@kova-sdk/wallet";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const wallet = new AgentWallet({ signer, chain, policy, store });
// Start the MCP server — agents connect via stdio (or any MCP transport)
const server = createMcpServer(wallet);
await server.connect(new StdioServerTransport());Or use the convenience helper:
import { createMcpStdioServer } from "@kova-sdk/wallet";
const server = await createMcpStdioServer(wallet);
// Server is now running and accepting tool calls via stdioAvailable tools
| Tool | Description |
|------|-------------|
| wallet_transfer | Transfer SOL, USDC, or USDT |
| wallet_swap | Swap tokens (requires pre-built swap transaction) |
| wallet_mint | Mint NFTs from a collection |
| wallet_stake | Stake tokens with a validator |
| wallet_get_balance | Query wallet balance for a token |
| wallet_get_all_balances | Query all token balances in one call |
| wallet_get_policy | View current policy rules and constraints |
| wallet_get_spending_remaining | Check remaining budget per spending window |
| wallet_get_supported_tokens | List supported tokens with mint addresses |
| wallet_get_address | Get the wallet's public address |
| wallet_get_token_price | Get current USD price from oracle |
| wallet_estimate_fee | Estimate network fee before sending |
| wallet_get_transaction_history | Query past transactions |
| wallet_get_transaction_status | Check transaction confirmation status |
| wallet_execute_custom | Raw on-chain instructions (opt-in, dangerous) |
Default tools: All read tools are enabled by default. Write operations (
wallet_transfer, etc.) must be explicitly enabled viaenabledTools. Onlywallet_execute_customrequires theincludeDangerousflag.
Signers
| Signer | Use case |
|--------|----------|
| LocalSigner | Development. In-memory Ed25519 signing. Guarded against production use. |
| MpcSigner | Production. Hardware-backed signing via pluggable providers. Retry with backoff, configurable timeout, cancellation via AbortSignal. |
| TurnkeyProvider | MPC provider for Turnkey. Drop-in for MpcSigner. |
The Signer interface is minimal — getAddress(), sign(), healthCheck(), destroy() — so you can implement your own provider for Fireblocks, Lit Protocol, or any other MPC backend.
Stores
| Store | Use case |
|-------|----------|
| MemoryStore | Development. In-memory, data lost on exit. Guarded against production. |
| SqliteStore | Production (single server). Persistent, WAL mode, HMAC-protected counters. Requires better-sqlite3. |
| RedisStore | Production (multi-server). AES-256-GCM encryption at rest, HMAC counter integrity, optional TLS enforcement. Requires ioredis. |
| PrefixedStore | Multi-wallet. Wraps any store, namespaces keys per wallet to prevent counter collisions. |
Floating-point precision note:
MemoryStorecounters use IEEE 754 doubles, which can accumulate drift over many increments. For high-precision accounting, preferSqliteStore(native numeric types) orRedisStore(INCRBYFLOAT).
Approval Channels
| Channel | How it works |
|---------|-------------|
| WebhookApprovalChannel | POSTs HMAC-SHA256 signed approval requests to your endpoint. SSRF protection with private IP blocking. Configurable timeout (default 5 min). |
| CallbackApprovalChannel | Developer-provided callbacks for custom approval flows — Slack, Discord, email, push notifications, or any other channel. |
The ApprovalChannel interface (requestApproval()) is open for custom implementations.
Oracles
| Provider | How it works |
|----------|-------------|
| createPythPriceProvider | Reads Pyth on-chain price feeds directly from Solana RPC. In-memory TTL cache, staleness and confidence validation, fail-closed on error. |
| createConsensusProvider | Multi-oracle aggregation with median or first-success fallback strategy. Resistant to single-source price manipulation. |
Oracles power USD-denominated spending limits in the policy engine.
Security
| Protection | Implementation |
|------------|---------------|
| Fail-closed | Exceptions in policy rules deny the transaction. Audit log failures block all transactions. |
| Two-phase evaluation | Dry-run prevents counter inflation on denied transactions. |
| Circuit breaker | Consecutive denials trigger automatic cooldown with single-process lock enforcement. |
| Hash-chained audit | SHA-256 linked entries with per-entry HMAC, checkpoint hashing every 1000 entries, verifyIntegrity() tamper detection. |
| Serialized execution | FIFO async mutex prevents TOCTOU race conditions. |
| Approval integrity | SHA-256 intent hashing prevents TOCTOU between approval and execution. |
| DNS pinning | SSRF and DNS rebinding protection for RPC endpoints. |
| Store timeouts | All store operations wrapped with configurable timeout (default 5s). |
| Timing resistance | Configurable minEvaluationTimeMs pads policy evaluation to mask rule count and denial source. |
| Idempotency | Duplicate intent IDs return cached results. |
| Error sanitization | Errors are sanitized before returning to agents — no policy reconnaissance. |
| Counter integrity | HMAC-protected store counters detect tampering. |
Report vulnerabilities via GitHub Security Advisory.
Project Structure
kova/
├── src/
│ ├── core/ # AgentWallet, intents, results, circuit breaker
│ ├── policy/ # Policy builder, engine, and 5 rule implementations
│ ├── signers/ # LocalSigner, MpcSigner, TurnkeyProvider
│ ├── stores/ # MemoryStore, SqliteStore, RedisStore, PrefixedStore
│ ├── chains/solana/ # SolanaAdapter, SOL + SPL token transfers
│ ├── approval/ # WebhookApprovalChannel, CallbackApprovalChannel
│ ├── adapters/ # MCP server adapter (sole agent interface)
│ ├── oracles/ # Pyth price feeds, consensus provider
│ └── logging/ # Hash-chained audit logger
├── tests/ # 1,281 unit and integration tests
└── docs-site/ # VitePress documentationDevelopment
git clone https://github.com/kova-wallet/kova.git
cd kova
npm install
npm run typecheck # type checking
npm run lint # eslint
npx vitest run # run all tests
npm run build # compile to dist/License
MIT — see LICENSE.
