Skip to content

IamHarrie-Labs/beni

Repository files navigation

Beni

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)


The problem

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.


What it does

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

Three ways to use it

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.


Live demo

usebeni.xyz

  • 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

Quick start

# 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.ts

MCP server

Give 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 key

SDK usage

import { 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);

Project structure

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

On-chain design

Datum schema

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,
}

Redeemers

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

Thread token security

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.


What's been built

Smart contracts (Aiken / PlutusV3)

  • agent_wallet.ak — guardrail validator with all 5 rules, including a verified freeze invariant
  • thread_token.ak — one-shot minting policy
  • Full Aiken test suite, including tests that specifically probe the freeze invariant

SDK (TypeScript)

  • createAgentWallet, agentSpend, ownerAction, freezeWallet
  • getDailyUsage / getTransactionHistory / getBalance — analytics queries
  • queueSpend / approveSpend — above-cap approval queue
  • MCP server exposing 5 guardrail tools, Preview or Mainnet

Web app (React + Vercel)

  • 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)

Telegram bot

  • Multi-tenant — every user gets their own on-chain guarded wallet
  • Custodial keys encrypted at rest (AES-256-GCM), Redis-backed persistence
  • /importkey for bringing your own key instead of trusting the bot to generate one

CI guards

  • 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

Stack

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

Contact

iamharrie01@gmail.com · GitHub · @IamHarrie


License

MIT

About

AI Agent Wallet Guardrails on Cardano

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages