Skip to main content

Policy language

Signing policies are a typed TransactionContext evaluated by four layers. The same payload is available as Cedar resource attributes, OPA input.transaction / input.tx, built-in tx_conditions, and consensus triggers.

See cookbooks for copy-paste rules. Getting started: Policy Engine.

Four layers

LayerWhoWhen
Agent guardrailsPer-agent knobs (tx_max_value_eth, allowlists, …)Every Intents sign/submit
Built-in tx_conditionsaccess_policies.tx_conditions JSON (all tiers)After calldata is known
Cedar / OPAOrg backend builtin+cedar / builtin+opaShadow or enforce; payload-aware second eval
Consensusconsensus_trigger on an access policyMatch → 202 pending approval

Human X-Auth-Confirm on export/import is AND with activity policies (export / import / raw_sign). Agents cannot export keys.

Evaluation order

sign or submit
→ build TransactionContext (ABI / IDL / request construction)
→ agent guardrails
→ built-in path + tx_conditions
→ Cedar or OPA with tx (action sign / submit / raw_sign)
→ consensus (202) or sign

Key-path Cedar/OPA still runs at key lookup (tx: None). A second evaluation runs once the payload is decoded — evaluating the transaction content, not only the key access.

TransactionContext fields

Serialized as context.tx (alias transaction for OPA). Optional fields require Cedar has guards.

FieldTypeNotes
chainstringethereum, solana, …
chain_familystringevm / solana / bitcoin / xrp / cardano / tron
intent_typestringtransaction, typed_data, personal_sign, eip712_digest, treasury_send
sign_onlybooltrue on /sign
to / fromstring?
value_gweilong?Overflow → value_overflow
function_namestring?ABI name or ERC-20/System/SPL hardcoded
function_selectorstring?0x + 4 bytes
function_signaturestring?alias of selector
function_argsmap of stringsNamed decoded args lifted as arg_amount, arg_to, etc.
arg_amount / arg_to / arg_from / arg_spenderstring?Lifted named args
decode_failedboolCalldata present but ABI decode failed — fail-closed
erc20_*string?Normalized transfer/approve
eip712_primary_typestring?typed_data
eip712_domainobject?{ name, version, chain_id, verifying_contract }
eip712_messageobject?Full typed data message JSON
eip7702_authorized_addressesstring[]?EIP-7702 delegation targets
program_idsstring[]Solana
btc_outputs{address, value_sat}[]Bitcoin
xrp_tx_typestring?default Payment
tron_contract_typestring?Transfer vs TriggerSmartContract

Outermost calldata by default. Set deep_inspect: true on tx_conditions or consensus_trigger to also evaluate inner calls from multicall, Safe execTransaction, and ERC-4337 handleOps. Treat decode_failed as deny.

Cedar (resource attributes)

entity Transaction carries the fields above. Qualify actions as OneClaw::Action::"sign" so they match the schema. Documented form:

permit(principal, action, resource);
forbid(principal, action == OneClaw::Action::"sign", resource)
when {
resource has function_name && resource.function_name == "transfer" &&
resource has value_gwei && resource.value_gwei > 1000000000
};

Actions: read, write, rotate, sign, submit, export, import, raw_sign.

Nested context.tx is also present (primitives only). Prefer resource.* for grammar that matches the schema.

Org backend scope defaults to ["sign"]. Add export / raw_sign to enforce activity policies.

OPA

input.tx and input.transaction are the same object (Rust serializes tx; both keys are hoisted).

deny[msg] {
input.transaction.function_name == "transfer"
msg := "transfer denied"
}

Built-in tx_conditions (all tiers)

Fields are combined according to match_mode. effect on the policy is allow or deny.

FieldMatch
match_mode"all" (default, AND) or "any" (OR)
function_name_incase-insensitive
function_selector_incase-insensitive
erc20_amount_aboveraw token units
value_abovegwei
to_address_in
chain_in
intent_type_in
decode_failedbool
program_id_inany listed program in program_ids
deep_inspectbool — also evaluate against inner calls
eip712_primary_type_intyped_data primaryType allowlist
eip712_verifying_contract_intyped_data domain.verifyingContract allowlist (case-insensitive)
eip712_domain_name_intyped_data domain.name allowlist
eip712_domain_chain_id_intyped_data domain.chainId allowlist (integers)
eip7702_authorized_addresses_inEIP-7702 authorized delegate addresses (case-insensitive)

Ignored on secret read (no TransactionContext). Agent guardrails stay the simple knobs; tx_conditions is the free expression layer.

OR logic with match_mode

By default, all present fields must match (AND). Set match_mode: "any" so at least one field match is sufficient (OR). This is useful for deny policies that block a transaction matching any of several criteria:

{
"tx_conditions": {
"match_mode": "any",
"function_name_in": ["approve", "setApprovalForAll"],
"to_address_in": ["0xSuspiciousContract"]
}
}

