Signing policies
A signing policy controls transaction requests from local agents, installed automations, and connected WalletConnect dapps. It does not grant any requester access to private keys or owner-only settings.
New accounts begin with an empty policy, so transactions ordinarily wait for owner review. Policy rules are evaluated from top to bottom, and the first matching rule decides each call:
- Allow lets a matching call proceed without asking the owner again.
- Review requires explicit owner approval for a matching call.
- Deny rejects a matching call without queuing it, and cannot be overridden from the review screen.
- No match also requires native review.
Every call is evaluated, and the transaction takes the least permissive result among them, ordering deny below review below allow. So a batch proceeds automatically only when every call in it is allowed, and a single denied call rejects the whole batch.
Build a narrow rule
Section titled “Build a narrow rule”Open Policies in Ekubo Wallet to use the guided editor. A rule can constrain the network, destination, native value, and calldata of each call. Conditions in one rule are combined; leaving a field unrestricted broadens what the rule matches.
A rule can also constrain the exact prepared transaction envelope that carries those calls: its transaction_type, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas, the delegation target of a signed EIP-7702 authorization, and the envelope’s own envelope_to and envelope_native_value. Per-call native value and envelope native value are distinct, and neither is a cumulative spending budget. The fee matchers are the ones worth setting on any rule that signs without a review, because they bound what a misreporting endpoint can cause that signature to pay.
For contract calls, prefer a full function signature whose top-level parameters are all named, such as approve(address spender, uint256 amount), and constrain arguments by those names. Put narrow exceptions before broader rules because order is part of the policy’s authority. The wallet rejects rules that are provably unreachable behind an earlier rule.
Tuple arguments use a positional tuple predicate. Its array must have exactly the tuple’s ABI arity; a predicate constrains that position and null leaves only that position unrestricted. Tuple predicates can nest, and an array of tuples uses {"each":{"tuple":[...]}}. For example, {"tuple":[{"eq":"1"},null,{"eq":"true"}]} constrains the first and third values of a three-element tuple.
JSON Schema and editor
Section titled “JSON Schema and editor”The editor below uses the same generated JSON Schema offered for download. It provides JSON syntax highlighting, schema-driven property and value completion, inline structural validation, and automatic formatting when focus leaves valid JSON. Choose an example to replace the editor contents with formatted JSON, then change it to explore the policy language. Examples containing addresses use obvious placeholders and are not ready to propose unchanged. Choose Review every transaction (Default) to restore the default behavior of asking the owner about every transaction.
Policy JSON editor
Press Ctrl+Space to see properties and values from the schema. Errors are underlined and listed in the diagnostics panel.
An empty policy leaves every transaction for owner review.
Loading the policy schema…
Download the Ekubo Wallet policy JSON Schema
View the complete JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Ekubo Wallet policy",
"type": "object",
"properties": {
"$schema": { "type": ["string", "null"] },
"version": { "type": "integer", "minimum": 1, "maximum": 1 },
"rules": { "type": "array", "maxItems": 256, "items": { "$ref": "#/$defs/Rule" } }
},
"additionalProperties": false,
"required": ["version", "rules"],
"$defs": {
"Rule": {
"type": "object",
"properties": {
"effect": { "$ref": "#/$defs/Effect" },
"label": { "type": ["string", "null"] },
"chain_id": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] },
"to": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] },
"native_value": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] },
"calldata": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] },
"transaction_type": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] },
"nonce": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] },
"gas_limit": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] },
"max_fee_per_gas": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] },
"max_priority_fee_per_gas": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] },
"delegation": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] },
"envelope_to": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] },
"envelope_native_value": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] }
},
"additionalProperties": false,
"required": ["effect"]
},
"Effect": {
"oneOf": [
{ "type": "string", "const": "allow" },
{ "type": "string", "const": "deny" },
{ "type": "string", "const": "review" }
]
},
"Predicate": {
"oneOf": [
{ "type": "string", "const": "any_value" },
{ "$ref": "#/$defs/Eq" },
{ "$ref": "#/$defs/In" },
{ "$ref": "#/$defs/Lt" },
{ "$ref": "#/$defs/Lte" },
{ "$ref": "#/$defs/Gt" },
{ "$ref": "#/$defs/Gte" },
{ "$ref": "#/$defs/Selector" },
{ "$ref": "#/$defs/Each" },
{ "$ref": "#/$defs/Tuple" },
{ "$ref": "#/$defs/Any" },
{ "$ref": "#/$defs/All" },
{ "$ref": "#/$defs/Not" },
{ "$ref": "#/$defs/Length" }
]
},
"Eq": { "type": "object", "properties": { "eq": { "type": "string" } }, "required": ["eq"], "additionalProperties": false },
"In": { "type": "object", "properties": { "in": { "type": "array", "uniqueItems": true, "items": { "type": "string" } } }, "required": ["in"], "additionalProperties": false },
"Lt": { "type": "object", "properties": { "lt": { "type": "string" } }, "required": ["lt"], "additionalProperties": false },
"Lte": { "type": "object", "properties": { "lte": { "type": "string" } }, "required": ["lte"], "additionalProperties": false },
"Gt": { "type": "object", "properties": { "gt": { "type": "string" } }, "required": ["gt"], "additionalProperties": false },
"Gte": { "type": "object", "properties": { "gte": { "type": "string" } }, "required": ["gte"], "additionalProperties": false },
"Selector": { "type": "object", "properties": { "selector": { "$ref": "#/$defs/SelectorPredicate" } }, "required": ["selector"], "additionalProperties": false },
"SelectorPredicate": {
"type": "object",
"properties": {
"abi": { "type": "string" },
"args": { "type": "object", "additionalProperties": { "$ref": "#/$defs/Predicate" } }
},
"additionalProperties": false,
"required": ["abi"]
},
"Each": { "type": "object", "properties": { "each": { "$ref": "#/$defs/Predicate" } }, "required": ["each"], "additionalProperties": false },
"Tuple": { "type": "object", "properties": { "tuple": { "type": "array", "items": { "anyOf": [{ "$ref": "#/$defs/Predicate" }, { "type": "null" }] } } }, "required": ["tuple"], "additionalProperties": false },
"Any": { "type": "object", "properties": { "any": { "type": "array", "items": { "$ref": "#/$defs/Predicate" } } }, "required": ["any"], "additionalProperties": false },
"All": { "type": "object", "properties": { "all": { "type": "array", "items": { "$ref": "#/$defs/Predicate" } } }, "required": ["all"], "additionalProperties": false },
"Not": { "type": "object", "properties": { "not": { "$ref": "#/$defs/Predicate" } }, "required": ["not"], "additionalProperties": false },
"Length": { "type": "object", "properties": { "length": { "$ref": "#/$defs/Predicate" } }, "required": ["length"], "additionalProperties": false }
},
"description": "Ordered stateless signing policy over each call and the exact prepared transaction envelope. The first matching rule decides each call: allow signs automatically, review requires explicit owner approval, deny rejects without queuing, and reaching the end also requires review. Every call is evaluated before the transaction takes the least-permissive result (deny < review < allow). Omitted matchers mean anything and present matchers are ANDed. Per-call native value and outer envelope native value are distinct; neither is a cumulative spending budget."
}
Schema validation checks document shape, required properties, allowed values, and predicate structure. The wallet remains the final authority: it also checks values in context, validates ABI signatures and predicate applicability, and rejects unreachable rules. A document that passes this browser editor is not installed and grants no permission.
The optional $schema property is only an editor hint. Ekubo Wallet does not fetch or trust content from that URL when it evaluates a policy.
Agent proposals
Section titled “Agent proposals”An authorized agent can read the active policy and propose a complete replacement based on its current revision. A proposal does not change permissions, and an agent cannot install it. The owner must inspect the permission diff in the native wallet. A transition that widens or ambiguously changes authority requires operating-system authentication; a transition that the wallet proves only tightens the active policy can be installed from the owner UI without an additional operating-system challenge.
Do not broaden policy merely to finish the request currently waiting. A one-time action can use the ordinary native review path without changing future permissions.
Display labels, token symbols, RPC simulation output, and other network-provided facts are review context, not policy inputs. Policy matching uses exact transaction fields.
Installing a policy stops automations
Section titled “Installing a policy stops automations”Each automation is bound to the policy revision that was active when it was installed. Installing any policy therefore stops every automation on that account until you review each one and choose to run it again. That is deliberate: without it, widening a policy for some unrelated reason would silently re-arm a scheduled job under a rule written for something else.