Skip to main content

Introduction

Policy engine is not available in Duo and Trio yet. Here is the overview of the implementation in Silent Network which is coming to Duo and Trio soon.

The Policy Engine is your platform's source of truth for access control. Declare rules once; the engine enforces them everywhere - across transactions, key operations, and any action your platform exposes.

Capabilities

CapabilityStateless PolicyStateful PolicyNotes
Multi-chain support✅✅One policy language across supported chains.
Offchain operations✅✅Can gate key export, key refresh, quorum change, and similar off-chain operations.
Per-transaction transfer limits✅✅Example: amount <= 100 for a single request.
Address controls: whitelisting (per-account)✅✅Restrict allowed destination/source addresses via policy conditions.
Address controls: source account/wallet restrictions✅✅Restrict signing requests to specific source wallets/accounts.
Scheduled policy and rule activation✅✅Set when a policy or individual rule is in active window, with effective_time.
Rolling-window totals and counts❌✅Track successful requests for cumulative spend limits and transaction rate limits.
Approval quorums✅✅Require eligible credential approvals for a signing rule or policy management.
External checks✅✅Require decisions from configured providers before a matched rule applies.
Fine-grained access control✅✅Supports issuer-based and condition-based controls.
MPC-focused architecture✅✅Designed for transactions signed by MPC nodes.
Key-bound policies✅✅Policies are bound to a specific key_id.
Auditable evaluation✅✅Evaluation outcomes are logged per action.

Concepts

A Policy is a JSON document that defines the complete set of constraints for a key. Policy contains multiple Rule. A Rule contains multiple Condition or ConditionGroup.

  • Rule: A composite statement about if the transaction/request satisfies all the condition groups or conditions to execute the action.
  • Condition: A boolean statement that evaluates a specific attribute of the transaction (e.g., checking if amount is less than 100).
  • ConditionGroup: A combination of boolean statements about the transaction/request.

Validation: Policies are integrity-checked using a Message Authentication Code (MAC).

Policy Structure

A Policy object follows this structure:

FieldTypeDescription
versionstringMust be "1.0".
descriptionstring (Optional)Policy description (max 512 chars).
management_approvalApprovalGroup (Optional)Quorum required to update or delete the policy. If not set, policy can be updated or deleted by the key's owner:

"management_approval": {
"members": [
{ "method": "eoa", "id": "<eoa_address>" },
{ "method": "passkey", "id": "<passkey_id>" }
],
"threshold": 2
}
rulesRule[]List of rules to evaluate.
effective_timeEffectiveTime (Optional)Validity window for the whole policy. Outside it every request is denied. See Effective Time.

Approval Group

Both management_approval and Rule.approval use the same structure, but they authorize different actions:

  • management_approval defines the quorum for policy management. Its members approve updates to or deletion of the policy currently attached to the key; this group does not approve signing requests.
  • Rule.approval defines the quorum for signing requests that match that rule.

The two groups are configured and evaluated independently. A credential can be a member of both, but it receives authority for each action only from the group in which it appears.

FieldTypeDescription
membersCredentialIdentity[]EOA, Passkey, or JWT credentials that can approve the action:

"members": [
{ "method": "eoa", "id": "<eoa_address>" },
{ "method": "passkey", "id": "<passkey_id>" }
]
thresholdnumberNumber of distinct member approvals required; from 2 through the number of members.

Each member contains its authentication method and credential id.

Rule Structure

FieldTypeDescription
descriptionstring (Optional)Rule description (max 512 chars).
issuerIssuer[]Entities authorized to issue the request that will be evaluated by the engine.
approvalApprovalGroup (Optional)Quorum required to authorize a signing request that matches this rule:

