Choose your integration
Pick the path that matches how you build:
Claude Code One command. Auto-intercepts all wallet transactions.
OpenClaw Install from ClawHub. Three tools, safety-net hook.
TypeScript SDK Full control. MandateClient + MandateWallet.
CLI Validate from scripts, CI, or MCP server mode.
Python / Any Language REST API. Works with Python, Go, Rust, or any HTTP client.
Claude Code Plugin
The fastest path. Two commands, zero code changes. The plugin intercepts all wallet transactions Claude executes and validates them against your policy.
Step 1: Add the marketplace and install the plugin:
/plugin marketplace add SwiftAdviser/claude-mandate-plugin
/plugin install mandate@mandate
Step 2: Register your agent:
npx @mandate.md/cli login --name "my-claude-agent"
Claim at the printed URL, then set policies at app.mandate.md .
The plugin gates every financial tool call. No valid Mandate token, no transaction.
Agent calls mandate validate with action + reason
If allowed: plugin records a 15-minute token, transaction proceeds
If blocked: agent gets counter-evidence explaining why, stops voluntarily
If no validation attempted: plugin blocks the tool call entirely
Auto-scan on startup finds unprotected wallet calls in your project
Works with any wallet: Bankr, MCP payment tools, direct RPC, custom CLIs
GitHub: SwiftAdviser/claude-mandate-plugin
Full Claude Code guide Two-phase enforcement, gate triggers, token TTLs, and advanced configuration.
OpenClaw Plugin
Install from ClawHub or npm. Your agent gets three Mandate tools automatically.
openclaw plugin install @mandate.md/mandate-openclaw-plugin
The plugin adds these tools to your agent:
Tool What it does mandate_registerRegister agent, get runtime key mandate_validateCheck transaction against policy mandate_statusPoll intent state
A safety-net hook blocks any financial tool call that skips mandate_validate.
GitHub: SwiftAdviser/mandate-openclaw-plugin
ClawHub: clawhub.ai/swiftadviser/mandate
Full OpenClaw guide Tool parameters, safety-net hook details, and configuration options.
TypeScript SDK
For custom agents. Full control over the validate, sign, broadcast flow.
Install
npm install @mandate.md/sdk
Register your agent
import { MandateClient } from '@mandate.md/sdk' ;
const { runtimeKey , claimUrl } = await MandateClient . register ({
name: 'my-trading-agent' ,
evmAddress: '0xYourAgentWalletAddress' ,
chainId: 84532 , // Base Sepolia
});
// Save runtimeKey securely. Share claimUrl with the wallet owner.
Store the runtimeKey in your .env file (MANDATE_RUNTIME_KEY=mndt_test_...). Never commit it to git. Share the claimUrl with the wallet owner to link the agent to the dashboard.
Test validation (no wallet needed)
You can test the policy engine without a private key or wallet. This is the fastest way to verify your setup works.
// validate-test.ts — Run: bun run validate-test.ts
import { MandateClient , PolicyBlockedError , ApprovalRequiredError } from '@mandate.md/sdk' ;
const client = new MandateClient ({
runtimeKey: process . env . MANDATE_RUNTIME_KEY ! ,
});
try {
const result = await client . validate ({
action: 'transfer' ,
amount: '50' ,
to: '0x036CbD53842c5426634e7929541eC2318f3dCF7e' ,
token: 'USDC' ,
reason: 'Payment for API access - invoice #1234' ,
});
console . log ( 'Allowed:' , result . allowed );
console . log ( 'Intent ID:' , result . intentId );
} catch ( err ) {
if ( err instanceof PolicyBlockedError ) {
console . log ( 'Blocked:' , err . blockReason , err . declineMessage );
} else if ( err instanceof ApprovalRequiredError ) {
console . log ( 'Needs approval:' , err . intentId );
const status = await client . waitForApproval ( err . intentId );
console . log ( 'Decision:' , status . status );
}
}
Expected output:
Allowed: true
Intent ID: a1b2c3d4-...
Execute the transfer
MandateWallet handles the full flow: validate, sign locally, broadcast, confirm. Your private key never leaves your machine.
validate() accepts amounts in USD (e.g., '50' means $50). wallet.transfer() accepts raw token units (e.g., '5000000' means 5 USDC, because USDC has 6 decimals). The policy engine always evaluates in USD.
import { MandateWallet } from '@mandate.md/sdk' ;
const wallet = new MandateWallet ({
runtimeKey: process . env . MANDATE_RUNTIME_KEY ! ,
privateKey: process . env . AGENT_PRIVATE_KEY ! as `0x ${ string } ` ,
chainId: 84532 ,
});
const { txHash , intentId } = await wallet . transfer (
'0xRecipientAddress' ,
'5000000' , // 5 USDC (6 decimals)
'0x036CbD53842c5426634e7929541eC2318f3dCF7e' ,
{ reason: 'Paying vendor for March services' },
);
console . log ( 'Transaction:' , txHash );
console . log ( 'Intent ID:' , intentId );
Expected output:
Transaction: 0x7a8b9c...
Intent ID: e5f6g7h8-...
Complete runnable example (register + validate + transfer)
// quickstart.ts — Run: bun run quickstart.ts
// Prerequisites: bun add @mandate.md/sdk viem
// Set MANDATE_RUNTIME_KEY and AGENT_PRIVATE_KEY in .env
import { MandateWallet , MandateClient , USDC , CHAIN_ID } from '@mandate.md/sdk' ;
const RECIPIENT = '0x0000000000000000000000000000000000000001' as `0x ${ string } ` ;
async function main () {
// Step 1: Register (skip if you already have a runtimeKey)
let runtimeKey = process . env . MANDATE_RUNTIME_KEY ;
if ( ! runtimeKey ) {
console . log ( 'No runtimeKey found. Registering new agent...' );
const reg = await MandateClient . register ({
name: 'QuickstartAgent' ,
evmAddress: '0xYourWalletAddress' as `0x ${ string } ` ,
chainId: CHAIN_ID . BASE_SEPOLIA ,
});
runtimeKey = reg . runtimeKey ;
console . log ( `Registered! runtimeKey: ${ runtimeKey } ` );
console . log ( `Claim URL: ${ reg . claimUrl } ` );
console . log ( 'Save runtimeKey to .env as MANDATE_RUNTIME_KEY' );
}
// Step 2: Create MandateWallet
const wallet = new MandateWallet ({
runtimeKey ,
privateKey: process . env . AGENT_PRIVATE_KEY as `0x ${ string } ` ,
chainId: CHAIN_ID . BASE_SEPOLIA ,
});
// Step 3: Transfer with policy enforcement
try {
const { txHash , intentId , status } = await wallet . transfer (
RECIPIENT ,
'1000000' , // 1 USDC (6 decimals)
USDC . BASE_SEPOLIA ,
);
console . log ( 'Transfer successful!' );
console . log ( ' txHash:' , txHash );
console . log ( ' intentId:' , intentId );
console . log ( ' status:' , status . status );
} catch ( err : unknown ) {
if ( err && typeof err === 'object' && 'blockReason' in err ) {
console . error ( 'Blocked:' , ( err as { blockReason : string }). blockReason );
} else {
throw err ;
}
}
}
main (). catch ( console . error );
npm: @mandate.md/sdk
CLI
Run directly with npx. No install needed.
Register
npx @mandate.md/cli login --name "my-agent"
Validate
npx @mandate.md/cli validate \
--action transfer \
--amount 50 \
--to 0x036CbD53842c5426634e7929541eC2318f3dCF7e \
--token USDC \
--reason "Payment for API access"
Scan your codebase
Find unprotected wallet calls before they reach production:
npx @mandate.md/cli scan ./src
The scanner detects sendTransaction, transfer, approve, and other financial calls missing Mandate validation. Exit code 1 if any found. Use it in CI.
MCP server mode
Turn the CLI into an MCP server for AI assistants:
npx @mandate.md/cli --mcp
npm: @mandate.md/cli
Full CLI reference All 10 commands, flags, credential storage, and MCP configuration.
Python / Any Language
No SDK needed. Call the REST API directly with any HTTP client.
Register
import requests
resp = requests.post( "https://app.mandate.md/api/agents/register" , json = {
"name" : "my-python-agent" ,
"evmAddress" : "0xYourAgentWalletAddress" ,
"chainId" : 84532 ,
})
data = resp.json()
runtime_key = data[ "runtimeKey" ]
print ( f "Runtime key: { runtime_key } " )
print ( f "Claim URL: { data[ 'claimUrl' ] } " )
# Save runtime_key to .env
Validate
import os, requests
headers = {
"Authorization" : f "Bearer { os.environ[ 'MANDATE_RUNTIME_KEY' ] } " ,
"Content-Type" : "application/json" ,
}
resp = requests.post( "https://app.mandate.md/api/validate" , headers = headers, json = {
"action" : "transfer" ,
"amount" : "50" ,
"to" : "0x036CbD53842c5426634e7929541eC2318f3dCF7e" ,
"token" : "USDC" ,
"reason" : "Payment for API access - invoice #1234" ,
})
data = resp.json()
if resp.status_code == 200 and data.get( "allowed" ):
print ( f "Allowed. Intent: { data[ 'intentId' ] } " )
elif resp.status_code == 422 :
print ( f "Blocked: { data[ 'blockReason' ] } - { data.get( 'blockDetail' ) } " )
elif resp.status_code == 202 :
print ( f "Needs approval. Intent: { data[ 'intentId' ] } " )
Expected output:
Allowed. Intent: a1b2c3d4-...
This pattern works with Python, Go, Rust, Ruby, or any language with an HTTP client. See the REST API reference for all endpoints.
Default policy
After registration, every agent starts with:
Rule Default Per-transaction limit $100 Daily limit $1,000 Address restrictions None Approval required No Schedule 24/7
Customize in the Dashboard policy builder.
What happens under the hood
Validate. Your agent sends action + reason to Mandate. The policy engine runs 14 checks.
Sign locally. After validation passes, the agent signs with its own key. Mandate never sees it.
Broadcast. Signed transaction goes to the chain.
Verify. Mandate confirms the on-chain tx matches what was validated. Mismatch trips the circuit breaker.
Resources
Resource Link Dashboard app.mandate.md Documentation docs.mandate.md SDK (npm) @mandate.md/sdk CLI (npm) @mandate.md/cli Claude Code Plugin GitHub OpenClaw Plugin ClawHub / GitHub Skill (SKILL.md) GitHub Developer Community Telegram
Next Steps
How It Works Validation flow, intent lifecycle, and state machine.
Handle Errors PolicyBlockedError, CircuitBreakerError, and 12 block reason codes.
Handle Approvals Wait for human approval with polling, timeouts, and callbacks.
All Integrations GOAT SDK, AgentKit, ElizaOS, GAME, ACP, MCP Server, and more.