Capabilities
What Are Capabilities?
Capabilities are fine-grained, expirable, metered permissions attached to a wallet. They are the primary gate on what an agent wallet (or any wallet) is allowed to do on the protocol.
A wallet without any capabilities can perform any action its auth groups permit. A wallet with explicit capabilities is restricted to only those actions; capabilities act as a whitelist that overrides the broader auth group permissions.
This makes capabilities the correct tool for scoping autonomous agents: assign the minimum set of actions the agent needs, bound by resource address, invocation count, and block-height expiry, and the protocol enforces that scope at every transaction.
Capability Structure
Capability {
Action string // The action type (e.g., "contract:invoke", "token:transfer")
Resource string // The target resource address, or "*" for any
Limits CapabilityLimits // Optional: maxAmount, maxInvocations
NotAfter uint64 // Block height after which the capability expires (0 = no expiry)
}
CapabilityLimits {
MaxAmount *big.Int // Maximum token amount per invocation (for token actions)
MaxInvocations uint64 // Maximum number of times this capability can be used
}
Action Types
| Action | Scope |
|---|---|
contract:invoke | Call a smart contract function |
token:transfer | Transfer fungible tokens |
token:mint | Mint new token supply |
token:burn | Burn token supply |
nft:transfer | Transfer an NFT |
data:submit | Submit arbitrary data transactions |
wallet:create | Create child wallets |
* | Wildcard: all actions (use with caution) |
The Resource field narrows the scope to a specific contract address, token address, or wallet address. Use "*" only when the action must apply globally; this is an anti-pattern for agent wallets (see Anti-Patterns).
How Capabilities Are Enforced
Capability checks run at transaction execution time (block processing), not at submission time:
Transaction submitted to pool
↓
Authorization pre-screen (ancestry, blacklists)
↓ [capabilities NOT checked here]
Transaction enters pending pool
↓
Block proposed, transactions ordered
↓
Block execution: for each transaction:
1. Is the sender wallet capability-restricted?
2. Does the transaction action match any held capability?
3. Is the capability's resource "*" or matches the target?
4. Is current block height ≤ capability.NotAfter?
5. If MaxInvocations set: has invocation count been exceeded?
6. If MaxAmount set: does the transaction amount exceed the limit?
→ All checks pass → execute
→ Any check fails → reject transaction
Important: Because capability expiry is checked at execution time, a transaction submitted before the expiry block may still fail if it is not included in a block until after the expiry. Design capability NotAfter values with sufficient buffer.
Attaching Capabilities to a Wallet
Capabilities are attached via ALTER_WALLET:
{
"walletAddress": "<agent-wallet-address>",
"capabilities": [
{
"action": "contract:invoke",
"resource": "5c83c81199820b91cf759a69f5ede919ea0568be34ec6e26ac8adf4173cbf01048451014b48cc4557474fde91f589c95bf4dd34349d96a83d7f0e15a7b664b83",
"limits": {
"maxInvocations": 1000
},
"notAfter": 200000
}
]
}
Inheritance Constraint
When creating a child wallet, the child's capabilities must be a strict subset of the parent's capabilities. A parent wallet cannot grant a child permission the parent itself does not hold. This prevents privilege escalation through the hierarchy.
Capability Patterns for Agent Wallets
Minimal Agent (Single Contract, Bounded)
[
{
"action": "contract:invoke",
"resource": "<specific-contract-address>",
"limits": { "maxInvocations": 500 },
"notAfter": 50000
}
]
This agent can call exactly one contract, at most 500 times, before block 50000. After block 50000, all transactions are rejected even if invocations remain.
Token-Spending Agent (Bounded Amount)
[
{
"action": "token:transfer",
"resource": "<specific-token-address>",
"limits": { "maxAmount": "1000000" },
"notAfter": 100000
}
]
This agent can transfer tokens from a specific token contract, but the cumulative transferred amount cannot exceed 1,000,000 units.
Multi-Action Agent (Research + Data)
[
{
"action": "contract:invoke",
"resource": "<research-contract>",
"limits": { "maxInvocations": 200 },
"notAfter": 75000
},
{
"action": "data:submit",
"resource": "*",
"limits": { "maxInvocations": 500 },
"notAfter": 75000
}
]
This agent can invoke one contract and submit arbitrary data, both bounded by invocation count and block-height expiry.
Renewing Capabilities
When a capability approaches its notAfter block or maxInvocations limit, the parent wallet submits a new ALTER_WALLET to replace the capability set. This is the explicit renewal mechanism; no auto-renewal occurs.
Capabilities vs. Auth Groups
| Dimension | Auth Groups | Capabilities |
|---|---|---|
| Scope | Wallet-level administrative CRUD | Action-level, resource-specific |
| Who uses them | Both human and agent wallets | Primarily agent wallets |
| Expiry | No expiry | Block-height expiry |
| Metering | No metering | Invocation count, amount limits |
| Effect when present | Adds permission | Restricts to whitelist only |
Rule: If a wallet has any capabilities assigned, it can only perform actions covered by those capabilities. Auth groups no longer govern its action scope; only capabilities do. Use auth groups for administrative access; use capabilities for operational scope.
Monitoring Capability Status
The node exposes endpoints for querying wallet capability state:
GET /chains/{chainId}/wallets/{address}/capabilities
→ Returns current capabilities, remaining invocations, expiry status
GET /chains/{chainId}/wallets/{address}/capabilities/status
→ Returns per-capability health: active | expiring_soon | exhausted | expired
Use these endpoints to monitor agent wallets and renew capabilities before they expire.
For common mistakes with capabilities (over-scoping, single-node verification, capability sprawl) see Anti-Patterns.