Build a careful agent.
One npm package, tillwarden: an SDK, a CLI and an MCP server. The agent key stays with the agent and signs locally. The engine only ever sees signatures.
Before you start
2
Make an agent key
npx tillwarden keygen, or generate it in the app. The private key goes to the agent only.
3
Issue an API key
In the app, the owner signs one message and gets an API key for that agent. Shown once.
SDK
// npm i tillwarden
import { Tillwarden } from 'tillwarden';
const tw = new Tillwarden({
baseUrl: 'https://tillwarden.fun', // or env TILLWARDEN_URL
apiKey: process.env.TILLWARDEN_API_KEY, // issued in /app
agentKey: process.env.TILLWARDEN_AGENT_KEY, // signs locally, never sent
});
// Ask first. No API key needed; uses the demo account unless you pass safe.
const v = await tw.check({ to, amount: '2.5', token: 'USDG', context: invoiceText });
// v.verdict: 'allow' | 'needs_approval' | 'block', v.checks[], v.simulation
// Pay. Always pass the text that made the agent decide to pay.
const r = await tw.pay({ to, amount: '2.5', token: 'USDG', memo: 'invoice 1042', context: invoiceText });
// r.status: 'paid' | 'pending_approval' | 'blocked', r.txHash, r.verdict
await tw.me(); // account, limits left, balances
await tw.receipts({ limit: 20 });
await tw.verifyReceipt(id); // hash chain + signature
The SDK re-checks what it signs. Before signing, it compares the typed data from the engine with what the agent asked for (payee, amount, token). A tampered intent is refused.
CLI
npx tillwarden check --to 0x… --amount 12 --token USDG --context @invoice.txt
npx tillwarden examples # the five playground scenarios
npx tillwarden example prompt-injection-drain
npx tillwarden pay --to 0x… --amount 12 --token USDG --memo "data export"
npx tillwarden me
npx tillwarden receipts --limit 20
npx tillwarden verify <receiptId>
npx tillwarden keygen
# options: --url <base> (env TILLWARDEN_URL) · --json
# env: TILLWARDEN_API_KEY, TILLWARDEN_AGENT_KEY
MCP server
Any MCP client can give its model five tools. The model never sees the agent key; the server signs locally.
| tillwarden_pay | Pay someone. Checked first; returns paid, pending_approval or blocked with the reason. |
| tillwarden_check | Run the firewall only. Nothing is signed or sent. |
| tillwarden_balance | Balances and what is left today and this week. |
| tillwarden_rules | The rules the owner set for this agent. |
| tillwarden_receipts | Recent signed receipts, in and out. |
{
"mcpServers": {
"tillwarden": {
"command": "npx",
"args": ["-y", "-p", "tillwarden", "tillwarden-mcp"],
"env": {
"TILLWARDEN_URL": "https://tillwarden.fun",
"TILLWARDEN_API_KEY": "…",
"TILLWARDEN_AGENT_KEY": "…"
}
}
}
}
Plain HTTP
No package needed for checks. The firewall answers anyone, rate limited per IP.
curl -s https://tillwarden.fun/api/firewall/check \
-H 'content-type: application/json' \
-d '{"to":"0x…","amount":"2.5","token":"USDG","context":"Invoice 1042, amount due 2.50 USDG"}'
Every endpoint is listed in the docs.