This is a technical reference for engineers implementing permissions. For a high-level overview of how permissions work, see Permissions & Access Control.
Data Model
Permissions use a four-level hierarchy: Policy → Scopes → Permission Templates → Conditions.Policy
A policy defines the full set of actions an application may ever request from a user’s wallet. If something is not included in the policy, it cannot happen.| Property | Details |
|---|---|
| App-specific | Each API key has its own policy |
| Immutable | Changes require creating a new policy version |
Scopes
Policies are broken down into scopes, which are the user-facing consent items. Each scope appears as a consent checkbox during onboarding or login. Each scope has:- A name and description, shown to the user in plain language
- A required flag: required scopes must be accepted to use the app; optional scopes can be declined
- One or more permission templates, the actual rules behind this scope
Permission Types
Each permission template specifies a type that determines which wallet actions it governs:| Type | Description | When It Applies |
|---|---|---|
SIGN_MESSAGE | Sign arbitrary messages (personal_sign, signTypedData) | App requests a message signature |
TRANSFER | Send native tokens (ETH, SOL, etc.) to an address | Transaction has a to address with no contract calldata |
CALL_CONTRACT | Invoke a function on a deployed smart contract | Transaction includes calldata targeting a contract |
DEPLOY_CONTRACT | Deploy a new smart contract | Transaction has no to address (contract creation) |
CALL_CONTRACT permissions, developers can further restrict by smartContractAddress and smartContractFunction. This enables rules like “only allow calling the swap function on a specific DEX router contract.”
Effects: ALLOW vs DENY
Each permission template has an effect: eitherALLOW or DENY.
When a transaction is submitted, Para evaluates all matching permission templates:
- If any matching permission evaluates to
DENY→ the transaction is blocked - If any matching permission evaluates to
ALLOW(and none evaluate toDENY) → the transaction is allowed - If no permissions match → the transaction is blocked (default-deny)
DENY always takes precedence over ALLOW. This means developers can create broad ALLOW rules and then add narrow DENY exceptions for specific cases.Conditions
Conditions add constraints to permission templates. They enable going beyond “allow transfers” to “allow transfers under 1 ETH to specific addresses.” Each permission template can have zero or more conditions. All conditions on a single permission must evaluate to true for that permission’s effect to apply (logical AND). If any condition is false, the permission does not match and is skipped during evaluation.Resources
The resource field specifies what part of the transaction to inspect:| Resource | Description | Applies To |
|---|---|---|
VALUE | Transaction value in wei (as a string) | TRANSFER, CALL_CONTRACT |
TO_ADDRESS | Destination address of the transaction | TRANSFER, CALL_CONTRACT |
MESSAGE | The message content being signed | SIGN_MESSAGE |
ARGUMENTS | Smart contract function arguments (by index, e.g., the first argument) | CALL_CONTRACT |
The
ARGUMENTS resource allows inspecting specific parameters of a smart contract function call. For example, in an ERC-20 transfer(address, uint256) call, the first argument is the recipient address and the second is the amount.Comparators
The comparator field determines how the resource value is compared to the reference:| Comparator | Description |
|---|---|
EQUALS | Exact match |
NOT_EQUALS | Does not match |
GREATER_THAN | Strictly greater than |
GREATER_THAN_OR_EQUALS | Greater than or equal to |
LESS_THAN | Strictly less than |
LESS_THAN_OR_EQUALS | Less than or equal to |
CONTAINED_IN | Value is in a provided list |
NOT_CONTAINED_IN | Value is not in a provided list |
CONTAINED_IN / NOT_CONTAINED_IN).
Condition Type
All conditions currently use theSTATIC type, meaning they are evaluated against the transaction data at the time of the request.
Chain-Specific Scoping
Every permission template includes achainId field. When a transaction is submitted, Para checks that the permission’s chain matches the transaction’s chain. This allows creating different rules for different chains. For example, allowing transfers on Ethereum mainnet but restricting them on other chains.
An empty chainId ("") means the permission applies to all chains.
Current Scope
| Scope | Details |
|---|---|
| EVM condition evaluation | The detailed condition system (VALUE, TO_ADDRESS, ARGUMENTS) is implemented for EVM chains. Solana, Stellar, and Cosmos transactions are evaluated at the permission type level (allow/deny by type) but do not yet support fine-grained conditions |
| Static conditions | Conditions are evaluated against transaction data at request time. All condition values are set at policy creation time and cannot be customized by end users |
Full Policy Schema
Here is the complete JSON structure of a policy with two scopes:The
VALUE resource uses wei denomination. 1 ETH = 1,000,000,000,000,000,000 wei (10^18).Examples
Allow message signing on any chain
Allow message signing on any chain
A simple permission that lets the app sign messages without restrictions.An empty
chainId means the permission applies to all chains. No conditions means no additional restrictions.Allow transfers under 1 ETH on Ethereum mainnet
Allow transfers under 1 ETH on Ethereum mainnet
Restrict transfers to a maximum value on a specific chain.This permission only matches transactions on Ethereum mainnet (chain ID
1) where the value is at most 1 ETH.Allow calling a specific contract function
Allow calling a specific contract function
Restrict interactions to a single function on a specific smart contract. This example allows calling the
swap function on a DEX router, but only when the first argument (the token address) is in an approved list.Deny transfers to a blocked address
Deny transfers to a blocked address
Use a The first permission allows all transfers on Ethereum mainnet. The second blocks transfers to a specific address. Because
DENY rule to block specific recipients while keeping a broad ALLOW rule for everything else. DENY takes precedence.DENY always wins, the blocked address cannot receive transfers even though the broad ALLOW rule would otherwise match.Ready to Get Started?
Para Permissions Builder
Configure permissions policies in the Para Permissions Builder.