Smart Account Provisioning for OpenClaw AI Agents on Solana
AgentVault provisions OpenClaw AI agents with secure, policy-controlled Swig smart accounts on Solana. It distributes signing keys across macOS Keychain and Shamir Secret Sharing, enforces dual-layer policies (Cedar off-chain + Swig on-chain), and features a natural language policy compiler that lets humans define agent rules in plain English.
Built for the Solana Smart Account Provisioning Bounty.
- Why AgentVault
- Key Features
- Architecture
- Security Model
- Policy Engine Design
- Progressive Autonomy (Trust Ledger)
- Risk Scoring & Approval Routing
- Guardrails
- Tech Stack
- Project Structure
- Setup & Installation
- Usage
- API Reference
- Example Flows
- Extensibility
- License
Most AI agents store their private key in a plaintext JSON file. No spending limits. No human oversight. No recovery if compromised. If the key leaks, everything is gone instantly.
Your bank account has 2FA, fraud detection, spending limits, and insurance. Your AI agent has... a plaintext file. That's a security model you wouldn't accept for a $50 PayPal account, let alone an autonomous agent managing real funds on Solana.
AgentVault fixes that.
Type policies in plain English:
"Allow the agent to swap on Jupiter up to 2 SOL per trade, max 5 SOL per day. Block all transfers to unknown addresses."
Claude API compiles this into synchronized Cedar off-chain rules + Swig on-chain spending limits in a single pass, shows you a human-readable diff, and applies on confirmation. This isn't a chatbot wrapper — it's a policy compiler that produces formally verifiable authorization rules.
Agents start in "training wheels" mode (0.1 SOL/tx) and earn expanded permissions through successful transactions. At tier thresholds, the system proposes upgrades. The owner approves or rejects. Agents literally earn their autonomy — and it can be revoked instantly.
Every transaction passes through Cedar pre-flight, risk scoring, approval routing, Solana simulation, and Swig on-chain enforcement. Even if every off-chain layer has a bug, the smart contract itself rejects overspending.
Primary key in macOS Keychain (Touch ID protected). Backup via Shamir 3-of-5 Secret Sharing. The agent process never sees the raw private key.
Dead man's switch (auto-freeze on missing heartbeat), time-of-day restrictions, risk-based approval routing, and default-deny for unrecognized operations.
User (Telegram / Discord / CLI)
|
v
+-------------------------------------------+
| OpenClaw Agent Runtime |
| |
| +--------------------------------------+ |
| | AgentVault Plugin | |
| | | |
| | Tools: | |
| | vault_provision | |
| | vault_set_policy (NL -> Cedar) | |
| | vault_execute (5-layer check) | |
| | vault_status | |
| | vault_approve / vault_reject | |
| | vault_freeze | |
| | | |
| | Hook: before_tool_call | |
| | (intercepts all calls for policy) | |
| | | |
| | Modules: | |
| | PolicyEngine (Cedar WASM) | |
| | KeyManager (Keychain+Shamir) | |
| | SwigManager (on-chain ops) | |
| | TrustLedger (autonomy tiers) | |
| | TxQueue (risk routing) | |
| | NLCompiler (Claude API) | |
| +--------------------------------------+ |
| |
+-------------------------------------------+
| |
v v
+-----------+ +---------------+
| macOS | | Swig Wallet |
| Keychain | | (on-chain) |
| + Shamir | | Solana Devnet |
+-----------+ +---------------+
Every call to vault_execute passes through all 5 layers sequentially:
Transaction Intent
|
v
[Layer 1] Cedar Pre-flight
| Evaluates all active Cedar policies against the request.
| If DENY -> blocked immediately, trust score -1.
v
[Layer 2] Risk Scoring
| Calculates weighted risk score (0-100) from 5 factors:
| amount ratio, daily budget usage, tx type, time-of-day, failure rate.
v
[Layer 3] Approval Routing
| score < autoApproveThreshold -> auto_approve
| score < requiresApprovalAbove -> delayed (60s cancellation window)
| score >= requiresApprovalAbove -> requires_approval (queued for human)
v
[Layer 4] Solana Simulation
| connection.simulateTransaction() catches on-chain errors
| before spending gas.
v
[Layer 5] Swig On-Chain Enforcement
| Even if all off-chain layers pass, Swig's on-chain spending
| limits are the final backstop. The smart contract itself
| rejects transactions exceeding the configured Actions limits.
v
TX Confirmed
plugin.ts (main entry point)
|
+-- db.ts (SQLite database)
+-- key-manager.ts (keytar + shamir-secret-sharing + crypto)
+-- swig-manager.ts (@solana/web3.js + @swig-wallet/classic)
+-- policy-engine.ts (@cedar-policy/cedar-wasm)
+-- trust-ledger.ts (scoring logic, depends on db.ts)
+-- tx-queue.ts (risk scoring, depends on trust-ledger.ts)
+-- nl-policy-compiler.ts (@anthropic-ai/sdk, depends on policy-engine.ts)
|
+-- cli.ts (standalone CLI wrapper, depends on plugin.ts)
+-- index.ts (public library exports)
No circular dependencies. Each module has a single responsibility.
Backend 1: macOS Keychain (Primary)
- Stores the agent's full Ed25519 private key
- Protected by macOS user password and Touch ID
- Accessed via
keytar.setPassword("agentvault", agentId, base64Key) - Key never enters the agent's runtime context
Backend 2: Shamir Secret Sharing (Recovery)
- On wallet creation, the private key is split into 5 shares with a threshold of 3
- Any 3 of 5 shares can reconstruct the full key
| Share | Storage | Purpose |
|---|---|---|
| Share 1 | Encrypted in SQLite (AES-256-GCM with scrypt-derived key) | Local recovery |
| Share 2 | Displayed once for owner to print/photograph | Physical backup |
| Share 3 | Stored as file for external backup (USB, cloud) | Offsite recovery |
| Share 4 | Given to trusted contact #1 | Social recovery |
| Share 5 | Given to trusted contact #2 | Social recovery |
Encryption Details (Share 1):
- Key derivation:
scrypt(passphrase, "agentvault-salt", keylen=32) - Cipher: AES-256-GCM
- Format stored:
{iv_hex}:{auth_tag_hex}:{ciphertext_hex}
| Threat | Impact | Mitigation |
|---|---|---|
| Keychain compromised (attacker has macOS access) | Can sign transactions | Swig on-chain limits cap max damage. Owner uses Shamir shares to reconstruct key on a new machine and rotates agent authority on-chain. |
| Single Shamir share leaked | None | Useless alone — attacker needs 3 of 5 shares. Owner rotates the affected share. |
| Agent process compromised (prompt injection) | Could attempt unauthorized transactions | Agent never sees raw private key. Signing happens in KeyManager. Cedar policies + Swig on-chain limits block unexpected patterns. |
| Agent process crashes | Agent stops operating | Dead man's switch auto-freezes after 30 minutes with no heartbeat. |
| Owner loses device | Loses Keychain access | Any 3 of 5 Shamir shares reconstruct the key. Social recovery via trusted contacts. |
| Cedar policy engine has a bug | Could allow transactions that should be denied | Swig on-chain limits are always equal to or tighter than Cedar limits. The smart contract is the final backstop. |
| Database compromised | Attacker sees encrypted share + policies | Share 1 is AES-256-GCM encrypted. Without passphrase + 2 more shares, key is unrecoverable. Policies are not secret. |
The agent's private key is stored in macOS Keychain and never enters the agent's context. When a transaction needs signing:
- The
vault_executehandler builds a serialized transaction - It calls
keyManager.getKeypair(agentId)to retrieve the key from Keychain - The key is used to sign within the handler function
- The key is never passed to the agent, stored in memory longer than the function scope, or logged
The agent only ever sees: wallet address, balance, transaction signatures, and policy results.
Policies are written in Cedar, a declarative authorization language by AWS with formal verification capabilities (42-60x faster than OPA/Rego).
Example — Permit swaps with limits:
permit(
principal == Agent::"agent-001",
action == Action::"swap",
resource in ProgramGroup::"approved-dexes"
) when {
context.amount_sol <= 2.0 &&
context.daily_total_sol <= 10.0
};
Example — Forbid transfers to unknown addresses:
forbid(
principal == Agent::"agent-001",
action == Action::"transfer",
resource
) unless {
resource in AddressGroup::"approved-destinations"
};
Example — Time-of-day restriction:
forbid(
principal == Agent::"agent-001",
action,
resource
) when {
context.hour_of_day < 6 || context.hour_of_day > 23
};
Swig Actions mirror Cedar policies as hard on-chain limits. The Swig smart contract evaluates these limits at transaction time — if the agent somehow bypasses every off-chain check, the on-chain program still rejects overspending.
Swig Actions used:
solRecurringLimit({ recurringAmount, window })— Daily SOL spending capprogramLimit({ programId })— Restrict to specific programs (Jupiter, Raydium, etc.)solDestinationLimit({ amount, destination })— Restrict transfers to specific addresses
On-chain limits are always equal to or tighter than Cedar limits.
The NL Policy Compiler uses Claude API (Sonnet 4) to convert English into synchronized Cedar + Swig configuration:
User Input: "Allow swaps on Jupiter, max 2 SOL per trade, 5 SOL per day"
|
v
Claude API (Sonnet 4)
|
+---------------+---------------+
| |
v v
Cedar Policies Swig Actions Config
(stored in SQLite) (updated on-chain)
Compilation output structure:
{
"cedarPolicies": ["permit(...) when { context.amount_sol <= 2.0 }"],
"swigActions": {
"solRecurringLimit": { "amount_sol": 5, "window_hours": 24 },
"programLimits": ["jupiter"]
},
"entityGroups": [
{
"entityType": "ProgramGroup",
"entityId": "approved-dexes",
"members": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"]
}
],
"explanation": "Allows swaps on Jupiter up to 2 SOL per trade, 5 SOL daily limit."
}The system shows a human-readable diff before applying. You inspect, then confirm.
Cedar uses an entity hierarchy for access control:
| Entity Type | Example | Purpose |
|---|---|---|
Agent |
Agent::"agent-001" |
The AI agent principal |
Action |
Action::"swap" |
Transaction types: swap, transfer, stake, unstake |
Program |
Program::"JUP6Lk..." |
Solana program being invoked |
ProgramGroup |
ProgramGroup::"approved-dexes" |
Whitelisted program sets |
Address |
Address::"7xKp..." |
Destination addresses |
AddressGroup |
AddressGroup::"approved-destinations" |
Whitelisted address sets |
Programs and addresses inherit group membership. A program in ProgramGroup::"approved-dexes" satisfies any policy requiring resource in ProgramGroup::"approved-dexes".
Every policy evaluation includes these context attributes:
| Attribute | Type | Description |
|---|---|---|
amount_sol |
number | SOL amount for this transaction |
daily_total_sol |
number | Total SOL spent today (from daily_spending table) |
hour_of_day |
number (0-23) | Current hour in local time |
destination |
string | Destination address (for transfers) |
token_mint |
string | SPL token mint address (for token swaps) |
portfolio_concentration |
number (0-1) | Herfindahl index of portfolio (reserved for future use) |
Every new agent starts with three "training wheels" policies:
- Permit small swaps:
amount_sol <= 0.1ANDdaily_total_sol <= 1.0 - Forbid all transfers: No direct SOL/token transfers allowed
- Forbid late-night activity: No operations between midnight and 6 AM
If no policies exist for an agent, the engine defaults to deny all (fail-safe).
| Tier | Name | Min Score | Max SOL/TX | Max SOL/Day | Auto-Approve Below | Human Approval Above |
|---|---|---|---|---|---|---|
| 0 | Training Wheels | 0 | 0.1 | 1 | risk < 20 | risk > 30 |
| 1 | Beginner | 10 | 1 | 5 | risk < 30 | risk > 50 |
| 2 | Intermediate | 25 | 5 | 25 | risk < 40 | risk > 60 |
| 3 | Advanced | 50 | 25 | 100 | risk < 50 | risk > 70 |
| 4 | Autonomous | 100 | 100 | 500 | risk < 60 | risk > 80 |
| Event | Score Change | Event Type |
|---|---|---|
| Successful transaction | +1 | tx_success |
| Transaction blocked by policy | -1 | tx_blocked |
| Policy violation (execution error) | -3 | policy_violation |
| Tier upgrade applied | 0 (logged only) | trust_upgrade |
Score floor is 0 (cannot go negative).
The system checks for upgrade eligibility after every successful transaction:
- Agent's score must be >= next tier's
minScore - Zero policy violations in the last 24 hours
- System generates a human-readable proposal message
- Owner must explicitly approve the upgrade via chat
Upgrades are never automatic — the human always has final say.
Each transaction is scored across 5 weighted factors:
| Factor | Weight | Scoring Logic | Example |
|---|---|---|---|
amount_ratio |
30% | (amountSol / tier.maxSolPerTx) * 100, capped at 100 |
1 SOL / 2 SOL limit = 50 |
daily_budget |
25% | ((dailyTotal + amount) / tier.maxDailySol) * 100, capped at 100 |
(3+1) / 5 daily = 80 |
tx_type |
20% | Base scores: swap=20, transfer=60, stake=30, unstake=40, unknown=80 | swap = 20 |
time_of_day |
10% | Off-hours (23:00-06:00)=70, normal hours=10 | 14:00 = 10 |
failure_rate |
15% | (failedTxCount / totalTxCount_24h) * 200, capped at 100 |
1 fail / 10 total = 20 |
Final score = round(sum of componentScore * weight)
if score < tier.autoApproveRiskThreshold --> auto_approve (execute immediately)
if score < tier.requiresApprovalAbove --> delayed (execute after 60s, cancellable)
if score >= tier.requiresApprovalAbove --> requires_approval (queued, 10-min timeout)
Pending transactions auto-expire after 10 minutes if not approved or rejected.
| Guardrail | Implementation | Trigger |
|---|---|---|
| Dead man's switch | Heartbeat check every 5 minutes. If no heartbeat for 30 minutes, agent is auto-frozen in DB. | Agent process crashes or hangs |
| Time-of-day restrictions | Cedar policy with context.hour_of_day condition. Default: no activity midnight-6AM. |
Late-night transactions |
| Default deny | If no active policies exist, all transactions are denied. | Misconfigured agent |
| Transfer block | Default policy forbids all direct transfers. Must be explicitly overridden. | Unauthorized fund extraction |
| Risk-based routing | High-risk transactions require human approval before execution. | Large or unusual transactions |
| Auto-expiry | Pending approvals expire after 10 minutes. | Unattended approval queues |
| On-chain backstop | Swig limits enforce hard caps even if all off-chain logic is bypassed. | Software bugs or exploits |
| Layer | Technology | Package | Purpose |
|---|---|---|---|
| Smart Account | Swig Wallet | @swig-wallet/classic |
On-chain wallet with session keys, granular Actions, single-signer execution |
| Agent Framework | OpenClaw | Plugin API | AI agent runtime with tool registration and lifecycle hooks |
| Policy Engine | Cedar WASM | @cedar-policy/cedar-wasm |
Attribute-based access control, 42-60x faster than OPA, formal verification |
| Key Storage (Primary) | macOS Keychain | keytar |
OS-native secure credential storage, Touch ID protected |
| Key Storage (Backup) | Shamir Secret Sharing | shamir-secret-sharing |
3-of-5 threshold key splitting (audited by Cure53 & Zellic) |
| NL Compiler | Claude API | @anthropic-ai/sdk |
Natural language to Cedar + Swig compilation (Sonnet 4) |
| Blockchain | Solana | @solana/web3.js |
Transaction building, simulation, and submission |
| Database | SQLite | better-sqlite3 |
Policy storage, trust logs, transaction queue, Shamir shares |
| Cryptography | Node.js crypto + tweetnacl | Built-in + tweetnacl |
AES-256-GCM encryption, Ed25519 signing |
| IDs | UUID v4 | uuid |
Agent IDs, policy IDs, transaction IDs |
Why Swig over Squads:
- Session keys with inherited permissions — agent gets a session keypair with precise spending limits that expire automatically
- Granular Actions API —
solRecurringLimit,programLimit,tokenDestinationLimitmap directly to agent guardrails - Single-signer execution — no proposal/vote/execute multisig latency, appropriate for autonomous agents
- Native AI agent focus — Swig ships an MCP server (
@swig-wallet/mcp-server), targeting this use case
agentvault/
|
+-- src/
| +-- plugin.ts # OpenClaw plugin entry + 7 tool handlers + hook
| +-- cli.ts # Standalone CLI with command + interactive modes
| +-- db.ts # SQLite schema (8 tables) + singleton connection
| +-- key-manager.ts # Keychain storage + Shamir SSS + AES-256-GCM
| +-- swig-manager.ts # Swig wallet create/update/sign/freeze
| +-- policy-engine.ts # Cedar WASM evaluation + policy CRUD
| +-- trust-ledger.ts # 5-tier progressive autonomy + scoring
| +-- tx-queue.ts # Risk scoring (5 factors) + approval routing
| +-- nl-policy-compiler.ts # Claude API NL -> Cedar + Swig compilation
| +-- index.ts # Public library exports
|
+-- data/ # SQLite database (gitignored)
+-- dist/ # Compiled JavaScript (gitignored)
|
+-- openclaw.plugin.json # OpenClaw plugin manifest
+-- package.json # Dependencies and scripts
+-- tsconfig.json # TypeScript strict mode config
+-- .env.example # Environment variable template
+-- .gitignore # Ignores node_modules, dist, data, .env
+-- README.md # This file
| File | Lines | Exports | Description |
|---|---|---|---|
db.ts |
~100 | getDb(), closeDb() |
SQLite singleton with WAL mode, foreign keys, 8-table schema |
key-manager.ts |
~225 | KeyManager class |
Ed25519 keygen, Keychain CRUD, Shamir split/combine, AES-256-GCM encrypt/decrypt, key rotation |
swig-manager.ts |
~230 | SwigManager class |
Swig wallet creation with dual authorities, Actions building, permission updates, sign-through-Swig execution, freeze |
policy-engine.ts |
~330 | PolicyEngine class |
Cedar WASM dynamic import, policy evaluation with entity hierarchy, CRUD operations, default training-wheels policies |
trust-ledger.ts |
~235 | TrustLedger class |
5-tier system with score tracking, upgrade eligibility checks (24h violation window), event logging |
tx-queue.ts |
~275 | TxQueue class |
5-factor risk assessment, 3-tier routing (auto/delayed/human), SQLite-backed queue, daily spending tracker, auto-expiry |
nl-policy-compiler.ts |
~240 | NLPolicyCompiler class |
Claude Sonnet 4 integration, structured prompt with Cedar examples, SOL-to-lamports conversion, diff generation |
plugin.ts |
~800 | register() + 7 handlers |
OpenClaw plugin registration, 5-layer execution pipeline, heartbeat monitor (dead man's switch), helper functions |
cli.ts |
~270 | CLI entry point | 8 commands + interactive REPL mode, mock OpenClaw API for standalone use |
index.ts |
~10 | Re-exports | Public API for library consumers |
8 tables, all with foreign key constraints:
-- Core agent record
agents (
id TEXT PRIMARY KEY, -- UUID
name TEXT NOT NULL, -- Human-readable name
swig_address TEXT NOT NULL, -- Solana address of Swig wallet
swig_id TEXT NOT NULL, -- Hex-encoded 32-byte Swig ID
agent_pubkey TEXT NOT NULL, -- Agent's Ed25519 public key
owner_pubkey TEXT NOT NULL, -- Owner's Solana public key
role_id INTEGER DEFAULT 1, -- Agent's Swig role ID
trust_score INTEGER DEFAULT 0, -- Accumulated trust points
trust_tier INTEGER DEFAULT 0, -- Current tier (0-4)
created_at INTEGER, -- Unix timestamp
frozen INTEGER DEFAULT 0 -- Emergency lock flag
)
-- Cedar policies (soft-deletable)
policies (
id TEXT PRIMARY KEY, -- UUID
agent_id TEXT FK -> agents,
cedar_text TEXT NOT NULL, -- Full Cedar policy syntax
nl_description TEXT, -- Original natural language input
created_at INTEGER,
active INTEGER DEFAULT 1 -- 0 = deactivated
)
-- Entity groups for Cedar (ProgramGroup, AddressGroup)
policy_entities (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT FK -> agents,
entity_type TEXT NOT NULL, -- "ProgramGroup", "AddressGroup"
entity_id TEXT NOT NULL, -- Group name
members TEXT DEFAULT '[]' -- JSON array of addresses
)
-- Trust event log (append-only)
trust_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT FK -> agents,
event_type TEXT NOT NULL, -- tx_success, policy_violation, tx_blocked, trust_upgrade
details TEXT,
trust_delta INTEGER DEFAULT 0,
trust_total INTEGER DEFAULT 0,
timestamp INTEGER
)
-- Transaction approval queue
tx_queue (
id TEXT PRIMARY KEY, -- UUID
agent_id TEXT FK -> agents,
intent TEXT NOT NULL, -- JSON-serialized TransactionIntent
risk_score INTEGER DEFAULT 0, -- 0-100
status TEXT DEFAULT 'pending', -- pending, approved, rejected, expired, executed
created_at INTEGER,
resolved_at INTEGER,
resolution TEXT -- Reason or TX signature
)
-- Encrypted Shamir shares (only share 0 stored)
shamir_shares (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT FK -> agents,
share_index INTEGER NOT NULL, -- 0-4
encrypted_share TEXT NOT NULL, -- AES-256-GCM encrypted hex
storage_hint TEXT NOT NULL -- Where this share should be kept
)
-- Daily spending tracker
daily_spending (
agent_id TEXT FK -> agents,
date TEXT NOT NULL, -- ISO date string (YYYY-MM-DD)
total_lamports INTEGER DEFAULT 0,
tx_count INTEGER DEFAULT 0,
PRIMARY KEY (agent_id, date)
)
-- Dead man's switch heartbeats
heartbeats (
agent_id TEXT PRIMARY KEY FK -> agents,
last_heartbeat INTEGER -- Unix timestamp
)- Node.js >= 20 (for ES2022 support)
- macOS (for Keychain support via
keytar; Linux/Windows use OS-native credential stores automatically) - Solana CLI (optional, for generating devnet wallets and airdrops)
git clone https://github.com/valani9/SOL.git
cd SOL
npm installcp .env.example .envEdit .env:
# Solana RPC endpoint (default: devnet)
SOLANA_RPC_URL=https://api.devnet.solana.com
# Claude API key for natural language policy compilation
ANTHROPIC_API_KEY=sk-ant-...
# Owner wallet private key (base58 string or JSON array of bytes)
# This wallet pays for Swig creation and is the root authority
OWNER_PRIVATE_KEY=<your-private-key>To generate a devnet wallet:
solana-keygen new --outfile ~/.config/solana/devnet.json
solana airdrop 5 --url devnet
# Then export the private key to OWNER_PRIVATE_KEY
cat ~/.config/solana/devnet.json
# Copy the JSON arraynpm run build # Compile TypeScript to dist/
npm run typecheck # Type check without emitting files# Create a new agent wallet
npx ts-node src/cli.ts provision --name TradingBot --sol 5 --passphrase "my-secure-passphrase"
# Set policies in natural language
npx ts-node src/cli.ts set-policy --agent TradingBot \
--policy "Allow swaps on Jupiter, max 2 SOL per trade, 5 SOL per day. Block transfers to unknown addresses."
# Replace all existing policies
npx ts-node src/cli.ts set-policy --agent TradingBot \
--policy "Only allow staking on Marinade, max 10 SOL per day" --replace
# Execute a transaction
npx ts-node src/cli.ts execute --agent TradingBot \
--type swap --amount 1 --program jupiter --desc "Swap 1 SOL for USDC"
# Check agent status
npx ts-node src/cli.ts status --agent TradingBot
# Approve a pending transaction
npx ts-node src/cli.ts approve --tx-id a3f8b2c1-...
# Reject with reason
npx ts-node src/cli.ts reject --tx-id a3f8b2c1-... --reason "Too much on memecoins"
# Emergency freeze (removes on-chain authority)
npx ts-node src/cli.ts freeze --agent TradingBotnpx ts-node src/cli.ts interactiveagentvault> provision TradingBot 5
agentvault> policy TradingBot Allow swaps on Jupiter, max 2 SOL per trade
agentvault> exec TradingBot swap 1 jupiter
agentvault> status TradingBot
agentvault> freeze TradingBot
agentvault> quit
Add to your OpenClaw configuration:
{
"plugins": ["./path/to/agentvault"]
}The plugin auto-registers 7 tools and a before_tool_call hook. Agents interact via natural language through any OpenClaw channel (Telegram, Discord, etc.).
Creates a new Swig smart wallet with secure key storage.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Agent name (must be unique) |
initial_sol |
number | No | 1 | SOL to fund the wallet |
passphrase |
string | Yes | — | Encrypts the local Shamir backup share |
What it does:
- Generates Ed25519 keypair
- Stores private key in macOS Keychain
- Splits key into 5 Shamir shares (threshold 3)
- Creates Swig wallet with owner (full control) + agent (restricted) authorities
- Funds the wallet
- Applies default training-wheels policies
- Initializes heartbeat for dead man's switch
Compiles natural language into Cedar + Swig limits.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
agent_name |
string | Yes | — | Agent to configure |
policy |
string | Yes | — | Natural language policy description |
replace_existing |
boolean | No | false | Clear all existing policies first |
What it does:
- Sends natural language + existing policies to Claude Sonnet 4
- Receives Cedar policies + Swig Actions + entity groups
- Shows human-readable diff
- Stores Cedar policies in SQLite
- Updates Swig on-chain spending limits
Executes a transaction through the 5-layer security gauntlet.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
agent_name |
string | Yes | — | Agent executing the transaction |
type |
string | Yes | — | "swap", "transfer", or "stake" |
amount_sol |
number | Yes | — | Amount in SOL |
destination |
string | No | — | Destination address (for transfers) |
program |
string | No | System Program | "jupiter", "raydium", "orca", "marinade", or a public key |
description |
string | Yes | — | Human-readable description |
Known program aliases:
jupiter→JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4raydium→675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8orca→whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCcmarinade→MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD
Shows agent state, trust score, policies, pending approvals, and recent activity.
| Parameter | Type | Required | Description |
|---|---|---|---|
agent_name |
string | Yes | Agent to query |
Approves a pending transaction and executes it.
| Parameter | Type | Required | Description |
|---|---|---|---|
tx_id |
string | Yes | Transaction UUID from vault_execute |
Rejects a pending transaction.
| Parameter | Type | Required | Description |
|---|---|---|---|
tx_id |
string | Yes | Transaction UUID |
reason |
string | No | Explanation for rejection |
Emergency freeze: removes the agent's on-chain Swig authority and rejects all pending transactions.
| Parameter | Type | Required | Description |
|---|---|---|---|
agent_name |
string | Yes | Agent to freeze |
This action:
- Calls
getRemoveAuthorityInstructions()on Swig to remove the agent's role - Sets
frozen = 1in the database - Rejects all pending transactions with reason "Agent frozen by owner"
- The agent can no longer sign any transactions on-chain
$ npx ts-node src/cli.ts provision --name TradingBot --sol 5 --passphrase my-secret
AgentVault: Wallet Created
Agent: TradingBot
Agent ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Swig Wallet: 7xKpN4vR8mQ2wL5jD9bYc3nYf...
Agent Pubkey: Fg7Ja2eLpXm8Rv3bKw...
Balance: 5 SOL
Trust Tier: Training Wheels (0.1 SOL/tx, 1 SOL/day)
Key Storage:
Primary: macOS Keychain (Touch ID protected)
Backup: Shamir Secret Sharing (3-of-5 threshold)
Shamir Backup Shares:
Share 1: Encrypted in local database
Share 2 (owner-print-or-photo): a4f8c2...9e31b7
Share 3 (external-backup-file): 7b21d9...f4a8c3
Share 4 (trusted-contact-1): e9c6a1...2d7f85
Share 5 (trusted-contact-2): 3f8b72...c1e946
SAVE SHARES 2-5 NOW — they won't be shown again.
Default Policies Applied:
- Swaps up to 0.1 SOL, 1 SOL/day max
- All direct transfers blocked
- No activity between midnight and 6 AM
$ npx ts-node src/cli.ts set-policy --agent TradingBot \
--policy "Allow swaps on Jupiter up to 2 SOL per trade, max 5 SOL per day. Block all transfers to unknown addresses."
Policy Compiled & Applied
=== Policy Changes ===
ADDING Cedar Rules:
+ permit(principal == Agent::"a1b2c3d4-...", action == Action::"swap", ...)
+ forbid(principal == Agent::"a1b2c3d4-...", action == Action::"transfer", ...)
SWIG On-Chain Limit: 5 SOL per 24 hours
SWIG Allowed Programs: JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4
Entity Groups:
+ ProgramGroup::"approved-dexes" with 1 members
2 Cedar rule(s) added. On-chain Swig limits updated.
$ npx ts-node src/cli.ts execute --agent TradingBot --type swap --amount 1 --program jupiter --desc "Swap 1 SOL for USDC"
Transaction EXECUTED
Cedar Policy: ALLOW
Risk Score: 12/100 (auto_approve)
amount_ratio: 50 (1 SOL / 2 SOL limit = 50%)
daily_budget: 20 (1 SOL / 5 SOL daily = 20%)
tx_type: 20 (swap = low risk)
time_of_day: 10 (14:00 = normal hours)
failure_rate: 0 (0% recent failures)
TX Signature: 4vJ9mK2...xK2m
Amount: 1 SOL
Trust Score: 1 (+1)
$ npx ts-node src/cli.ts execute --agent TradingBot --type transfer --amount 3 --destination Random111...111 --desc "Transfer to unknown"
Transaction BLOCKED
Cedar Policy: DENY
- Transfers to unknown addresses forbidden by policy
- Matched: forbid(action == Action::"transfer") unless resource in AddressGroup::"approved-destinations"
Trust Score: 0 (-1)
$ npx ts-node src/cli.ts execute --agent TradingBot --type swap --amount 4.5 --program jupiter --desc "Swap 4.5 SOL for BONK"
Transaction QUEUED for Approval
Cedar Policy: ALLOW
Risk Score: 78/100 — requires human approval
amount_ratio: 225% (exceeds per-tx limit)
daily_budget: 90% of daily limit
TX ID: a3f8b2c1-...
Auto-expires in 10 minutes.
$ npx ts-node src/cli.ts reject --tx-id a3f8b2c1-... --reason "Too much on memecoins"
Transaction Rejected
AgentVault is designed as a set of independent, pluggable modules. Each module has a single responsibility and clean interface.
| Extension | Where to Add | Effort |
|---|---|---|
| Ledger hardware wallet | New KeyStorageBackend in key-manager.ts using @ledgerhq/hw-app-solana |
Medium |
| Secure Enclave / TEE | Use P-256 key as authorization layer for Ed25519 access | Medium |
| Volatility-aware limits | Fetch SOL price from Pyth oracle in policy-engine.ts, halve limits when vol > 15% |
Small |
| Portfolio concentration | Calculate Herfindahl index in tx-queue.ts, add as Cedar context attribute |
Small |
| Anomaly detection | Track 7-day rolling averages in tx-queue.ts, flag 2+ std dev deviations |
Medium |
| Telegram/Discord approvals | Wire vault_approve/vault_reject to bot commands via OpenClaw channels |
Small |
| New agent frameworks | Only plugin.ts is OpenClaw-specific. Core modules work with any framework. |
Small |
| New Cedar policy templates | Add to addDefaultPolicies() or create a policy template library |
Trivial |
| Passkey authentication | Swig supports SECP256R1 authorities — add passkey-based owner auth | Medium |
| Windows/Linux support | keytar automatically uses Windows Credential Manager / Linux Secret Service |
Built-in |
MIT