Skip to main content

Dry Run a Policy

Dry run previews whether a policy allows or denies a transaction before you save a policy update or submit a signing request. Use it to check draft rules, test stateful limits, or evaluate the policy already attached to a key.

The request uses the signing flow's policy engine. It produces no transaction signature, saves no policy or state changes, and leaves the key's authorization epoch unchanged.

Process Overview

  1. Choose Policy: Use an existing Key ID and supply a draft policy, or select the policy already attached to that key.
  2. Prepare Transaction: Include one transaction and, for a stateful draft, the controllers and starting state to test.
  3. Submit Authenticated Request: Send the request to your Wallet Backend using the same authentication flow as other policy-management requests.
  4. Review Result: Inspect the decision and any reasons or predicted state changes. Revise the draft or proceed with the signing or policy-update flow.

Policy Source

The policy field selects what to evaluate:

Modepolicy valueState used for evaluation
DraftPolicy as a JSON stringControllers and initial entries supplied in the request.
StoredEmpty string ("")The key's stored controllers and entries, copied into memory.

Use draft mode to test policy and controller changes before saving them. Use stored mode to preview the current policy. Stored mode requires an existing policy; omit state_controllers and initial_state_entries, or leave both as "[]".

Dry Run Policy in Silent Network

Loading Diagram...

Example

This draft allows the EIP-191 message "Sign me" for issuer alice. Authenticate as that issuer to test the allow case.

POST /v2/rest/dryRunPolicy accepts the following request:

curl -X POST "http://<YOUR_WALLET_BACKEND_ENDPOINT>/v2/rest/dryRunPolicy" \
-H "Content-Type: application/json" \
-d '{
"payload": {
"key_id": "YOUR_KEY_ID_HERE",
"message": "{\"tx-1\":{\"requestType\":\"EIP191\",\"signingMessage\":\"Sign me\"}}",
"signAlg": "secp256k1",
"policy": "{\"version\":\"1.0\",\"epoch\":1,\"description\":\"Allow one message\",\"rules\":[{\"description\":\"Allow exact EIP-191 message\",\"issuer\":[{\"type\":\"UserId\",\"id\":\"alice\"}],\"action\":\"allow\",\"chain_type\":\"ethereum\",\"conditions\":[{\"transaction_type\":\"eip191\",\"transaction_attr\":\"message\",\"operator\":\"eq\",\"value\":\"Sign me\"}]}]}"
},
"userSignatures": { ... }
}'

To test the stored policy instead, use the same request with "policy": "".

Send message, policy, state_controllers, and initial_state_entries as JSON strings. Replace the placeholders with your Key ID, backend endpoint, and authentication signatures.

Request Fields

FieldDescription
key_idKey ID used for authentication and, in stored mode, loading the policy.
messageMap containing exactly one transaction ID and its signing payload, sent as a JSON string.
signAlgSignature algorithm for the transaction, such as "secp256k1".
policyDraft policy as a JSON string, or "" to evaluate the stored policy.
state_controllersOptional array of draft controllers, sent as a JSON string. Defaults to "[]"; used only in draft mode.
initial_state_entriesOptional array of starting state entries, sent as a JSON string. Defaults to "[]"; used only in draft mode.
evaluation_countNumber of times to simulate the same transaction. Defaults to 1.

evaluation_count accepts 1–100 by default. Operators can set a different positive maximum with MAX_DRY_RUN_EVALUATION_COUNT.

Response

An allowed evaluation returns:

{
"policy_valid": true,
"stateful": false,
"evaluation_count": 1,
"results": [
{
"evaluation_index": 0,
"action": "allow"
}
]
}

An allow or deny is a completed evaluation. A deny means the policy would reject the transaction; it does not mean the API request failed.

FieldDescription
policy_validtrue when the policy parsed and validated. An invalid policy returns an API error instead.
statefulWhether the policy evaluates accumulated state.
evaluation_countNumber of evaluations performed.
resultsEvaluation results in order.
results[*].evaluation_indexEvaluation number, starting at 0.
results[*].action"allow" or "deny" for that evaluation.
results[*].reasonsExplanations of the decision, when available. Use them to understand why a transaction was denied.
results[*].state_changesPredicted state updates from an allowed stateful evaluation. These are not saved.

A later signing request evaluates the policy again using the state and approvals available at that time.

Stateful Dry Run

Use stateful dry run to test limits across repeated requests, such as a daily spend cap or a maximum transaction count. Each allowed evaluation updates the simulated state before the next evaluation; a denied evaluation leaves it unchanged.

In stored mode, this starts from the key's stored state. In draft mode, supply the controllers referenced by condition.state.id and, optionally, starting state entries. For the underlying concepts, see Stateful Policy.

Example: Test a Message Count Limit

To allow "Sign me" at most twice per 24-hour window, add the stateful condition under payload.policy.rules[].conditions.

These examples show the expanded request structure, with "..." for unrelated values. Convert policy and state_controllers to JSON strings before sending the request.

{ // dry-run request payload
"payload": {
"key_id": "...",
"message": "...",
"signAlg": "...",
"policy": {
// "...",
"rules": [
{
// "...",
"conditions": [
{
"transaction_type": "eip191",
"transaction_attr": "message",
"operator": "lte",
"value": 2,
"state": {
"id": "draft-message-count",
"exclude_current": false
}
}
]
}
]
},
"state_controllers": "...",
"initial_state_entries": "...",
"evaluation_count": "..."
},
"userSignatures": "..."
}

Here, method: "count" makes the condition compare the accumulated count with 2; exclude_current: false includes the request being evaluated.

Define the matching controller under payload.state_controllers:

{
"payload": {
// "...",
"state_controllers": [
{
"id": "draft-message-count",
"key_id": "YOUR_KEY_ID_HERE",
"description": "At most two evaluations per message in 24 hours",
"method": "count",
"window_config": {
"type": "rolling",
"interval": "day",
"count": 1
},
"partition_by": [
{
"transaction_type": "eip191",
"transaction_attr": "message"
}
]
}
],
},
"userSignatures": "..."
}

The controller ID must match state.id, and its key_id must match the request. partition_by tracks each message separately. Controllers support sum for accumulated amounts or count for request counts, with rolling windows.

To create a controller for use outside the dry run, see Create a State Controller.

Leave initial_state_entries as "[]" and set evaluation_count to 3. Starting from an empty count, all three evaluations within the same window produce:

evaluation_indexDecisionSimulated count after evaluation
0allow1
1allow2
2deny2

The third evaluation would raise the count to 3, so it is denied without changing the simulated state. No stored entries are created or updated.

Start from Existing Simulation Results

To continue a draft simulation, convert the previous result's state_changes to a JSON string and send it as initial_state_entries in the next request.

Set window_start (UTC) to test window expiry; otherwise, the window starts at each operator's current time. This field is not returned in the response.

Validation and Limits

Invalid requests return an API error rather than an evaluation result. Common causes include:

  • Invalid policy JSON, an unsupported transaction payload, or a message map containing more or fewer than one transaction.
  • An evaluation_count outside the configured range.
  • Stored mode with no policy attached to the key, or with draft controllers or initial entries supplied.
  • A referenced controller that is missing, a draft controller with the wrong Key ID, or duplicate draft controller IDs.
  • Initial entries with a missing controller, duplicate (controller_id, partition_key) pairs, or invalid numeric values.

Dry run upcoming:

  • External checks: If a matched rule requires external provider decisions, the dry run returns an API error. This requires mocked provider decisions.
  • Approval quorums: Requires simulate a full multi-approver quorum.