"approval": {
"members": [
{ "method": "eoa", "id": "<eoa_address>" },
{ "method": "passkey", "id": "<passkey_id>" }
],
"threshold": 2
}
actionstring"allow", "deny".
logicstring"or", "and" (combining conditions). Defaults to "and" if omitted.
chain_typestring"off", "ethereum", "solana".
conditionsConditionOrGroup[]List of conditions or condition groups.
effective_timeEffectiveTime (Optional)Validity window for this rule. Outside it the rule is skipped. See Effective Time.
external_checksExternalCheckGroup (Optional)External provider checks that must pass before this rule applies:

"external_checks": {
"logic": "and",
"checks": [
{
"check_id": "screen-destination",
"integration_id": "transaction-screening",
"operation": "screen-destination-address",
"duration_ms": 3000
}
]
}

Issuer

Defines who is making the request which is usually defined by the authentication payload in the request.

  • type: "UserId", "SessionKeyId", or "*" (any).
  • id: The specific ID of the user or session.

Effective Time

effective_time window bounds when a policy or a rule is in force. It is optional in both places, and leaving it out is the normal case: a policy without a window is always in force, and a rule without a window is always eligible to match. Add one to schedule policy activation or limit how long a rule applies.

When you do add it, it is a half-open window [from, to) expressed in seconds since the unix epoch:

{
"effective_time": { "from": 1798761600, "to": 1798768800 }
}
FieldTypeDescription
fromnumber (Optional)Unix seconds. Inclusive lower bound. Omit for no lower bound.
tonumber (Optional)Unix seconds. Exclusive upper bound. Omit for no upper bound.

A window must set at least from or to. Leave the field out entirely if you want the policy or rule to be always in force.

Policy window vs. rule window

A window on the Policy gates the whole document; a window on a Rule gates only that rule. What each one does depends on where the evaluation time falls:

At evaluation timePolicy.effective_timeRule.effective_time
No window setThe policy is always in force.The rule is always eligible to match.
Inside the windowThe policy is in force; its rules are evaluated as usual.The rule is evaluated as usual.
Outside the windowEvery request is denied, before any rule is evaluated. The denial reason is "Policy is outside its effective_time window".The rule is skipped entirely - it can neither allow nor deny - and the skip is reported as "Rule '<description>' skipped: outside effective_time".

External Checks

Rule.external_checks configures one or more decisions from external providers

FieldTypeDescription
logicstring"and" requires every check to pass; "or" requires at least one check to pass.
checksExternalCheck[]Non-empty list of external checks required by the rule, each check referenced by check_id:

"checks": [
{
"check_id": "screen-destination",
"integration_id": "transaction-screening",
"operation": "screen-destination-address",
"duration_ms": 3000
}
]

Each external check contains:

FieldTypeDescription
check_idstringUnique identifier for the check within the policy.
integration_idstringSelects a configured external provider integration.
operationstringSelects an operation offered by the integration.
duration_msnumberMaximum time allowed for the provider check, in milliseconds.

Conditions

Conditions are the building blocks of rules. They compare transaction attributes against expected values. A rule can contain individual conditions, condition groups, or both.

Condition Groups

A ConditionGroup combines a non-empty list of conditions using its own boolean logic. The rule's logic then combines the group's result with the other entries in Rule.conditions. Condition groups contain only conditions and cannot be nested.

{
"logic": "and",
"group": [
{
"transaction_type": "nativeTransfer",
"transaction_attr": "receiver",
"operator": "eq",
"value": "0x538f44e...d35Cc"
},
{
"transaction_type": "nativeTransfer",
"transaction_attr": "nativeValue",
"operator": "lte",
"value": "1000000000000000000"
}
]
}
FieldTypeDescription
logicstring"and" requires every condition to pass; "or" requires at least one condition to pass.
groupCondition[]Non-empty list of conditions evaluated within the group.
abiobject (Optional)Group-level ABI for decoding Ethereum calldata. A condition-level ABI takes precedence.

Condition Fields