The above policy fires when the function is approve OR setApprovalForAll OR the to address is the listed contract. With "all" (default), all three would need to match simultaneously.

Deep calldata inspection

When deep_inspect: true, conditions are also evaluated against inner calls extracted from wrapper transactions:

  • Multicall (multicall(bytes[])) — each encoded sub-call
  • Safe execTransaction — the inner call target + data
  • ERC-4337 handleOps — each UserOperation's callData

A match on any inner call counts as an overall match. This catches transfers hidden inside batched operations:

{
"tx_conditions": {
"function_name_in": ["transfer"],
"to_address_in": ["0xBlockedRecipient"],
"deep_inspect": true
}
}

Without deep_inspect, the outer function name is multicall or execTransaction and would not match transfer.

EIP-712 per-field conditions

Use eip712_* fields to write policies over typed data signing (Permit2, order signing, etc.):

{
"tx_conditions": {
"intent_type_in": ["typed_data"],
"eip712_primary_type_in": ["Permit", "Permit2", "PermitSingle"],
"eip712_verifying_contract_in": ["0x000000000022D473030F116dDEE9F6B43aC78BA3"]
}
}

Match is case-insensitive on addresses. Combine with effect: "deny" to block dangerous typed-data signatures, or effect: "allow" to whitelist known verifiers.

EIP-7702 authorization list conditions

EIP-7702 "set code" transactions delegate execution to another contract. Use eip7702_authorized_addresses_in to restrict which contracts an agent may delegate to:

{
"tx_conditions": {
"eip7702_authorized_addresses_in": ["0xKnownSafeImplementation"],
"chain_in": ["ethereum"]
}
}

To deny all EIP-7702 transactions that delegate to unknown contracts, pair with effect: "deny" and set the list to known-bad addresses, or create an allow-only policy with your approved implementation addresses.

Consensus JSON

approvers.count() >= N maps to consensus_trigger.approval.min_approvals. Conditions AND together (value_above, chain_in, to_address_in, function_selector_in, erc20_amount_above, intent_type_in, always). Match → 202.

Consensus composability (skip_when / require_when)

Two optional arrays on consensus_trigger give you fine-grained control over when consensus fires:

  • require_when — consensus is ONLY required when at least one entry matches. If set and none match, the transaction signs immediately (no 202). Use this to limit consensus to high-value or high-risk transactions.
  • skip_when — when ALL conditions in ANY entry match, consensus is bypassed even though conditions matched. Use this to exempt known-safe addresses or low-value transfers.

Each entry is a FlatConditionSet with the same vocabulary as ConsensusCondition: value_above, chain_in, to_address_in, function_selector_in, erc20_amount_above, intent_type_in, always. Fields within one entry are AND-combined; entries within the array are OR-combined.

Evaluation order: require_when is checked first, then skip_when.

{
"consensus_trigger": {
"conditions": [{ "type": "always" }],
"approval": { "min_approvals": 2 },
"expiry_secs": 3600,
"require_when": [
{ "value_above": "1000000000" }
],
"skip_when": [
{ "to_address_in": ["0xTreasuryMultisig"], "chain_in": ["ethereum"] }
]
}
}

In this example, consensus only triggers for transactions above 1 ETH (via require_when), but even those are exempted when sending to the known treasury multisig on Ethereum (via skip_when).

Consensus value precision

ConsensusCondition::ValueAbove supports threshold_wei (string, arbitrary precision) alongside the deprecated threshold_gwei (i64). Prefer threshold_wei for all new policies:

{
"consensus_trigger": {
"conditions": [{ "type": "value_above", "threshold_wei": "1000000000000000000" }],
"approval": { "min_approvals": 2 }
}
}

When both are present, threshold_wei takes precedence. threshold_gwei is retained for backward compatibility only.

Consensus approver roles and credential types

Fine-grained control over who can approve and with what authentication method:

{
"consensus_trigger": {
"conditions": [{ "type": "value_above", "threshold_wei": "5000000000000000000" }],
"approval": {
"min_approvals": 2,
"required_roles": ["owner", "admin"],
"per_role_minimums": { "owner": 1 },
"require_credential_types": ["passkey"]
},
"expiry_secs": 3600
}
}
FieldEffect
required_rolesOnly signatures from users with a listed org role (owner, admin, member) count toward min_approvals
per_role_minimumsEnforce minimum counts per role — e.g. at least 1 owner must approve
require_credential_typesAt least one approval must use a listed credential type (passkey, totp, biometric, password, api_key)

The signature's credential_type is recorded at vote time from the step-up authentication method used (X-Auth-Confirm header).

Control-plane governance

Administrative actions (policy CRUD, key export, member management) can be governed by the same consensus engine. Set an org-level control_plane_consensus_policy_id referencing a policy with consensus_trigger:

