Install
openclaw skills install guardrail-smart-accountsGive AI agents on-chain spending guardrails. Deploy ERC-4337 smart accounts with policy-enforced limits — agents cannot move funds beyond what you authorize,...
openclaw skills install guardrail-smart-accountsGive your AI agents a wallet they can't abuse. AgentGuardrail deploys ERC-4337 smart accounts with policy-enforced spending limits. Agents cannot move funds beyond what you authorize — enforcement happens at the contract level, not just in software.
Homepage: https://agentguardrail.xyz
AI agents need to move money. The problem is trust: how do you let an agent trade, bridge, or pay for compute without risking runaway spending, compromised keys, or unauthorized actions?
AgentGuardrail solves this with:
AgentSmartAccount.validateUserOp() calls PermissionEnforcer before any transaction executes. Violating transactions revert. There is no override.Every agent gets an ERC-4337 smart account deployed via AgentAccountFactory. The account's validateUserOp() enforces your policy through PermissionEnforcer before any transaction reaches the blockchain. If the action violates the policy — wrong token, wrong protocol, spend limit exceeded — the UserOperation reverts.
Execution path:
Agent builds UserOperation
→ AgentSmartAccount.validateUserOp()
→ PermissionEnforcer.validateAction()
→ Policy constraints checked on-chain
→ EntryPoint executes (only if all checks pass)
The AgentGuardrail API at https://agentguardrail.xyz provides:
Most enforcement operations can be done directly against the contracts without the API. The API is most useful for policy management, pre-flight simulation, and audit log queries.
This skill performs on-chain operations that require JSON-RPC access and transaction signing.
Private keys must never be provided in chat and must never be stored in unconstrained agent memory.
The agent prepares a transaction. The runtime forwards it to a secure signer service (HSM, MPC, hosted signer). The signer enforces scope, rate limits, and allowlists. The agent never sees raw private keys.
Transactions are prepared by the agent. A user wallet (browser, hardware wallet) prompts for approval. Keys remain in the wallet.
Session keys must be policy-restricted, short-lived, and rotated frequently. Never use a long-lived owner EOA private key as a session key.
If secure signing is not configured, operate in read-only mode until proper signing is established.
Required (via secure secret storage, not chat):
| Variable | Description |
|---|---|
GUARDRAIL_CHAIN_ID | Target chain ID (e.g., 8453 for Base, 11155111 for Sepolia) |
GUARDRAIL_RPC_URL | JSON-RPC endpoint — treat as sensitive, often contains an API key |
GUARDRAIL_SIGNING_MODE | One of: external_signer, wallet_connector, session_key |
Optional:
| Variable | Description |
|---|---|
GUARDRAIL_API_URL | AgentGuardrail API base. Defaults to https://agentguardrail.xyz |
GUARDRAIL_SIGNER_ENDPOINT | External signer URL — required when GUARDRAIL_SIGNING_MODE=external_signer |
GUARDRAIL_SIGNER_AUTH_TOKEN | Auth token for the external signer — sensitive, store securely |
GUARDRAIL_DASHBOARD_API_KEY | API key for agentguardrail.xyz management API |
The API URL default in all code examples below is https://agentguardrail.xyz. Override with GUARDRAIL_API_URL if self-hosting.
| Contract | Address |
|---|---|
| IdentityRegistry | 0xd0978eA4101d6144457bfbF5317499fbb5Fccf01 |
| PolicyRegistry | 0xc35B3D74521005C7AeA58E1B3483DcBE99B1336B |
| PermissionEnforcer | 0xDc602Cf56679FF23dd17Ea65d3c47E7Ba81Eb470 |
| PriceOracle | 0x32b2088F68427526bE8931C2Dc61eC2520d10F00 |
| GuardrailFeeManager | 0x980d454d79306AFdB8EE5B01F50BeF84760A8380 |
| AgentAccountFactory | 0x94991827135fbd0E681B3db51699e4988a7752f1 |
| EntryPoint (v0.6) | 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 |
| Contract | Address |
|---|---|
| IdentityRegistry | 0xc1fa477f991C74Cc665E605fC74f0e2B795b5104 |
| PolicyRegistry | 0x92cd41e6a4aA13072CeBCda8830d48f269F058c4 |
| PermissionEnforcer | 0x45Aa939A935b6B2Bde32a43aD48cF58AE0D9308d |
| PriceOracle | 0x052cDddba3C55A63F5e48F9e5bC6b70604Db93b8 |
| GuardrailFeeManager | 0x59f50323A5e31ec64470b854c44735EC95929c78 |
| AgentAccountFactory | 0xb284E09d396F5fbeb49587886FB13a186767F14C |
| EntryPoint (v0.6) | 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 |
Start on Sepolia. Move to Base Mainnet when policies are validated.
Deploy a new ERC-4337 smart account via AgentAccountFactory. The account is bound to PermissionEnforcer at creation. Deployment is deterministic via CREATE2 — the same owner, agentId, and salt always produce the same address.
One-time creation fee: $10 USD equivalent in ETH.
Direct contract call (recommended):
// Get required creation fee
uint256 fee = factory.getCreationFee();
// Deploy account (send fee as msg.value)
address account = factory.createAccount{value: fee}(
ownerAddress, // Controls the account
agentId, // bytes32 identifier for this agent
salt // bytes32 for CREATE2 determinism
);
Via API:
const apiUrl = process.env.GUARDRAIL_API_URL ?? "https://agentguardrail.xyz";
const response = await fetch(`${apiUrl}/api/v1/agents/${agentId}/deploy-smart-account`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.GUARDRAIL_DASHBOARD_API_KEY}` },
});
const { smart_account_address } = await response.json();
The API path is useful when using dashboard-generated bot signers (the dashboard generates the keypair and handles deployment in one step).
Send ETH directly to the smart account address. Inbound transfers are free — no fee, no contract call needed.
await walletClient.sendTransaction({
to: smartAccountAddress,
value: parseEther("1.0"),
});
Agent executes a transaction from its smart account. validateUserOp() enforces the policy before the transaction reaches the chain — no override exists.
Outbound transfer fee: 10 bps (0.10%), capped at $100 USD per transaction.
// Build the UserOperation
const callData = encodeFunctionData({
abi: agentSmartAccountABI,
functionName: "execute",
args: [destinationAddress, parseEther("1.0"), "0x"],
});
// Submit via ERC-4337 EntryPoint
// The EntryPoint calls validateUserOp → PermissionEnforcer before execution
Fee enforcement occurs inside GuardrailFeeManager. Fee is deducted from the transaction value automatically.
| Transfer Amount | Fee |
|---|---|
| $1,000 | $1.00 |
| $10,000 | $10.00 |
| $100,000+ | $100.00 (cap) |
// Get account owner
const owner = await publicClient.readContract({
address: smartAccountAddress,
abi: agentSmartAccountABI,
functionName: "owner",
});
// Get smart account creation fee
const fee = await publicClient.readContract({
address: factoryAddress,
abi: agentAccountFactoryABI,
functionName: "getCreationFee",
});
// Calculate transfer fee before executing
const transferFee = await publicClient.readContract({
address: feeManagerAddress,
abi: guardrailFeeManagerABI,
functionName: "calculateTransferFee",
args: [parseEther("10.0")],
});
// Check if a permission is active on-chain
const isActive = await publicClient.readContract({
address: policyRegistryAddress,
abi: policyRegistryABI,
functionName: "isPermissionActive",
args: [agentAddress, permissionId],
});
Policies define what an agent is allowed to do: which actions, tokens, protocols, chains, and spend limits. Policies are stored on-chain in PolicyRegistry and enforced by PermissionEnforcer.
On-chain (direct):
// Register a policy on-chain
bytes32 policyId = policyRegistry.registerPolicy(
bytes32(policyHash), // Unique identifier
allowedActions, // bytes32[] action type hashes
allowedTokens, // address[] token contracts
allowedProtocols, // address[] protocol contracts
allowedChains, // uint256[] chain IDs
maxValuePerTx, // uint256 in wei
maxDailyVolume, // uint256 in wei
expiresAt // uint256 unix timestamp
);
Via API (recommended for most use cases — handles on-chain registration automatically):
const apiUrl = process.env.GUARDRAIL_API_URL ?? "https://agentguardrail.xyz";
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.GUARDRAIL_DASHBOARD_API_KEY}`,
};
// Create and activate a policy
const policy = await fetch(`${apiUrl}/api/v1/policies`, {
method: "POST",
headers,
body: JSON.stringify({
name: "DeFi Trading Policy",
description: "Allow swaps and transfers with daily limits",
definition: {
actions: ["swap", "transfer"],
assets: {
tokens: ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"],
protocols: ["*"],
chains: [1, 8453],
},
constraints: {
maxValuePerTx: "1000000000000000000", // 1 ETH
maxDailyVolume: "10000000000000000000", // 10 ETH
maxTxCount: 100,
requireApproval: false,
},
duration: {
validFrom: "2026-01-01T00:00:00Z",
validUntil: "2026-12-31T23:59:59Z",
},
},
}),
}).then((r) => r.json());
// Activate it (registers on-chain via PolicyRegistry)
await fetch(`${apiUrl}/api/v1/policies/${policy.id}/activate`, {
method: "POST",
headers,
});
PolicyDefinition fields:
| Field | Type | Description |
|---|---|---|
actions | string[] | swap, transfer, approve, stake, unstake, deposit, withdraw, mint, burn, bridge, claim, vote, delegate, lp_add, lp_remove, borrow, repay, liquidate, * |
assets.tokens | string[] | Token contract addresses, or ["*"] for all |
assets.protocols | string[] | Protocol addresses, or ["*"] for all |
assets.chains | number[] | Allowed chain IDs |
constraints.maxValuePerTx | string | Max per-tx value in wei |
constraints.maxDailyVolume | string | Max daily volume in wei |
constraints.maxWeeklyVolume | string | Max weekly volume in wei |
constraints.maxTxCount | number | Max transactions within the duration |
duration.validFrom / validUntil | string | ISO 8601 |
conditions | array | Advanced rules: { field, operator, value }. Operators: eq, ne, gt, gte, lt, lte, in, not_in, contains, regex |
Other policy operations:
// Update (draft policies update in place; active policies create a new version)
await fetch(`${apiUrl}/api/v1/policies/${policyId}`, { method: "PUT", headers, body: JSON.stringify({...}) });
// Revoke (disables on-chain — active permissions using this policy stop validating)
await fetch(`${apiUrl}/api/v1/policies/${policyId}/revoke`, { method: "POST", headers });
// Reactivate
await fetch(`${apiUrl}/api/v1/policies/${policyId}/reactivate`, { method: "POST", headers });
// List / Get / Delete (draft only)
await fetch(`${apiUrl}/api/v1/policies`, { headers });
await fetch(`${apiUrl}/api/v1/policies/${policyId}`, { headers });
await fetch(`${apiUrl}/api/v1/policies/${policyId}`, { method: "DELETE", headers });
Permissions link an agent to a policy for a defined period. On-chain, PolicyRegistry.grantPermission() registers the link. PermissionEnforcer reads active permissions during validateUserOp.
On-chain (direct):
// Grant permission on-chain
bytes32 permissionId = policyRegistry.grantPermission(
agentAddress, // The smart account address
policyId, // bytes32 policy identifier
validFrom, // uint256 unix timestamp
validUntil // uint256 unix timestamp
);
Revoke on-chain:
policyRegistry.revokePermission(permissionId);
Via API (handles on-chain registration + constraint sync to PermissionEnforcer):
const apiUrl = process.env.GUARDRAIL_API_URL ?? "https://agentguardrail.xyz";
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.GUARDRAIL_DASHBOARD_API_KEY}`,
};
// Grant a permission
const permission = await fetch(`${apiUrl}/api/v1/permissions`, {
method: "POST",
headers,
body: JSON.stringify({
agent_id: "agent-uuid",
policy_id: "policy-uuid",
valid_from: "2026-01-01T00:00:00Z",
valid_until: "2026-12-31T23:59:59Z",
}),
}).then((r) => r.json());
// Mint it on-chain (registers in PolicyRegistry, syncs constraints to PermissionEnforcer)
const minted = await fetch(`${apiUrl}/api/v1/permissions/${permission.id}/mint`, {
method: "POST",
headers,
}).then((r) => r.json());
// minted.onchain_token_id — the on-chain token ID
// Revoke
await fetch(`${apiUrl}/api/v1/permissions/${permission.id}`, { method: "DELETE", headers });
// List (filter by agent or policy)
await fetch(`${apiUrl}/api/v1/permissions?agent_id=${agentId}`, { headers });
Use the API path when you need the
PermissionEnforcerconstraints to sync automatically. The direct contract path is sufficient if you are managingPermissionEnforcerconstraint updates yourself.
On-chain enforcement (primary):
Validation happens automatically inside AgentSmartAccount.validateUserOp() via PermissionEnforcer. You do not call this directly — it runs as part of every UserOperation. Transactions that violate policy constraints revert before execution.
Off-chain pre-flight (via API — useful for dashboards, simulation, agent planning):
const apiUrl = process.env.GUARDRAIL_API_URL ?? "https://agentguardrail.xyz";
// Pre-flight check before building a UserOperation
const result = await fetch(`${apiUrl}/api/v1/validate`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.GUARDRAIL_DASHBOARD_API_KEY}`,
},
body: JSON.stringify({
agent_id: "agent-uuid",
action: {
type: "swap",
token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
protocol: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
amount: "500000000000000000",
chain: 8453,
to: "0xDef1C0ded9bec7F1a1670819833240f027b25EfF",
},
}),
}).then((r) => r.json());
// result.allowed — boolean
// result.reason — explanation if denied
// result.permission_id — matching permission
// result.policy_id — matching policy
// result.constraints — active constraints
// result.request_id — audit trail reference
Simulate (dry-run, no audit record):
const sim = await fetch(`${apiUrl}/api/v1/validate/simulate`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.GUARDRAIL_DASHBOARD_API_KEY}` },
body: JSON.stringify({
agent_id: "agent-uuid",
action: { type: "transfer", amount: "1000000000000000000", chain: 1 },
}),
}).then((r) => r.json());
// sim.would_allow — boolean
// sim.current_usage — current period usage
// sim.remaining_quota — remaining allowance
// sim.recommendations — suggested policy adjustments
Batch validate:
// POST /api/v1/validate/batch
// Body: { actions: [...] } — same structure as single validate
On-chain events (source of truth):
The AgentGuardrail indexer polls contracts every 12 seconds and writes events to the audit log with tx_hash and block_number. Events emitted by the contracts:
| Event | Contract | Description |
|---|---|---|
EnforcementResult | PermissionEnforcer | Every validateUserOp result |
ConstraintViolation | PermissionEnforcer | Policy constraint breached |
UsageRecorded | PermissionEnforcer | Spend/volume tracked |
Executed | AgentSmartAccount | Transaction executed |
AccountCreated | AgentAccountFactory | New smart account deployed |
Query via API:
const apiUrl = process.env.GUARDRAIL_API_URL ?? "https://agentguardrail.xyz";
// List audit logs
const logs = await fetch(
`${apiUrl}/api/v1/audit?agent_id=${agentId}&source=onchain&start_date=2026-01-01T00:00:00Z`,
{ headers: { Authorization: `Bearer ${process.env.GUARDRAIL_DASHBOARD_API_KEY}` } }
).then((r) => r.json());
// Each log entry includes: source ('onchain'|'offchain'), tx_hash, block_number, event_type
// Filter by source
// source=onchain — events from contract indexer (tx_hash + block_number included)
// source=offchain — API validation requests
// Export (JSON or CSV)
const csvUrl = `${apiUrl}/api/v1/audit/export?format=csv&start_date=2026-01-01T00:00:00Z`;
Event types: policy.created, policy.activated, policy.revoked, permission.created, permission.minted, permission.revoked, validation.request.
All agents are ERC-4337 smart accounts with enforced on-chain policy enforcement. There is no advisory or EOA mode.
AgentSmartAccount.validateUserOp() calls PermissionEnforcer on every UserOperation. Transactions that violate policy constraints revert before execution. The agent cannot bypass enforcement — it is built into the account's validation logic.
Off-chain validation (/api/v1/validate) runs as a pre-flight simulation for dashboards, SDK use, and agent planning. The on-chain PermissionEnforcer performs the final, authoritative enforcement.
1. Deploy smart account (AgentAccountFactory.createAccount)
2. Create policy (API) → activate (registers on-chain via PolicyRegistry)
3. Grant permission (API) → mint (syncs constraints to PermissionEnforcer)
4. Agent builds UserOperations — enforcement is automatic
5. Monitor via audit logs or on-chain events
GUARDRAIL_RPC_URL may contain an embedded API key. Treat it as sensitive.GUARDRAIL_SIGNER_AUTH_TOKEN grants signing capability. Store in secure secret storage — never expose in logs, prompts, or chat.All operations are available via the web dashboard at https://agentguardrail.xyz — manage agents, policies, permissions, and audit logs visually. API keys and bot signer keypairs generated by the dashboard should be stored in secure secret storage and never pasted into chat.