FieldTypeDescription
transaction_typestringThe type of transaction (see below).
transaction_attrstringThe specific attribute to check (see below).
operatorstringComparison operator.
valueanyThe static value to compare against.
abiobject (Optional)ABI/IDL for decoding data.
stateobject (Optional)Links the condition to a state controller so it compares an accumulated total or count:

"state": {
"id": "<controller_id>",
"exclude_current": false
}
exclude_current defaults to false (include this request); use true to compare prior activity only.

Transaction Types

Used to deserialize the transaction payload correctly.

  • eip712: Typed Data signing.
  • eip191: Personal Sign.
  • erc20: Fungible Token interaction.
  • erc721: NFT interaction.
  • nativeTransfer: A transfer of the chain's native token (ETH or SOL), evaluated by sender, recipient, or amount.
  • solanaTransaction: A Solana transaction's accounts and instructions, such as an SPL token transfer or program interaction; not limited to a simple SOL transfer.

Transaction Attributes

The specific field within the transaction to evaluate.

NameDescriptionExample
senderTransaction sender. EIP-1559 "from" field in erc20, erc721 and ETH transfer transactions"0x742d35Cc...8f44e"
receiverTransaction destination. EIP-1559 "to" field in erc20, erc721 and ETH transfer transactions. Or, sender account key in SOL transfer transaction"0x538f44e...d35Cc"
nativeValueAmount of native token (ETH/SOL)."1000000000000000000" (1 ETH)
chainIdBlockchain Network ID.1
functionSelectorEthereum function signature (4 bytes)."0xa9059cbb"
messageEIP-191 message content."Sign this message"
verifyingContractEIP-712 contract address."0xCcCC...cccC"
primaryTypeEIP-712 primary type."Mail"
domainNameEIP-712 domain name."Ether Mail"
splTransferAmountSolana SPL token amount.1000000
splTokenMintSolana SPL token mint address."EPjFWdd5...TDt1v"
solanaAccountKeysList of accounts in a Solana transaction.["Account1...", "Account2..."]

If an attribute is not found in the list above, the engine will try to resolve it as a parameter from the provided ABI. To resolve naming collisions between built-in attributes and ABI parameters, you can use the abi: prefix (e.g. abi:sender). This forces the engine to look up the attribute in the ABI parameters instead of using the built-in value.

Operators

The operands to evaluate Transaction Attributes against the Policy's expected values.

  • eq: Equal
  • neq: Not Equal
  • lt: Less Than
  • lte: Less Than or Equal
  • gt: Greater Than
  • gte: Greater Than or Equal
  • in: Value is in a list.
  • all: All values match (for arrays).

Policy evaluation flow

For a signing request, the engine checks each rule in phases. A rule contributes its ALLOW or DENY action only after every phase it requires passes. A rule that fails a check contributes no action.

Loading Diagram...

The policy evaluation ends up with either ALLOW or DENY action. Depending on the policy evaluation result the MPC layer either starts or rejects the MPC-signing.

Rule

The phases run in this order:

  1. Local rule checks.

    • For each rule, the engine checks its active time window, issuer, chain and transaction type, and stateless/stateful conditions.
    • Only rules that pass these checks advance to the external-check phase.
  2. External checks.

    • Rules that pass the local checks, and have external_checks continue to evaluate the results returned from its external check group before moving to next phase.
    • A rule without external checks moves to the approval phase.
  3. Approval quorum.

    • If a rule defines Rule.approval, the engine counts distinct, eligible credentials against the approval quorum's threshold. If the threshold is not met, the rule contributes no action. With threshold met, the rule is a match and contributes to final policy evaluation.
    • Without an approval group, the rule matched the request at this point and contributes either ALLOW or DENY action to final policy evaluation.

Policy

Finally, the policy combines the actions of all fully confirmed rules. Any confirmed DENY takes precedence. Otherwise, at least one confirmed ALLOW is needed; with no actions, the default is DENY. Stateful entries are saved only after signing succeeds.

If no policy is attached, the current signing path skips policy evaluation.

Next steps