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

# Declarative rules for business accounts

> Write declarative policy rules, including approval rules, on a business account's account, wallet, and signer layers with the JavaScript SDK.

> [!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.

[Declarative rules](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/declarative-rules.md) describe what a request must look like and what happens when it does, so one rule can combine chain, token, amount, destination, and recent spending, or hold a transaction for approvers. This page shows the JavaScript SDK helpers for writing them on a business account.

## Before you start

* Create and initialize a Dynamic client (see [Creating a Dynamic Client](https://docs.dynamic.xyz/docs/javascript/reference/client/create-dynamic-client.md), [Initializing the Dynamic Client](https://docs.dynamic.xyz/docs/javascript/reference/client/initialize-dynamic-client.md)).
* Use `@dynamic-labs-sdk/client` 1.42.0 or later. The helpers are exported from `@dynamic-labs-sdk/client/waas`.
* The helpers run with the signed-in member's session, not an API token.
* A layer holds legacy rules or declarative rules, never both. A write that would mix them is refused, and until declarative rules are enabled for your environment a declarative write throws an `APIError` with code `rule_syntax_disabled`.

### Legacy or declarative

Use the legacy `PolicyRules` helpers on [Policies](https://docs.dynamic.xyz/docs/javascript/reference/business-accounts/policies/overview.md) when an address list, a per-transaction cap, or a key-export block is all you need. Use declarative rules when a rule has to combine conditions, cap spending over time, or require approval only under a specific condition. Before writing a layer, decide whether it names what is forbidden (deny style) or what is permitted (allow style with a deny fallback); see [Deny style and allow style](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/declarative-rules.md#deny-style-and-allow-style).

## Which layer to use

| Layer | Applies to | Who can write it | Helpers |
| - | - | - | - |
| Account | Every wallet in the business account | Business-account owner or admin | `upsertAccountPolicyRule`, `removeAccountPolicyRule` |
| Wallet | One wallet | Business-account owner or admin | `upsertWalletPolicyRule`, `removeWalletPolicyRule` |
| Signer | One signer share set | The signer for their own layer, or a business-account owner or admin | `upsertSignerPolicyRule`, `removeSignerPolicyRule` |

`replacePolicyLayerRules` replaces any of the three, and only business-account admins and environment admins can call it. Approval rules can be written only by a business-account owner or admin, or an environment admin.

## Add or update a rule

The examples use this rule from [Rule examples](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/rule-examples.md). Leave `ruleId` out to create a rule, and the returned layer contains the generated id.

```javascript theme={"system"}
import {
  WaasPolicyAction,
  WaasPolicyDeclarativeRuleInputPhaseEnum,
} from '@dynamic-labs/sdk-api-core';

const rule = {
  ruleId: '00000000-0000-4000-8000-000000000004',
  name: 'Blocked address',
  phase: WaasPolicyDeclarativeRuleInputPhaseEnum.Transaction,
  then: WaasPolicyAction.Deny,
  when: {
    exists: {
      in: 'counterparties',
      where: { field: 'address', op: 'in', value: ['0x0000000000000000000000000000000000000bad'] },
    },
  },
};
```

**Account layer**

```javascript theme={"system"}
import { upsertAccountPolicyRule } from '@dynamic-labs-sdk/client/waas';

const layer = await upsertAccountPolicyRule({ businessAccountId, rule });
```

**Wallet layer**

```javascript theme={"system"}
import { upsertWalletPolicyRule } from '@dynamic-labs-sdk/client/waas';

const layer = await upsertWalletPolicyRule({ walletAccount, rule });
```

**Signer layer**

```javascript theme={"system"}
import { upsertSignerPolicyRule } from '@dynamic-labs-sdk/client/waas';

// Omit shareSetId to target the caller's own signer.
const layer = await upsertSignerPolicyRule({ walletAccount, shareSetId, rule });
```

## Read rules

Read the layer, then keep its declarative rules with `readPolicyLayerRules` and `isDeclarativePolicyRule`.

```javascript theme={"system"}
import {
  getAccountPolicyLayer,
  isDeclarativePolicyRule,
  readPolicyLayerRules,
} from '@dynamic-labs-sdk/client/waas';

const layer = await getAccountPolicyLayer({ businessAccountId });
const rules = readPolicyLayerRules(layer).filter(isDeclarativePolicyRule);
```

For the wallet and signer layers, use `getWalletPolicyLayer({ walletAccount })` and `getSignerPolicyLayer({ walletAccount, shareSetId })`.

## Remove a rule

```javascript theme={"system"}
import { removeAccountPolicyRule } from '@dynamic-labs-sdk/client/waas';

const layer = await removeAccountPolicyRule({
  businessAccountId,
  ruleId: '00000000-0000-4000-8000-000000000004',
});
```

For the wallet and signer layers, use `removeWalletPolicyRule({ walletAccount, ruleId })` and `removeSignerPolicyRule({ walletAccount, shareSetId, ruleId })`.

## Replace a layer's rules

`replacePolicyLayerRules` sets the layer's full rule list, so any rule you leave out is removed. Keep the `ruleId` of every rule you are keeping. `scope` is `{ businessAccountId }` for the account layer, `{ walletId }` for a wallet layer, or `{ walletId, shareSetId }` for a signer layer.

```javascript theme={"system"}
import { replacePolicyLayerRules } from '@dynamic-labs-sdk/client/waas';

const layer = await replacePolicyLayerRules({
  scope: { businessAccountId },
  rules: [rule],
});
```

## Approval rules

A rule with `then: REQUIRE_APPROVAL` holds a matching transaction until enough members approve it. This one holds any transfer of more than 10,000 USDC on an EVM chain until two members with the built-in `admin` role or the custom `cfo` role approve it within 24 hours.

```javascript theme={"system"}
import { upsertAccountPolicyRule } from '@dynamic-labs-sdk/client/waas';
import {
  WaasPolicyAction,
  WaasPolicyDeclarativeRuleInputPhaseEnum,
} from '@dynamic-labs/sdk-api-core';

const layer = await upsertAccountPolicyRule({
  businessAccountId,
  rule: {
    ruleId: '00000000-0000-4000-8000-000000000002',
    name: 'Large USDC needs approval',
    phase: WaasPolicyDeclarativeRuleInputPhaseEnum.Transaction,
    then: WaasPolicyAction.RequireApproval,
    when: {
      all: [
        { field: 'operation.chain', op: 'eq', value: 'EVM' },
        {
          exists: {
            in: 'outgoingAssets',
            where: {
              all: [
                { field: 'asset.address', op: 'eq', value: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' },
                { field: 'totalRawValue', op: 'gt', value: '10000000000' },
              ],
            },
          },
        },
      ],
    },
    approvalRequirements: [
      {
        id: 'approval-1',
        v: 1,
        requiredApprovals: 2,
        eligibleApproverRoles: ['admin', 'cfo'],
        completeWithinSeconds: 86400,
      },
    ],
  },
});
```

* The member who made the request never counts toward `requiredApprovals`.
* Role names are matched without regard to case, and a custom role also counts as the built-in role it inherits.
* Dynamic does not check that a role exists. If you name a role nobody in the account holds, a matching request can never be approved.
* The rule's `when` decides when approval applies. Unlike a legacy `approvalRequirements` entry, a declarative requirement has no `when` of its own, and one that includes it is refused.

To offer real role names when you build an approver picker, list the account's custom roles. Any member can read them. The built-in roles `owner`, `admin`, and `viewer` are always available and are not in the result.

```javascript theme={"system"}
import { listBusinessAccountRoles } from '@dynamic-labs-sdk/client/waas';

const roles = await listBusinessAccountRoles({ businessAccountId });
// For example: { cfo: { inherits: 'admin' } }
const approverRoles = ['owner', 'admin', 'viewer', ...Object.keys(roles)];
```

A matching transaction goes through the same signing quorum flow as a legacy approval rule: the signing call rejects with `SigningConsentRequiredError`, other members approve the proposal, and the initiator resumes it. Follow [Complete a quorum-gated signing operation](https://docs.dynamic.xyz/docs/javascript/reference/business-accounts/policies/quorum-policies.md#complete-a-quorum-gated-signing-operation).

## Next steps

> **[Rule examples](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/rule-examples.md)**
>
> Ready-made declarative rules to copy and adjust.

> **[Quorum policies](https://docs.dynamic.xyz/docs/javascript/reference/business-accounts/policies/quorum-policies.md)**
>
> Approve and resume a quorum-gated signing request.


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