Skip to content
Warden

Agent wallets with limits that hold

Fund one account and give each AI agent its own key and policy. The contract refuses any payment outside it, and says why.

Live on Base Sepolia. ETH and any ERC-20. The contracts are testnet code and have not been audited.

research-agent

agent 0x7a3e…a9e1

USDC
5 per payment
50 per day
Pays
Inference API, Weather data
Asks owner
at 4 USDC and above
Valid until
31 Dec 2026
Sample policy and payments. Decisions computed by the same rules as the contract.

15 October 2026

OpenAI disclosed that one of its research models, out of test ETH mid-task, derived keys from publicly known seed phrases and paid for a Sepolia deployment from a wallet it did not own. Agents need money of their own, with limits that hold when the agent goes off script. Sources: OpenAI's report, CoinDesk, Gizmodo.

Set a policy, then try to break it

Every stamp here comes from the same rules the contract runs, in the same order. Change a cap and watch the stamps on the payments update. Nothing here touches a chain.

Policy for research-agent

USDC
USDC
USDC
USDC
ETH

No limit set, so the agent cannot spend ETH at all.

Recipients
Validity

Payments to try

The stamp shows what the contract would decide right now.

  • 3 USDC to Inference APImemo inv-1042
  • 4.50 USDC to Weather datamemo q4-history
  • 12 USDC to GPU rentalTARGET_NOT_ALLOWED
  • 2 USDC to 0x9f3a…c41dTARGET_NOT_ALLOWED
  • 0.002 ETH to Inference APITOKEN_NOT_ALLOWED
  • Call getAmountsOut on Swap routerread a price, moves nothing
  • Call swapExactTokensForTokens on Swap routerSELECTOR_NOT_ALLOWED

Your own payment

Denied

Owner and clock

Balance 120 USDC, 0.05 ETH
Day 1, 09:00 UTC

What the contract checked

Send a payment to see each gate it passes.

  1. Caller is a registered agentNOT_AGENT
  2. Account is not pausedPAUSED
  3. Policy has startedNOT_YET_VALID
  4. Policy has not expiredEXPIRED
  5. Recipient is on the allowlistTARGET_NOT_ALLOWED
  6. Function is allowed on that contractSELECTOR_NOT_ALLOWED
  7. Token has a limitTOKEN_NOT_ALLOWED
  8. Amount is within the per-payment capOVER_PER_TX
  9. Window total stays within the period capOVER_PERIOD
  10. Amount is below the approval thresholdNeedsApproval
  11. Account holds enoughINSUFFICIENT_BALANCE

Budget left in this window

  • USDC50 of 50 left

    Window resets in 15 h 0 min

Ledger

Nothing yet. Every attempt lands here, refused ones included.

Two keys, one place where the rules live

The agent signs its own transactions. The account holds the money and checks each one against the policy you set. A server is never in the path, so there is nothing to bypass.

Agent

Its own key, nothing else

Signs pay() or call() from a hot key in your agent runtime, through the SDK or the MCP server.
pay(USDC, api, 3)

Warden account

Holds the funds and every policy

On every payment it asks, in this order:

  1. Is the caller an agent, and is the account live?
  2. Is the policy inside its valid dates?
  3. Are the recipient and the function allowlisted?
  4. Does the token have a limit, and is the amount inside it?
  5. Does the window still have room?
  6. Is the amount below the approval threshold?
  7. Does the account hold enough?
  • Allowed

    Executes in the same transaction. The account emits Spent with the memo and the policy hash.

  • Needs approval

    Parked as a request. Nothing moves until the owner approves, within 24 hours by default.

  • Denied

    Reverts with Denied(reason). The agent can read the same reason from check() before it pays gas.

Owner

Your browser wallet

Sets each policy, funds the account, approves held payments, pauses and withdraws. Calls setAgent, approve, setPaused.

A dozen lines from agent to payment

The SDK asks the contract first and throws the exact reason before your agent spends gas. Models get the same calls as MCP tools.

check
Free view. Allowed, needs approval, or denied with a reason.
pay
Sends ETH or an ERC-20. Returns a receipt or a request id.
call
Calls an allowlisted contract, value and token calldata counted.
waitForApproval
Resolves when the owner approves or rejects.

Preview: @warden/sdk and @warden/mcp are not published yet. These samples follow the v0.1 interface.

SDK reference
agent.ts
import { createWardenAgent } from "@warden/sdk";
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";

const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });
const agentWallet = createWalletClient({
  account: privateKeyToAccount(process.env.AGENT_KEY as `0x${string}`),
  chain: baseSepolia,
  transport: http(),
});

const agent = createWardenAgent({ publicClient, walletClient: agentWallet, account: WARDEN_ACCOUNT });

// Free: asks the contract what it would decide, moves nothing.
const decision = await agent.check({ token: "USDC", to: apiProvider, amount: "3" });

// Checks first and throws WardenDenied with the reason before any gas is spent.
const result = await agent.pay({ token: "USDC", to: apiProvider, amount: "3", memo: "inv-1042" });
// result.status: "executed" (with receipts) or "pending_approval" (with a requestId)

What a policy stops, and what it cannot

Warden limits the damage a misbehaving or stolen agent key can do. It does not make a bad allowlist safe, and it cannot protect an owner key you lose.

The contract enforces

  • An agent key moves only what its policy names: listed tokens, under the per-payment and per-window caps, to allowlisted addresses, between the valid-from and valid-until times.
  • Payments at or above the approval threshold wait for your signature. You see the amount, recipient and memo before anything moves.
  • ERC-20 transfer and approve calls count against the cap at their full amount, so an agent cannot approve its way around a limit.
  • Agents can never call the account itself or the factory.
  • One switch pauses every agent. Your withdrawals keep working while paused.
  • Every spend emits a receipt with the hash of the exact policy it was spent under.

Still on you

  • A leaked agent key can still spend up to its caps, on allowlisted recipients, until you pause or revoke it.
  • Whoever holds the owner key controls the account. Keep it in a hardware wallet.
  • An allowlisted contract that misbehaves can lose what the agent sends it.
  • Approval fatigue: if you approve every request without reading it, the threshold protects nothing.
  • Caps are in token units with no price oracle, so an ETH cap is worth a different dollar amount next week.
  • Version 0.1 is unaudited testnet code.
Read the threat model

Contracts

One factory, one account per owner. Read them on Basescan, or download the ABIs and call them from any client.

Events and functions Basescan

Contract addresses on Base Sepolia
ContractAddress on Base Sepolia (84532)
WardenFactoryCreates accounts at predictable addresses0x1a4aA3C45E9fdF8845dBdfcAb6428f5f7D0272F6
WardenAccountImplementation every account clones0x2d8eFc03262f4cc905d344569a3f541B0233CA92
USDCCircle test token used in the demo0x036CbD53842c5426634e7929541eC2318f3dCF7e