Skip to main content

Dry Run a Policy

Test a draft or stored policy against a single transaction before saving a policy update or starting a real signing flow.

POST /v2/rest/dryRunPolicy evaluates a transaction with the same policy engine used by signing requests, but it does not produce a signature and does not mutate policy or state data.

Use dry run when you need to:

  • Validate a draft policy JSON document before calling updatePolicy.
  • Preview whether a transaction would be allowed or denied by the draft/stored policy for a key.
  • Test draft state controllers before creating them.
  • Simulate repeated stateful evaluations without writing state entries.

How it fits

Loading Diagram...

Dry run follows the same authenticated initiator path as policy update and state-controller creation. A non-empty policy evaluates a draft policy from the request. An empty policy loads the stored policy for key_id, reject when policy is empty and (state_controllers or initial_state_entries) contains draft data.

In both modes, persistence is read-only: draft inputs are never saved, and stored policy state may be read but is never mutated.

Request

Draft policy:

curl -sS -X POST "$WALLET_PROVIDER_BE_URL/v2/rest/dryRunPolicy" \
-H "Content-Type: application/json" \
-d '{
"payload": {
"key_id": "YOUR_KEY_ID",
"message": "{\"tx-1\":{\"requestType\":\"EIP191\",\"signingMessage\":\"Sign me\"}}",
"signAlg": "secp256k1",
"policy": "{\"version\":\"1.0\",\"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": { ... }
}'

Stored policy:

curl -sS -X POST "$WALLET_PROVIDER_BE_URL/v2/rest/dryRunPolicy" \
-H "Content-Type: application/json" \
-d '{
"payload": {
"key_id": "YOUR_KEY_ID",
"message": "{\"tx-1\":{\"requestType\":\"EIP191\",\"signingMessage\":\"Sign me\"}}",
"signAlg": "secp256k1",
"policy": ""
},
"userSignatures": { ... }
}'
type DryRunPolicyPostRequest = {
payload: DryRunPolicyRequest;
userSignatures?: Record<string, UserAuthentication>;
};

type DryRunPolicyRequest = {
key_id: string;
message: string;
signAlg: string;
policy: string;
state_controllers?: string;
initial_state_entries?: string;
evaluation_count?: number;
};
FieldDescription
key_idKey ID whose auth context is used for the dry run. When policy is empty, the stored policy for this key is loaded.
messageStringified single-entry map of transaction ID to DSGTransaction. The request must contain exactly one transaction.
signAlgSignature algorithm used to parse message into a policy-engine transaction.
policyPolicy JSON string evaluated only for this dry run. Send an empty string to load the stored policy for key_id.
state_controllersOptional JSON string containing DryRunStateController[]. Defaults to "[]". Used only with a supplied draft policy.
initial_state_entriesOptional JSON string containing PolicyStateEntry[]. Defaults to "[]". Used only with a supplied draft policy.
evaluation_countOptional number of repeated evaluations to simulate for the same transaction. Defaults to 1.

evaluation_count must be between 1 and 100. This is configurable with MAX_DRY_RUN_EVALUATION_COUNT.

Policy source

policy valueWhat is evaluatedStateful data source
Non-empty stringSupplied draft policy JSONRequest state_controllers and initial_state_entries
Empty stringStored policy for key_idStored controllers and entries for key_id, loaded into memory

Use draft mode before saving policy or state-controller changes. Use stored mode to preview how the current key policy would evaluate a transaction before sending a real sign request.

Response

Allowed evaluation:

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

Denied evaluation:

{
"policy_valid": true,
"stateful": false,
"evaluation_count": 1,
"results": [
{
"evaluation_index": 0,
"action": "deny",
"reasons": [
"No allow rule matched transaction type eip191 for issuer UserId:alice",
"Condition message eq \"Sign me\" was not satisfied"
]
}
]
}

Policy denial is a successful dry-run response. Inspect results[*].action to see whether the policy would allow or deny the transaction, and inspect results[*].reasons to understand which rule or condition prevented approval.