{
"consensus_trigger": {
"conditions": [{ "type": "action_in", "actions": ["policy.create", "policy.update", "policy.delete", "signing_key.export", "member.role_change", "member.remove"] }],
"approval": { "min_approvals": 2, "required_roles": ["owner"] },
"expiry_secs": 3600
}
}

When configured, matching control-plane mutations return 202 with a pending_approval_id, just like transaction consensus. The action_payload contains the full request body (hash-bound) so approvers see exactly what is being authorized.

Governed actions: policy.create, policy.update, policy.delete, signing_key.export, signing_key.import, member.role_change, member.remove, agent.create, agent.delete.

Consensus deep inspection

Set deep_inspect: true on the consensus trigger to also evaluate conditions against inner calls from multicall, Safe, and ERC-4337 wrappers. A match on any inner call triggers the consensus requirement:

{
"consensus_trigger": {
"conditions": [{ "type": "value_above", "threshold_wei": "5000000000000000000" }],
"approval": { "min_approvals": 2 },
"deep_inspect": true
}
}

Expression engine (schema v2)

Set policy_schema_version: 2 on access policies to enable the mini DSL in tx_conditions.expression. The evaluator runs at signing time (wired in policy_engine.rs via evaluate_expression_fail_closed) and is AND-combined with v1 field-matching when both are present.

{
"policy_schema_version": 2,
"tx_conditions": {
"chain_in": ["ethereum"],
"expression": "value_gwei > 1000000000 && function_name != 'approve'"
}
}

Security properties (fail-closed):

PropertyLimit
Step budget1000 evaluation steps
AST depth16 levels
Expression length1024 characters
On parse/eval errorDeny for allow-policies; effect-aware for deny-policies

Supported operators: ==, !=, >, >=, <, <=, &&, ||, !, in, contains. Context fields include chain, chain_family, intent_type, to, value_wei, value_gwei, function_name, function_selector, decode_failed, and ERC-20 lift fields. Set policy_schema_version: 2 on the policy to enable the expression engine (see above).

Attribute conditions

attribute_conditions JSON on access policies (Policy Engine v2) gates access by caller metadata — evaluated alongside glob path match, IP/time conditions, and effect/priority.

FieldTypeDescription
required_tagsstring[]Caller must have all listed secret/principal tags
principal_rolestring[]Required org role (owner, admin, member)
auth_methodstring[]Required auth method (api_key, mtls, oidc_client_credentials)
risk_verdict_maxstringMax acceptable risk verdict (low, medium, high, critical) — fail-closed when no risk context
device_knownbooleanWhen true, require a known/registered mobile device
{
"effect": "deny",
"priority": 10,
"attribute_conditions": {
"risk_verdict_max": "medium",
"auth_method": ["api_key"],
"device_known": true
}
}

Also supported in conditions.environment_in (string array) for agent environment scoping on secret reads.

Confidence builders

FeatureStatus
Hash-chained audit logsEvery audit_events row includes prev_event_id and integrity_hash (SHA-256 chain). Enables cryptographic tamper detection across the full event log.
Calldata-bound approvalspending_approvals.payload_hash is validated at vote time — the approved payload cannot be swapped before execution.
Policy simulatorPOST /v1/org/cedar-policies/test and POST /v1/org/opa-policies/test (dry-run). Shadow mode available via org settings.
Deep calldata inspectionMulticall, Safe execTransaction, ERC-4337 handleOps inner calls unwrapped and evaluated. Fail-closed decode_failed flag.
HSM/TEE attestationGCP KMS FIPS 140-2 Level 3 for key operations. Shroud runs on AMD SEV-SNP confidential compute.

Time window

conditions.time_window restricts when a policy is active.

FieldTypeNotes
start_hourint (0–23)Inclusive start
end_hourint (0–24)Exclusive end
days_of_weekint[]0=Sun, 6=Sat
timezonestringIANA timezone (e.g. "America/New_York"). Defaults to UTC.
cron_exprstring6-field cron (with seconds). When set, policy is only active at times matching the cron schedule in the given timezone.

Timezone-aware scheduling

By default, start_hour and end_hour are evaluated in UTC. Set timezone to evaluate in a local timezone instead:

{
"conditions": {
"time_window": {
"start_hour": 9,
"end_hour": 17,
"days_of_week": [1, 2, 3, 4, 5],
"timezone": "America/New_York"
}
}
}

This restricts the policy to US Eastern business hours (9 AM – 5 PM, Monday–Friday).

Cron expressions

For more complex schedules, use cron_expr (6-field: sec min hour day month weekday):

{
"conditions": {
"time_window": {
"cron_expr": "0 0 9-17 * * 1-5",
"timezone": "Europe/London"
}
}
}

This restricts the policy to weekdays 9 AM–5 PM London time. The cron expression is validated on policy creation — invalid expressions are rejected with a 400 error.

Solana interfaces

POST /v1/org/contract-abis with interface_kind: "solana_idl" (address = program id). System/SPL transfer / transferChecked names work without an IDL.