Integration note
Mandate needs hooks to reliably catch payment intents. If your platform supports plugins with hooks (OpenClaw, Claude Code), use the plugin: it intercepts financial tool calls automatically. This SKILL.md is for agents that integrate via API directly, but without hooks there is no enforcement layer: the agent must voluntarily call /validate before every transaction.
OpenClaw (recommended): openclaw plugins install @mandate.md/mandate-openclaw-plugin
Hooks auto-intercept Locus, Bankr, Sponge, and any swap/transfer/send tool call.
Claude Code (recommended): claude plugin:install claude-mandate-plugin or see claude-mandate-plugin on GitHub
Two-phase enforcement: PostToolUse records validation tokens, PreToolUse blocks unvalidated transactions. Fail-closed, no network calls in the gate.
Other frameworks (GOAT, AgentKit, ElizaOS, MCP): Use this SKILL.md + SDK. No hooks, agent must self-enforce.
MANDATORY SECURITY RULES
Mandate is the security boundary, not optional infrastructure. The card must be swiped before the purchase.
- 1. You MUST call
/validate before EVERY transaction. No exceptions. No shortcuts. (Legacy /validate/raw is also available for existing EVM integrations.) - If validation returns
allowed: false, you MUST NOT execute the transaction. Display the blockReason to the user. - If validation is unreachable, you MUST NOT execute the transaction. Do NOT fall back to calling the wallet directly. See Fail-Safe Behavior.
- You MUST display validation results to the user before any wallet call. See Required User Feedback.
- Even with custodial wallets, you MUST validate with Mandate BEFORE calling the wallet API. Do NOT call transfer/swap directly.
- Skipping validation is a security violation, not a simplification. An unvalidated transaction bypasses spend limits, allowlists, and approval workflows the owner configured to protect their funds.
CODEBLOCK0
Required User Feedback
You MUST display a Mandate status message as the first thing the user sees when a transaction starts, BEFORE any wallet API call.
Happy path:
CODEBLOCK1
Blocked:
CODEBLOCK2
Approval required:
CODEBLOCK3
API unreachable:
CODEBLOCK4
Mandate API, Agent Skills Guide
Validation
Policy check before every transaction. Works with any wallet type (custodial or self-custodial). No intentHash, nonce, or gas params needed.
CLI
CODEBLOCK5
REST
CODEBLOCK6
Validate params
| Field | Required | Description |
|---|
| INLINECODE7 | Yes | What you're doing: "transfer", "swap", "buy", "bridge", "stake", "bet" (free text) |
| INLINECODE8 |
Yes | Why you're doing it (max 1000 chars). Scanned for prompt injection. |
|
amount | No | USD value (assumes stablecoins) |
|
to | No | Recipient address (checked against allowlist) |
|
token | No | Token address |
Response: INLINECODE12
All policy checks apply: circuit breaker, schedule, allowlist, spend limits, daily/monthly quotas, reason scanner. Every call is logged to the audit trail with the action field.
Validate flow
CODEBLOCK7
Raw Validation (deprecated, EVM only)
Deprecated. Use /validate for all new integrations. /validate/raw remains available for existing EVM integrations that require intent hash verification and envelope verification.
Full pre-signing policy check for self-custodial agents who sign transactions locally. Requires all tx params + intentHash.
CLI
mandate validate-raw \
--to 0x036CbD53842c5426634e7929541eC2318f3dCF7e \
--calldata 0xa9059cbb... \
--nonce 42 \
--gas-limit 90000 \
--max-fee-per-gas 1000000000 \
--max-priority-fee-per-gas 1000000000 \
--reason "Invoice #127 from Alice"
The CLI computes
intentHash automatically.
For ERC20 transfers, use the high-level command:
CODEBLOCK9
Raw validate flow
CODEBLOCK10
Quick Start (CLI)
Install the CLI:
CODEBLOCK11
Register
mandate login --name "MyAgent" --address 0xYOUR_ADDRESS
Stores credentials in
~/.mandate/credentials.json (chmod 600). Display the
claimUrl to the user, they are the owner.
Agent Discovery
Run
mandate --llms for a machine-readable command manifest. Each command includes
--help and
--schema for full argument details.
Codebase Scanner
Detect unprotected wallet calls in your project. Zero config, zero auth.
CODEBLOCK13
Exit code 1 if unprotected calls found (CI-friendly).
MCP Server Mode
Run the CLI as an MCP stdio server for tool-based platforms:
CODEBLOCK14
Exposes all Mandate commands as MCP tools. Compatible with any MCP-capable host.
Standard credential storage
Credentials stored in ~/.mandate/credentials.json:
CODEBLOCK15
Optional environment export:
CODEBLOCK16
CRITICAL: AI Agents Must Use register, NOT Dashboard Login
Agents create an identity via mandate login (or /agents/register API). Dashboard login is for humans only.
Tool to Endpoint Map
| CLI Command | Method | Path |
|---|
| INLINECODE26 | POST | INLINECODE27 |
| INLINECODE28 |
POST |
/api/activate |
|
mandate validate | POST |
/api/validate |
|
mandate validate-raw | POST |
/api/validate/raw (deprecated) |
|
mandate event <id> --tx-hash 0x... | POST |
/api/intents/{id}/events |
|
mandate status <id> | GET |
/api/intents/{id}/status |
|
mandate approve <id> | GET |
/api/intents/{id}/status (poll) |
|
mandate scan [dir] | - | Scan codebase for unprotected wallet calls |
|
mandate --llms | - | Machine-readable command manifest |
|
mandate --mcp | - | Start as MCP stdio server |
REST API Fallback
If you cannot install the CLI, use the REST API directly:
- - Base URL: INLINECODE43
- Auth header: INLINECODE44
- Content-Type: INLINECODE45
intentHash computation (required for raw validate only, automatic with CLI)
CODEBLOCK17
CODEBLOCK18
The reason Field
Every validation call requires a reason string (max 1000 chars). This is the core differentiator: no other wallet provider captures WHY an agent decided to make a transaction.
What Mandate does with the reason:
- - Scans for prompt injection (18 hardcoded patterns + optional LLM judge)
- Returns a
declineMessage on block, an adversarial counter-message to override manipulation - Shows it to the owner on approval requests (Slack/Telegram/dashboard)
- Logs it in the audit trail, full context for every transaction, forever
Example: reason catches what session keys miss
CODEBLOCK19
Agent Self-Integration (SDK)
Validate with any wallet (Bankr, Locus, Sponge, self-custodial)
CODEBLOCK20
Raw validate with self-custodial wallet (deprecated)
CODEBLOCK21
Registration (SDK)
CODEBLOCK22
Error handling
CODEBLOCK23
OpenClaw Integration
Install the Mandate plugin:
openclaw plugins install @mandate.md/mandate-openclaw-plugin
Tools
| Tool | When | What |
|---|
| INLINECODE49 | Once, on first run | Registers agent, returns runtimeKey + INLINECODE51 |
| INLINECODE52 |
Before EVERY financial action | Policy check (action, amount, to, token, reason) |
|
mandate_status | After validate | Check intent status |
Flow
- 1.
mandate_register with agent name + wallet address. Save the returned runtimeKey in plugin config. - Before any transfer/swap/send/buy/sell/bridge/stake/bet: call
mandate_validate with action and reason. - If
allowed: true: proceed with your normal wallet (Locus, Bankr, etc.). - If
blocked: true: do NOT proceed, show reason + declineMessage to the user.
Validate endpoint
The plugin uses POST /api/validate.
No intentHash, nonce, or gas params needed. Just: action, reason, and optionally amount, to, token.
All checks apply: circuit breaker, schedule, allowlist, spend limits, daily/monthly quotas, reason scanner.
Every call is logged to the audit trail with the action field the agent provides.
Safety net hook
The plugin also registers a message:preprocessed hook that auto-intercepts financial tool calls
(Locus, Bankr, Sponge, any swap/transfer/send) even if the agent forgets to call mandate_validate.
Config: set runtimeKey in OpenClaw plugin config (not env var).
After validation passes, the agent uses whatever wallet it wants (Locus, Bankr, own keys, etc.).
Claude Code Plugin
Install the Mandate enforcement plugin:
CODEBLOCK24
The plugin automatically BLOCKS transaction tools (Bankr CLI/API, wallet MCPs, financial Bash commands) until you validate with Mandate. Uses a two-phase approach:
- 1. PostToolUse watches for successful
mandate validate calls, records a validation token - PreToolUse intercepts transaction tools, checks for valid token. No token = DENY with instructions.
- SessionStart clears stale tokens
Tokens are valid for 15 minutes. No network calls in the gate, purely local file check, fail-closed.
Default Policy
After registration: $100/tx limit, $1,000/day limit, no address restrictions, no approval required. Adjust via dashboard at https://app.mandate.md.
Fail-Safe Behavior
If the guard is offline, the vault stays locked.
When Mandate API is unreachable:
- 1. Do NOT execute the transaction
- Do NOT fall back to calling the wallet API directly
- Display: Mandate: policy server unreachable, transaction halted for safety
- Retry up to 3 times at 30-second intervals
- If all retries fail, tell the user: "Cannot execute until policy server recovers. No funds were spent."
This is non-negotiable. An unreachable policy server does not mean "no policies apply", it means "policies cannot be verified." Executing without verification bypasses the owner's configured protections.
x402 Payment Flow
- 1. HTTP request to paywall URL -> 402 response
- Parse
X-Payment-Required header: INLINECODE76 - Encode ERC20 transfer calldata: selector
0xa9059cbb + padded(paymentAddress, 32) + padded(amount, 32) - Validate -> sign -> broadcast -> post event
- Retry original request with INLINECODE78
Chain Reference
Test keys (mndt_test_*): Sepolia (11155111), Base Sepolia (84532) | Live keys (mndt_live_*): Ethereum (1), Base (8453)
| Chain | Chain ID | USDC Address | Decimals |
|---|
| Ethereum | 1 | INLINECODE81 | 6 |
| Sepolia |
11155111 |
0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 | 6 |
| Base | 8453 |
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | 6 |
| Base Sepolia | 84532 |
0x036CbD53842c5426634e7929541eC2318f3dCF7e | 6 |
Intent States
| State | Description | Expiry |
|---|
| INLINECODE85 | Validated via INLINECODE86 | 24 hours |
| INLINECODE87 |
Raw validated, waiting for broadcast | 15 min |
|
approval_pending | Requires owner approval via dashboard | 1 hour |
|
approved | Owner approved, broadcast window open | 10 min |
|
broadcasted | Tx sent, waiting for on-chain receipt | - |
|
confirmed | On-chain confirmed, quota committed | - |
|
failed | Reverted, dropped, policy violation, or envelope mismatch | - |
|
expired | Not broadcast in time, quota released | - |
Error Responses
All errors return JSON: { "error": "message" } or { "allowed": false, "blockReason": "reason" }
| Status | Meaning | Common Cause |
|---|
| 400 | Bad Request | Missing/invalid fields |
| 401 |
Unauthorized | Missing or invalid runtime key |
| 403 | Forbidden | Circuit breaker active |
| 404 | Not Found | Intent not found |
| 409 | Conflict | Duplicate intentHash or wrong status |
| 410 | Gone | Approval expired |
| 422 | Policy Blocked | Validation failed (see blockReason) |
| 429 | Rate Limited | Too many requests (back off + retry) |
| 500 | Server Error | Transient; retry later |
blockReason values
| Value | Meaning |
|---|
| INLINECODE96 | Agent is circuit-broken (dashboard to reset) |
| INLINECODE97 |
No policy set (visit dashboard) |
|
intent_hash_mismatch | Client hash doesn't match server recompute (raw validate only) |
|
gas_limit_exceeded | Gas too high per policy |
|
value_wei_exceeded | Native ETH value too high |
|
outside_schedule | Outside allowed hours/days |
|
address_not_allowed | Recipient not in allowlist |
|
selector_blocked | Function selector is blocked |
|
per_tx_limit_exceeded | Amount exceeds per-tx USD limit |
|
daily_quota_exceeded | Daily USD limit reached |
|
monthly_quota_exceeded | Monthly USD limit reached |
|
reason_blocked | Prompt injection detected in agent's
reason field |
|
aegis_critical_risk | Transaction flagged as CRITICAL risk by security scanner |
Calldata Encoding Reference (raw validate only)
ERC20 transfer(address to, uint256 amount):
CODEBLOCK25
ERC20 approve(address spender, uint256 amount): selector 0x095ea7b3, not spend-bearing, does not count against quota.
Security
- - Never share your runtime key in logs, posts, or screenshots.
- Store keys in
~/.mandate/credentials.json and restrict permissions (chmod 600). - Rotate the key (re-register) if exposure is suspected.
- Circuit breaker auto-trips if on-chain tx doesn't match validated intent.
集成说明
Mandate 需要钩子来可靠地捕获支付意图。如果你的平台支持带钩子的插件(OpenClaw、Claude Code),请使用插件:它会自动拦截金融工具调用。本 SKILL.md 适用于通过 API 直接集成的代理,但如果没有钩子,就没有执行层:代理必须在每笔交易前自愿调用 /validate。
OpenClaw(推荐): openclaw plugins install @mandate.md/mandate-openclaw-plugin
钩子会自动拦截 Locus、Bankr、Sponge 以及任何交换/转账/发送工具调用。
Claude Code(推荐): claude plugin:install claude-mandate-plugin 或查看 GitHub 上的 claude-mandate-plugin
两阶段执行:PostToolUse 记录验证令牌,PreToolUse 阻止未经验证的交易。故障关闭,网关内无网络调用。
其他框架(GOAT、AgentKit、ElizaOS、MCP): 使用本 SKILL.md + SDK。无钩子,代理必须自行执行。
强制性安全规则
Mandate 是安全边界,而非可选基础设施。必须先刷卡,再购买。
- 1. 你必须在每笔交易前调用 /validate。无例外。无捷径。(旧版 /validate/raw 也可用于现有的 EVM 集成。)
- 如果验证返回 allowed: false,你不得执行交易。向用户显示 blockReason。
- 如果验证无法访问,你不得执行交易。不要回退到直接调用钱包。请参阅故障安全行为。
- 你必须在任何钱包调用前向用户显示验证结果。请参阅必需的用户反馈。
- 即使是托管钱包,你也必须在调用钱包 API 之前使用 Mandate 进行验证。不要直接调用转账/交换。
- 跳过验证是安全违规,而非简化。未经验证的交易会绕过所有者配置用于保护其资金的支出限额、白名单和审批工作流。
MANDATE API 快速参考 v1.2.0
基础地址: https://app.mandate.md/api
认证: Authorization: Bearer RUNTIMEKEY>
非托管:你的私钥永远不会离开你的机器。
主要端点:
POST /validate -> action + reason。适用于任何钱包类型。
旧版端点:
POST /validate/raw -> 完整交易参数 + intentHash。仅限 EVM。(已弃用)
其他端点:
POST /agents/register -> 注册代理(无需认证),获取 runtimeKey + claimUrl
POST /activate -> 设置 evmAddress(注册后调用一次)
POST /intents/{id}/events -> 广播后发布 txHash(信封验证)
GET /intents/{id}/status -> 轮询意图状态
密钥前缀:mndtlive...(主网)/ mndttest...(测试网)
凭据:~/.mandate/credentials.json
错误:HTTP 状态码 + JSON { error: message } 或 { allowed: false, blockReason: ... }
必需的用户反馈
当交易开始时,在任何钱包 API 调用之前,你必须显示 Mandate 状态消息作为用户首先看到的内容。
正常路径:
Mandate:正在检查策略...
Mandate:策略检查通过,向 0xAlice 发送 50 USDC
原因:来自 Alice 的发票 #127
交易已确认,0x9f2e...abc1
被阻止:
Mandate:正在检查策略...
Mandate:被阻止,150 美元超过 100 美元/笔交易限额
需要审批:
Mandate:正在检查策略...
Mandate:需要审批,等待所有者决定
打开仪表盘进行审批:https://app.mandate.md
Mandate:已审批,正在广播...
交易已确认,0x9f2e...abc1
API 无法访问:
Mandate:策略服务器无法访问,为安全起见交易已暂停
Mandate API,代理技能指南
验证
每笔交易前的策略检查。适用于任何钱包类型(托管或自托管)。无需 intentHash、nonce 或 gas 参数。
CLI
bash
mandate validate \
--action swap \
--reason 在 Uniswap 上将 0.1 ETH 兑换为 USDC \
--amount 50 --to 0xAlice
REST
bash
curl -X POST https://app.mandate.md/api/validate \
-H Authorization: Bearer $MANDATE
RUNTIMEKEY \
-H Content-Type: application/json \
-d {action:swap,reason:在 Uniswap 上将 0.1 ETH 兑换为 USDC,amount:50,to:0xAlice}
验证参数
| 字段 | 必需 | 描述 |
|---|
| action | 是 | 你在做什么:transfer、swap、buy、bridge、stake、bet(自由文本) |
| reason |
是 | 你为什么这样做(最多 1000 个字符)。会扫描提示注入。 |
| amount | 否 | 美元价值(假设为稳定币) |
| to | 否 | 接收方地址(对照白名单检查) |
| token | 否 | 代币地址 |
响应: { allowed: true, intentId: ..., action: swap, requiresApproval: false }
所有策略检查均适用:断路器、时间表、白名单、支出限额、每日/每月配额、原因扫描器。每次调用都会记录到审计跟踪中,包含 action 字段。
验证流程
- 1. mandate validate --action swap --reason 将 ETH 兑换为 USDC (策略检查)
- bankr prompt 将 0.1 ETH 兑换为 USDC (通过钱包执行)
- 完成。
原始验证(已弃用,仅限 EVM)
已弃用。 所有新集成请使用 /validate。/validate/raw 仍可用于需要意图哈希验证和信封验证的现有 EVM 集成。
针对本地签署交易的自托管代理的完整预签名策略检查。需要所有交易参数 + intentHash。
CLI
bash
mandate validate-raw \
--to 0x036CbD53842c5426634e7929541eC2318f3dCF7e \
--calldata 0xa9059cbb... \
--nonce 42 \
--gas-limit 90000 \
--max-fee-per-gas 1000000000 \
--max-priority-fee-per-gas 1000000000 \
--reason 来自 Alice 的发票 #127
CLI 会自动计算 intentHash。
对于 ERC20 转账,使用高级命令:
bash
mandate transfer \
--to 0xAlice --amount 10000000 \
--token 0x036CbD53842c5426634e7929541eC2318f3dCF7e \
--reason 发票 #127 \
--nonce 42 --max-fee-per-gas 1000000000 --max-priority-fee-per-gas 1000000000
原始验证流程
- 1. mandate validate-raw --to ... --calldata ... --reason ... (策略检查)
- 本地签署(你的密钥,Mandate 永远不会看到它们)
- 广播交易
- mandate event --tx-hash 0x... (信封验证)
- mandate status (确认)
快速入门(CLI)
安装 CLI:
bash
bun add -g @mandate.md/cli
或无需安装即可发现命令:
npx @mandate.md/cli --llms
注册
bash
mandate login --name MyAgent --address 0xYOUR_ADDRESS
将凭据存储在 ~/.mandate/credentials.json(chmod 600)中。向用户显示 claimUrl,他们是所有者。
代理发现
运行 mandate --llms 获取机器可读的命令清单。每个命令都包含 --help 和 --schema 以获取完整的参数详情。
代码库扫描器
检测项目中未受保护的钱包调用。零配置,零认证。
bash
npx @mandate.md/cli scan # 扫描当前目录
npx @mandate.md/cli scan ./src # 扫描特定文件夹
如果发现未受保护的调用,退出代码为 1(CI 友好)。
MCP