AI agent wallet guardrails enforced by the Cardano ledger — not by trusting your app.
Beni is an Aiken smart contract + TypeScript SDK that gives AI agents (or you, or anyone you don't fully trust with a wallet) a guarded wallet with spending rules baked directly into the Plutus script. Per-transaction caps, 24h rolling limits, address whitelists, emergency freeze — all enforced on-chain. If a transaction breaks a rule, the ledger itself rejects it. No app-layer trust required, and no server in the middle that could be talked out of it.
Built for the Gimbalabs Piece of Pie 2026 Hackathon · April 13 – July 2026.
Live on Cardano mainnet → usebeni.xyz (also at beni-wallet.vercel.app)
AI agents that control wallets are a real risk. There's no standard way to say "this agent can spend up to 2 ADA per transaction, max 10 ADA per day, only to these addresses" and have it actually hold — not as a setting the app enforces, but as something nothing can override. Most solutions put guardrails in the application layer, which the agent itself (or a compromised app, or a leaked key) can bypass. Beni puts them in the Plutus script instead.
| Guardrail | How it works |
|---|---|
| Per-transaction cap | Validator rejects any single spend above the configured lovelace limit |
| 24h rolling window | Daily cap tracked on-chain with a self-updating datum; window resets automatically |
| Address whitelist | Agent can send to pre-approved credential hashes without touching the caps |
| Emergency freeze | Owner halts all spending instantly with one signed transaction — and the validator verifies the freeze actually took effect, not just that the owner signed something |
| Thread token security | A one-shot NFT minted at creation prevents an attacker from planting a fake UTxO with a forged datum |
| Human approval queue | Above-cap spends queue for owner co-signature via CIP-30 wallet connect |
Web dashboard — connect any CIP-30 wallet (Eternl, Nami, Lace, Flint, Vespr, Typhon). Your own wallet becomes the agent — Beni never holds a custodial key. Deploy your own guarded wallet, set your own caps, watch your own contract's live balance and activity.
Telegram bot — chat-first alternative. Message @Beniwallet_bot and get a guarded wallet in two commands. Custodial by necessity (a chat interface can't pop up a wallet extension), but every key is encrypted at rest, and /importkey lets you bring a key you already control instead of trusting the bot to generate one.
MCP server — give any MCP-compatible AI agent (Claude, or a third-party framework like Nous Research's Hermes Agent) the same guardrail tools with zero Cardano setup on the agent's side.
- Connect any CIP-30 wallet — real mainnet, real ADA
- First-run guided tour walks through the workspace, getting-started checklist, live status, and emergency freeze
- Deploy your own guarded wallet: set caps, pay a one-time 2 ADA fee (verified on-chain before deploy), two signed transactions later you have a live contract
- Dashboard reads your own deployed contract's real balance, spend history, and guardrail state — not a shared demo's
- Ask the built-in AI assistant about your wallet in plain English
# 1. Install Aiken
# https://aiken-lang.org/installation-instructions
# 2. Build and test the validators
aiken check # runs all on-chain tests
aiken build # compiles to plutus.json
# 3. Install SDK dependencies
cd sdk && npm install
# 4. Type-check the SDK
npm run typecheck
# 5. Run SDK unit tests (no network required)
npm test
# 6. Run the offline demo (all guardrail scenarios)
npx tsx examples/demo.tsGive any MCP-compatible AI agent a Beni-guarded wallet with zero Cardano setup. The sdk/mcp-server.ts process exposes five tools over Model Context Protocol, on either Preview or Mainnet (set via BENI_NETWORK):
| Tool | Keys required | Description |
|---|---|---|
check_limits |
None | Validate a spend offline, instantly |
get_status |
None | Caps, daily usage, frozen state |
spend |
Blockfrost + agent key | Guarded spend to chain |
freeze |
Blockfrost + agent key | Emergency freeze |
create_wallet |
Blockfrost + agent key | Deploy a new wallet |
Claude Desktop / Hermes Agent / any MCP-compatible client — add an entry like:
{
"mcpServers": {
"beni": {
"command": "npx",
"args": ["tsx", "C:/path/to/sdk/mcp-server.ts"],
"env": {
"BENI_NETWORK": "Mainnet",
"BLOCKFROST_MAINNET_KEY": "mainnetXXXXXXXXXXXXXXXX",
"AGENT_PRIVATE_KEY": "ed25519_sk1..."
}
}
}
}A ready-to-edit copy with the correct path is at sdk/claude-desktop-config.example.json. Hermes Agent uses the same shape under mcp_servers: in ~/.hermes/config.yaml.
Test offline (no keys needed):
cd sdk && npx tsx examples/demo.ts
# All 6 guardrail scenarios pass with no Blockfrost keyimport { makeLucid, createAgentWallet, agentSpend, freezeWallet } from "beni-sdk";
const lucid = await makeLucid({
network: "Mainnet",
blockfrostApiKey: process.env.BLOCKFROST_MAINNET_KEY,
});
lucid.selectWallet.fromPrivateKey(process.env.AGENT_PRIVATE_KEY);
// Deploy a new agent wallet on-chain
const wallet = await createAgentWallet(lucid, {
perTxCapLovelace: 2_000_000n, // 2 ADA per-tx cap
dailyCapLovelace: 10_000_000n, // 10 ADA daily cap
allowedCredentialHashes: [], // empty = no whitelist restriction
ownerPkh: "your_owner_pkh_hex",
isFrozen: false,
});
// Agent spends — guardrails enforced on-chain
const result = await agentSpend(lucid, wallet, "addr1...", 1_000_000n);
console.log(result.txHash);
// Owner freezes the wallet instantly
await freezeWallet(lucid, wallet);beni/
├── validators/
│ ├── agent_wallet.ak # Guardrail enforcement — per-tx cap, daily window,
│ │ # whitelist, freeze (with a verified is_frozen invariant),
│ │ # thread token auth
│ └── thread_token.ak # One-shot minting policy (parameterised per wallet)
├── validators/agent_wallet_test.ak # Aiken on-chain tests
├── sdk/
│ ├── mcp-server.ts # MCP server — Preview or Mainnet via BENI_NETWORK
│ ├── scripts/deploy-wallet-mainnet.ts # Mainnet deploy script (--confirm gated)
│ ├── claude-desktop-config.example.json
│ └── src/
│ ├── index.ts # createAgentWallet, agentSpend, ownerAction, freezeWallet
│ ├── validators.ts # Compiled CBOR from plutus.json
│ ├── types.ts # GuardrailConfig, BeniWallet, CreateWalletConfig
│ ├── datum.ts # WalletDatum encode/decode (field-order critical)
│ ├── validation.ts # Client-side mirror of on-chain guardrail logic
│ ├── analytics.ts # getDailyUsage, getTransactionHistory, getBalance
│ ├── approvals.ts # Above-cap spend queue + CIP-30 owner approval
│ ├── lucid-setup.ts # Blockfrost provider factory
│ └── errors.ts # GuardrailViolationError, WalletFrozenError, etc.
├── app/
│ ├── index.html # Entry point — clean-path routing (no #hash)
│ ├── styles.css # Design tokens + hand-drawn UI primitives
│ ├── components.jsx # Shared primitives: Wordmark, TopNav, Footer, WalletConnect, etc.
│ ├── landing.jsx # Marketing landing page
│ ├── dashboard.jsx # Wallet dashboard — first-run tour, getting-started
│ │ # checklist, per-user contract data, 6 tool tabs
│ └── pages.jsx # Docs site (plain-language "Using the dashboard" +
│ # developer SDK reference) + Security page
├── api/
│ ├── deploy.js # Vercel serverless — public wallet deploy endpoint
│ ├── fee.js # Vercel serverless — the 2 ADA deployment fee flow
│ ├── chat.js # Vercel serverless — AI assistant
│ ├── blockfrost.js # Vercel serverless — Blockfrost proxy (Preview/Mainnet)
│ └── agent-state.js # Vercel serverless — per-address on-chain state reads
├── integrations/telegram/ # Multi-tenant Telegram bot — encrypted keys, Redis persistence
├── scripts/ # CI guards: network-consistency + bytecode-sync checks
├── plutus.json # Compiled validators (aiken build output)
├── vercel.json # Deployment config
└── .github/workflows/ci.yml # aiken check + build, tsc, SDK tests, CI guards
type WalletDatum {
per_tx_cap: Int, // max lovelace per single spend
daily_cap: Int, // max lovelace per 24h window
allowed_addresses: List<CredentialHash>,
owner_pkh: PubKeyHash,
agent_pkh: PubKeyHash, // required signer on every Spend
last_window_start: Int, // POSIX ms — window reset anchor
window_spent: Int, // lovelace spent in current window
is_frozen: Bool,
thread_token_policy: PolicyId,
}| Index | Name | Who calls it | Enforced |
|---|---|---|---|
| 0 | Spend |
Agent — normal spend | Agent's own signature, caps, whitelist, thread token continuity |
| 1 | OwnerAction |
Wallet owner — update rules, unfreeze, reclaim | Owner's signature |
| 2 | FreezeWallet |
Wallet owner — emergency halt | Owner's signature and verification that the continuing datum actually has is_frozen: true, with every other rule field unchanged |
Cardano's eUTXO model means anyone can send a UTxO to the script address with a fake datum (e.g. window_spent: 0 to reset the daily limit). Beni prevents this with a thread token — a unique NFT minted exactly once at wallet creation using a one-shot minting policy parameterised by the seed UTxO reference. The validator checks on every spend that the continuing output carries exactly 1 thread token; a forged UTxO won't have it.
agent_wallet.ak— guardrail validator with all 5 rules, including a verified freeze invariantthread_token.ak— one-shot minting policy- Full Aiken test suite, including tests that specifically probe the freeze invariant
createAgentWallet,agentSpend,ownerAction,freezeWalletgetDailyUsage/getTransactionHistory/getBalance— analytics queriesqueueSpend/approveSpend— above-cap approval queue- MCP server exposing 5 guardrail tools, Preview or Mainnet
- Landing page with real mainnet proof links
- Dashboard — first-run guided tour, a getting-started checklist for new users, per-user contract data (not a shared demo), 6 tool tabs (overview, monitor, transactions, approvals, rules, whitelist)
- AI assistant embedded in the dashboard, with an animated open/close instead of an instant snap
- Docs site with a plain-language "using the dashboard" walkthrough ahead of the developer SDK reference
- Clean URL routing (
/dashboard,/docs,/security— no#hash)
- Multi-tenant — every user gets their own on-chain guarded wallet
- Custodial keys encrypted at rest (AES-256-GCM), Redis-backed persistence
/importkeyfor bringing your own key instead of trusting the bot to generate one
aiken check/aiken build/ SDK typecheck / SDK tests, every push- Two custom checks: no hardcoded testnet-only endpoints left behind after a mainnet cutover, and the compiled validator bytecode can't silently drift out of sync between the SDK and the public deploy API
| Layer | Technology |
|---|---|
| Smart contracts | Aiken v1.1.x — PlutusV3 / Conway era |
| Transaction building | @lucid-evolution/lucid v0.4.31 |
| Chain queries | Blockfrost — Mainnet + Preview |
| AI layer | Claude API |
| Frontend | React 18 UMD + Babel standalone (no build step) |
| Bot | Telegram Bot API + Redis (Railway) |
| Hosting | Vercel serverless |
| Language | TypeScript + Node.js 22 |
iamharrie01@gmail.com · GitHub · @IamHarrie
MIT