> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cdp.coinbase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Test and Debug Policies

> Simulate a wallet operation against your current policies to see whether it would be accepted or rejected, and why.

Policy simulation evaluates one operation against your project's current policies and returns the `action` the real operation would take. The result names the rule that decided it and the first unmet criterion of every rule that did not match.

Simulation does not sign, broadcast, or persist anything. Gas and nonce are not populated, so the transaction is evaluated exactly as you submit it.

Use simulation to:

* Check a new or updated policy against sample transactions before you rely on it.
* Find out why a real operation was rejected.
* Decide in your application whether to attempt an operation.

## Simulate an operation

Set `operation` to the policy rule operation you want to test, such as `signEvmTransaction` or `signEndUserEvmTransaction`, and pass the same inputs the real operation takes. See [Supported operations](./overview#supported-operations) for the full list.

**API Key Wallets**

**Node (TypeScript)**

```typescript theme={null}
import { parseEther, serializeTransaction } from "viem";
import { baseSepolia } from "viem/chains";

const account = await cdp.evm.getOrCreateAccount({ name: "PolicyAccount" });

const transaction = serializeTransaction({
  chainId: baseSepolia.id,
  type: "eip1559",
  nonce: 0,
  gas: 21000n,
  maxFeePerGas: 1000000000n,
  maxPriorityFeePerGas: 100000000n,
  to: "0x000000000000000000000000000000000000dEaD",
  value: parseEther("0.001"),
});

const result = await cdp.policies.simulate({
  operation: "signEvmTransaction",
  address: account.address,
  transaction,
});

console.log(result.action, result.reason);
```

**Python**

```python theme={null}
from eth_account.typed_transactions import DynamicFeeTransaction

from cdp.openapi_client.models.sign_evm_transaction_simulation_input import (
    SignEvmTransactionSimulationInput,
)
from cdp.utils import serialize_unsigned_transaction

account = await cdp.evm.get_or_create_account(name="PolicyAccount")

transaction = serialize_unsigned_transaction(
    DynamicFeeTransaction.from_dict(
        {
            "type": 2,
            "chainId": 84532,
            "nonce": 0,
            "to": "0x000000000000000000000000000000000000dEaD",
            "value": 1000000000000000,
            "gas": 21000,
            "maxFeePerGas": 1000000000,
            "maxPriorityFeePerGas": 100000000,
            "data": "0x",
        }
    )
)

result = await cdp.policies.simulate(
    SignEvmTransactionSimulationInput(
        operation="signEvmTransaction",
        address=account.address,
        transaction=transaction,
    )
)

print(result.action, result.reason)
```

**User Wallets**

End-user operations also take the end user's `userId`. They are evaluated against the project policy only.

**Node (TypeScript)**

```typescript theme={null}
const result = await cdp.policies.simulate({
  operation: "signEndUserEvmTransaction",
  userId: "e051beeb-7163-4527-a5b6-35e301529ff2",
  address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
  transaction, // RLP-encoded unsigned transaction, 0x-prefixed hex
});

console.log(result.action, result.reason);
```

**Python**

```python theme={null}
from cdp.openapi_client.models.sign_end_user_evm_transaction_simulation_input import (
    SignEndUserEvmTransactionSimulationInput,
)

result = await cdp.policies.simulate(
    SignEndUserEvmTransactionSimulationInput(
        operation="signEndUserEvmTransaction",
        user_id="e051beeb-7163-4527-a5b6-35e301529ff2",
        address="0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
        transaction=transaction,  # RLP-encoded unsigned transaction, 0x-prefixed hex
    )
)

print(result.action, result.reason)
```

## Read the result

A policy rejection is a successful response: the request returns `200` with `action: reject`. Simulation never returns a `policy_violation` error.

| Field | Description |
| - | - |
| `action` | `accept` or `reject`: what the real operation would do |
| `reason` | Why the operation got its `action` |
| `matchedRule` | The rule that decided the result. Present only when `reason` is `rule_accepted` or `rule_rejected` |
| `unmatchedRules` | Evaluated rules that did not match, each with its first unmet criterion |
| `evaluatedPolicies` | Policies consulted, in evaluation order |
| `evaluatedUnit` | The part of the operation this result describes, when the operation is split into units |

### Reasons

| Reason | Action | Meaning |
| - | - | - |
| `rule_accepted` | `accept` | `matchedRule` is an `accept` rule that matched all of its criteria, and no `reject` rule in the same policy matched |
| `no_rule_for_operation` | `accept` | No rule targets the operation, so it is accepted by default |
| `rule_rejected` | `reject` | `matchedRule` is a `reject` rule that matched all of its criteria |
| `no_rule_matched` | `reject` | At least one rule targets the operation but none matched, so it is rejected by default |

### Rejected by a rule

When `reason` is `rule_rejected`, the whole matched rule caused the rejection. `matchedRule.rule` is the rule as defined at the evaluated policy version. `matchedRule.observations[i]` is the value that `rule.criteria[i]` was compared against.

```json theme={null}
{
  "action": "reject",
  "reason": "rule_rejected",
  "matchedRule": {
    "policy": { "id": "123e4567-e89b-12d3-a456-426614174000", "scope": "project", "updatedAt": "2026-09-01T12:00:00Z" },
    "ruleIndex": 1,
    "rule": {
      "action": "reject",
      "operation": "signEvmTransaction",
      "criteria": [{ "type": "ethValue", "ethValue": "1000000000000000000", "operator": ">" }]
    },
    "observations": [{ "type": "ethValue", "ethValue": "2500000000000000000" }]
  },
  "unmatchedRules": [],
  "evaluatedPolicies": [
    { "id": "123e4567-e89b-12d3-a456-426614174000", "scope": "project", "updatedAt": "2026-09-01T12:00:00Z" }
  ]
}
```

### Rejected by default

When `reason` is `no_rule_matched`, read `unmatchedRules`. It lists every rule that targets the operation. Criteria are evaluated in order and evaluation stops at the first unmet one, so each entry shows one criterion:

| Field | Description |
| - | - |
| `policy` | The policy that contains the rule |
| `ruleIndex` | Zero-based index into the policy's `rules` |
| `criterionIndex` | Zero-based index of the first unmet criterion in the rule's `criteria` |
| `criterion` | The unmet criterion, as defined in the rule |
| `observed` | The value from the request that the criterion was compared against, when available |

In this example, the transaction exceeds the value limit of the project policy's first rule and targets the wrong network for the account policy's third rule:

```json theme={null}
{
  "action": "reject",
  "reason": "no_rule_matched",
  "unmatchedRules": [
    {
      "policy": { "id": "123e4567-e89b-12d3-a456-426614174000", "scope": "project", "updatedAt": "2026-09-01T12:00:00Z" },
      "ruleIndex": 0,
      "criterionIndex": 0,
      "criterion": { "type": "ethValue", "ethValue": "1000", "operator": "<=" },
      "observed": { "type": "ethValue", "ethValue": "1000000000000000" }
    },
    {
      "policy": { "id": "223e4567-e89b-12d3-a456-426614174000", "scope": "account", "updatedAt": "2026-09-02T12:00:00Z" },
      "ruleIndex": 2,
      "criterionIndex": 1,
      "criterion": { "type": "evmNetwork", "networks": ["base"], "operator": "in" },
      "observed": { "type": "evmNetwork", "network": "base-sepolia", "chainId": 84532 }
    }
  ],
  "evaluatedPolicies": [
    { "id": "123e4567-e89b-12d3-a456-426614174000", "scope": "project", "updatedAt": "2026-09-01T12:00:00Z" },
    { "id": "223e4567-e89b-12d3-a456-426614174000", "scope": "account", "updatedAt": "2026-09-02T12:00:00Z" }
  ]
}
```

Fixing the listed criterion does not guarantee the rule matches. Later criteria in the same rule were not evaluated.

When a rule matches, `unmatchedRules` contains only the rules evaluated before it. Within a policy, `reject` rules are evaluated before `accept` rules.

### Evaluated policies

`evaluatedPolicies` lists the project policy, then the account policy. Evaluation stops at the policy that contains `matchedRule`. End-user operations consult only the project policy.

Each policy's `updatedAt` identifies the version evaluated. `ruleIndex` and `criterionIndex` refer to that version, so compare `updatedAt` with the current policy before you edit rules by index.

### Evaluated unit

User operations are evaluated once per call, and Solana transactions once per signing account. When an operation is split this way, `evaluatedUnit` gives the unit's zero-based `index`, the total `count`, and, for Solana, the signing `address`. A rejected result describes the first rejected unit. An accepted result describes the last unit.

## Errors

| Status | When |
| - | - |
| `400` | The request is invalid, for example a missing required input (`invalid_request`) or a transaction that cannot be decoded (`malformed_transaction`) |
| `404` | The account, end user, or prepared user operation does not exist |
| `422` | A criterion cannot be evaluated for this input, so the real operation would fail closed. For example, `transaction_simulation_failed` when a `netUSDChange` criterion cannot simulate the transaction |
| `503` | The service is temporarily unavailable. Retry the request |

## Limitations

* The result is a snapshot. Policies, rate-limit counters, or chain state can change before you make the real call.
* `rateLimiting` criteria read the current usage but do not consume it, so simulating does not count toward a limit.
* `netUSDChange` criteria run transaction simulation as the real operation does, and pass on testnets.

## What to read next

* [Policy Engine overview](./overview): how rules and policies are evaluated.
* [EVM policies](./evm-policies): criteria for EVM operations.
* [Solana policies](./solana-policies): criteria for Solana operations.
