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

# Advanced conditions

> The full condition language for policy rules: operators, combinators, lists with exists, matching every item, the nesting limit, and combined flows.

> [!NOTE]
>
> Declarative rules are in **early access** and are not enabled by default. [Talk to us](https://www.dynamic.xyz/talk-to-us) if you'd like to participate.

This page covers the full condition language, for rules the [examples](https://www.dynamic.xyz/docs/embedded-wallets/mpc/policies/rule-examples.md) do not cover. For how rules combine, read [Declarative rules](https://www.dynamic.xyz/docs/embedded-wallets/mpc/policies/declarative-rules.md).

## Comparisons

The basic building block compares one field with a value:

```json theme={"system"}
{ "field": "chain.id", "op": "in", "value": [1, 8453] }
```

| Operator | Meaning | `value` holds |
| - | - | - |
| `eq`, `neq` | Equals, does not equal | A single value |
| `in`, `nin` | Is one of, is not one of | A list |
| `gt`, `gte`, `lt`, `lte` | More than, at least, less than, at most | A single value |
| `contains` | The text appears somewhere inside | A single string |

Ordering operators are for amounts (`totalRawValue`) and numbers. Do not use `contains` to match an address: addresses share long runs of characters, so a partial match catches addresses you did not intend. Use `in` with the full addresses. Addresses are compared without regard to case.

## Combining conditions

| Form | Means |
| - | - |
| `{ "all": [ ... ] }` | Every condition is true (AND) |
| `{ "any": [ ... ] }` | At least one condition is true (OR) |
| `{ "not": { ... } }` | The condition is false |
| `{ "always": true }` | Matches every request. Only valid as a rule's whole condition, and the rule must be last. |

## Lists and exists

Top-level fields such as `chain.id` are listed in [Rule anatomy](https://www.dynamic.xyz/docs/embedded-wallets/mpc/policies/rule-anatomy.md#fields-a-rule-reads). A transaction can move several assets and touch several addresses, so those facts arrive as lists. You read a list with `exists`, which is true when at least one item matches its `where`:

| List (`in`) | Each item has |
| - | - |
| `outgoingAssets` | `asset.address`, `asset.isNative`, `asset.type`, and `totalRawValue` (the amount of that asset, summed across the transaction) |
| `counterparties` | `address`: every address the transaction touches, including the recipient, the contract called, any contract behind a proxy, and any approved spender |
| `effectiveCounterparties` | `address`: the same, with gas infrastructure removed (bundler, entrypoint, paymaster, and similar) |
| `spendWindows` | `type` (`rolling`), `duration`, `asset.address` or `asset.isNative`, and `totalRawValue` (what the wallet has moved in that window, including this transaction) |

Use `counterparties` for denylists, so that naming an address always blocks it. Use `effectiveCounterparties` for allowlists, so that you do not have to list infrastructure you never chose.

Keep an asset and its amount inside the same `exists`. Split across two `exists` blocks, the two comparisons can match different assets: a small amount of the named token alongside a large amount of another one. An `exists` cannot contain another `exists`.

## Matching every item

There is no "every" operator. To say "every item is X", write "there is no item that is not X" with `not` around `exists`:

| Written | Reads as | Means |
| - | - | - |
| `exists(not native)` | At least one asset is not the coin | The transaction moves a token |
| `not exists(not native)` | No asset is anything but the coin | Every asset is the coin |

The same pattern works for destinations: `not exists(address nin [list])` means every address is on the list.

```json theme={"system"}
{
  "not": {
    "exists": {
      "in": "outgoingAssets",
      "where": { "field": "asset.isNative", "op": "eq", "value": false }
    }
  }
}
```

## Nesting limit

A condition can be at most five levels deep. Each `all`, `any`, `not`, `exists`, and the final comparison counts as one level, so `any` then `all` then `not` then `exists` then a comparison is exactly five. A deeper condition is rejected when you save it. A layer can hold up to 200 rules per phase, and a list of values or conditions can hold up to 200 entries.

## Combining flows

Both examples below are allow rules. Pair each with the `DENY` fallback. The outcomes were recorded by running the rules against the policy engine. The vendor is `0x5f09B2caaafD345EaE7B711A32CcBdf59befB4bB`, and the USDC implementation is `0x43506849d7c04f9138d1a2050bbf3a0c054402dd`.

**Nothing but USDC to approved vendors.** The first `not exists` says every asset is USDC, and the second says every address is approved. With plain `exists`, the rule would allow a transaction that moves USDC and something else.

```json theme={"system"}
{
  "ruleId": "00000000-0000-4000-8000-000000000013",
  "name": "USDC to approved vendors",
  "phase": "transaction",
  "then": "ALLOW",
  "when": {
    "all": [
      {
        "not": {
          "exists": {
            "in": "outgoingAssets",
            "where": { "field": "asset.address", "op": "neq", "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" }
          }
        }
      },
      {
        "not": {
          "exists": {
            "in": "effectiveCounterparties",
            "where": {
              "field": "address",
              "op": "nin",
              "value": [
                "0x5f09B2caaafD345EaE7B711A32CcBdf59befB4bB",
                "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
                "0x43506849d7c04f9138d1a2050bbf3a0c054402dd"
              ]
            }
          }
        }
      }
    ]
  }
}
```

**Either native transfers, or USDC to approved vendors.** `any` means either branch is enough. Each branch requires every asset to qualify, so a transaction that moves ETH and USDC together satisfies neither. This rule sits exactly at the five-level limit.

```json theme={"system"}
{
  "ruleId": "00000000-0000-4000-8000-000000000014",
  "name": "Two permitted flows",
  "phase": "transaction",
  "then": "ALLOW",
  "when": {
    "any": [
      {
        "not": {
          "exists": {
            "in": "outgoingAssets",
            "where": { "field": "asset.isNative", "op": "eq", "value": false }
          }
        }
      },
      {
        "all": [
          {
            "not": {
              "exists": {
                "in": "outgoingAssets",
                "where": { "field": "asset.address", "op": "neq", "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" }
              }
            }
          },
          {
            "not": {
              "exists": {
                "in": "effectiveCounterparties",
                "where": {
                  "field": "address",
                  "op": "nin",
                  "value": [
                    "0x5f09B2caaafD345EaE7B711A32CcBdf59befB4bB",
                    "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
                    "0x43506849d7c04f9138d1a2050bbf3a0c054402dd"
                  ]
                }
              }
            }
          }
        ]
      }
    ]
  }
}
```

| Transaction | USDC to approved vendors | Either flow |
| - | - | - |
| ETH to a stranger | Denied | Allowed |
| USDC to the vendor | Allowed | Allowed |
| USDC to a stranger | Denied | Denied |
| WETH to the vendor | Denied | Denied |
| ETH and USDC together | Denied | Denied |
| Anything touching `0x...0bad` | Denied | Allowed |

## Destinations and tokens

* **A token allowlist must name the token contract.** A token transfer touches the token contract, so an allowlist of destinations that names only the vendor refuses the payment. On a proxy token such as USDC, name the implementation as well, and update the list if the token is upgraded to a new implementation.
* **An allow rule says nothing about what it does not mention.** The "either flow" rule above checks destinations only on the USDC branch, so ETH can go anywhere, including to a blocked address. If that matters, add a deny rule for the address in the same layer. A deny beats an allow.
* **Test with a transaction that moves two assets.** A single-asset transfer often cannot tell a correct rule from one that uses `exists` where it needs `not exists`.

## Next steps

> **[Rule anatomy](https://www.dynamic.xyz/docs/embedded-wallets/mpc/policies/rule-anatomy.md)**
>
> Rule fields, the fallback rule, and approval requirements.

> **[Rule examples](https://www.dynamic.xyz/docs/embedded-wallets/mpc/policies/rule-examples.md)**
>
> A ready-made rule for each common case, and two whole layers.

> **[Declarative rules](https://www.dynamic.xyz/docs/embedded-wallets/mpc/policies/declarative-rules.md)**
>
> How rules, the fallback, and the allow and deny styles work.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.