Request validation failures do not use the dry-run evaluation response shape. They fail before policy evaluation and return an API error message for the first validation failure, see validation section.

type DryRunPolicyResponse = {
policy_valid: boolean;
stateful: boolean;
evaluation_count: number;
results: DryRunPolicyEvaluationResult[];
};

type DryRunPolicyEvaluationResult = {
evaluation_index: number;
action: "allow" | "deny";
reasons?: string[];
state_changes?: PolicyStateEntry[];
};
FieldDescription
policy_validWhether the evaluated policy parsed and validated successfully.
statefulWhether the evaluated policy uses stateful evaluation.
evaluation_countNumber of evaluations requested and returned.
resultsOrdered dry-run evaluation results.
evaluation_indexZero-based simulation index.
actionPolicy decision for this evaluation. A policy denial returns deny; it is not a transport failure.
results[*].reasonsDenial reasons for a specific evaluation, when available.
state_changesPredicted state updates for an allowed stateful evaluation. These entries are not persisted.

The response does not include key_id; the caller already supplied it. It also does not include evaluated_at, because each participant uses its own local evaluation time.

Stateful dry run

For a draft stateful policy, include draft state controllers and optional initial state entries in the request. The policy references the draft controller through condition.state.id, and the controller ID is defined by the client.

For a stored stateful policy, send policy: "". The service loads the stored policy, stored controllers, and stored entries for the key into an in-memory state store.

{
"transaction_type": "erc20",
"transaction_attr": "value",
"operator": "lte",
"value": 1000,
"state": {
"id": "draft-controller-1",
"exclude_current": false
}
}
type DryRunStateController = {
id: string;
key_id: string;
description: string;
method: "sum" | "count";
window_config: WindowConfig;
partition_by: PartitionField[];
};
FieldDescription
idClient-generated draft controller ID referenced by condition.state.id in the dry-run policy.
key_idMust match the request key_id.
descriptionHuman-readable controller description.
methodAggregation method used by the referenced state condition.
window_configRolling or fixed-window configuration.
partition_byFields used to derive partition keys for state entries.
curl -sS -X POST "$WALLET_PROVIDER_BE_URL/v2/rest/dryRunPolicy" \
-H "Content-Type: application/json" \
-d '{
"payload": {
"key_id": "YOUR_KEY_ID",
"message": "{\"tx-1\":{\"requestType\":\"EIP1559\",\"signingMessage\":\"...\"}}",
"signAlg": "secp256k1",
"policy": "{...}",
"state_controllers": "[{\"id\":\"draft-controller-1\",\"key_id\":\"YOUR_KEY_ID\",\"description\":\"Daily limit\",\"method\":\"sum\",\"window_config\":{\"type\":\"fixed\",\"interval\":\"day\"},\"partition_by\":[{\"transaction_type\":\"erc20\",\"transaction_attr\":\"to\"}]}]",
"initial_state_entries": "[]",
"evaluation_count": 3
},
"userSignatures": { ... }
}'

For repeated stateful dry run, each allowed evaluation applies its predicted state changes to the in-memory store before the next evaluation. This simulates repeated use of the same transaction without touching persistent storage.

Validation

The request is rejected before evaluation when:

  • evaluation_count is 0 or greater than MAX_DRY_RUN_EVALUATION_COUNT.
  • policy is non-empty and contains invalid JSON.
  • policy is empty and key_id has no stored policy or when policy is empty and (state_controllers or initial_state_entries) contains draft data.
  • A draft policy references condition.state.id but no matching draft controller is supplied in state_controllers.
  • A stored policy references a controller that cannot be loaded from stored state.
  • A draft controller has a key_id different from the request key_id.
  • Two draft controllers use the same id.
  • An initial state entry references a missing controller.
  • An initial state entry duplicates the same (controller_id, partition_key).
  • An initial state entry contains an invalid numeric value.

A policy denial is different from these validation errors.

Environment

VariableDefaultDescription
MAX_DRY_RUN_EVALUATION_COUNT100Maximum allowed evaluation_count. Must be greater than 0.