> ## 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.

# Policies

> Apply account-wide, wallet, and signer policy rules to business-account wallets using the JavaScript SDK policy helpers.

<Note>
  Business Accounts are in **early access**. See the [overview](/docs/javascript/reference/business-accounts/overview) for the model.
</Note>

Business-account wallets use Dynamic's policy composition feature. Rules are evaluated in layers, from the broadest to the narrowest:

* **Environment-Layer**: rules set by the developer for the whole environment. For how to set environment-wide rules, see [Creating & Managing Rules](/docs/overview/wallets/embedded-wallets/mpc/policies/creating-rules) in the general [Policies & Rules](/docs/overview/wallets/embedded-wallets/mpc/policies/overview) guide.
* **Account-Layer**: one policy that applies to every wallet in the business account. Only a business-account owner or admin can edit it.
* **Wallet-Layer**: rules for a specific wallet. On a business-account wallet, only a business-account owner or admin can edit it.
* **Signer-Layer**: rules for an individual signer. A signer can edit their own layer; a business-account owner or admin can edit any signer's layer.

A transaction must pass every layer.

For the rule types and security model, see [Policies & Rules](/docs/overview/wallets/embedded-wallets/mpc/policies/overview).

## Before you start

Before this: create and initialize a Dynamic client (see [Creating a Dynamic Client](/docs/javascript/reference/client/create-dynamic-client), [Initializing the Dynamic Client](/docs/javascript/reference/client/initialize-dynamic-client)).

The policy helpers are exported from `@dynamic-labs-sdk/client/waas` and the matching React hooks are exported from `@dynamic-labs-sdk/react-hooks`.

## PolicyRules keys

`createPolicy` and `removePolicyRules` work with a `PolicyRules` map. Each key maps to at most one underlying `WaasPolicyRule`, so calling `createPolicy` again with the same key updates that rule in place instead of duplicating it.

| Key                       | Type                                 | Description                                                                                                                     |
| ------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `allowAddresses`          | `string[]`                           | Only allow transactions that interact with these addresses. Combine with `maxAmountPerTransaction` to cap the same allow rule.  |
| `denyAddresses`           | `string[]`                           | Deny transactions that interact with these addresses. Independent of `allowAddresses`.                                          |
| `blockExport`             | `boolean`                            | When `true`, blocks exporting the wallet's private key.                                                                         |
| `maxAmountPerTransaction` | `{ amount: string; asset?: string }` | Cap the value of a single transaction, in the asset's smallest unit. Omit `asset` to cap the chain's native asset.              |
| `names`                   | `object`                             | Set a custom name for each rule kind. Keys are `allowAddresses`, `blockExport`, `denyAddresses`, and `maxAmountPerTransaction`. |

For the raw fields these keys produce, see [Rule fields](#rule-fields).

## Create or update rules

`createPolicy` writes a `PolicyRules` map to a layer in one batch. The `scope` object decides which layer is updated.

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={"system"}
    import { createPolicy } from '@dynamic-labs-sdk/client/waas';

    const layer = await createPolicy({
      scope: { businessAccountId },
      chain: 'EVM',
      chainIds: [1],
      rules: {
        allowAddresses: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'],
        maxAmountPerTransaction: { amount: '100000000000' }, // 100 USDC
      },
    });
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { useCreatePolicy } from '@dynamic-labs-sdk/react-hooks';

    const SaveAccountPolicy = ({ businessAccountId }: { businessAccountId: string }) => {
      const { mutate: createPolicy, isPending } = useCreatePolicy();

      return (
        <button
          onClick={() =>
            createPolicy({
              scope: { businessAccountId },
              chain: 'EVM',
              chainIds: [1],
              rules: {
                allowAddresses: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'],
                maxAmountPerTransaction: { amount: '100000000000' },
              },
            })
          }
          disabled={isPending}
        >
          Save account policy
        </button>
      );
    };
    ```
  </Tab>
</Tabs>

<ParamField path="scope" type="PolicyScope" required>
  The policy layer to update. Use `{ businessAccountId }` for the account layer, `{ walletId }` for a wallet layer, `{ walletId, shareSetId }` for a specific signer, or `{ shareSetId }` for the caller's own signer.
</ParamField>

<ParamField path="chain" type="string" required>
  The chain the rules apply to, for example `EVM`, `SVM`, or `SUI`.
</ParamField>

<ParamField path="chainIds" type="number[]">
  The chain IDs the rules apply to. Omit this to target the wildcard rule that matches every chain ID for `chain`.
</ParamField>

<ParamField path="rules" type="PolicyRules" required>
  The desired rules as a `PolicyRules` map.
</ParamField>

### Account-Layer

Use `scope: { businessAccountId }` to apply rules to every wallet in the business account.

```javascript theme={"system"}
import { WaasChainEnum } from '@dynamic-labs/sdk-api-core';
import { createPolicy } from '@dynamic-labs-sdk/client/waas';
const layer = await createPolicy({
  scope: { businessAccountId },
  chain: WaasChainEnum.Evm,
  chainIds: [1, 8453],
  rules: {
    allowAddresses: [
      '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
      '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
    ],
    maxAmountPerTransaction: { amount: '100000000000' },
    names: { allowAddresses: 'Approved stablecoin contracts' },
  },
});
```

### Wallet-Layer

Use `scope: { walletId }`, where `walletId` is the wallet's `verifiedCredentialId`.

```javascript theme={"system"}
import { WaasChainEnum } from '@dynamic-labs/sdk-api-core';
import { createPolicy } from '@dynamic-labs-sdk/client/waas';
const walletId = walletAccount.verifiedCredentialId;

const layer = await createPolicy({
  scope: { walletId },
  chain: WaasChainEnum.Evm,
  chainIds: [1],
  rules: {
    denyAddresses: ['0x...'],
    maxAmountPerTransaction: { amount: '500000000000000000' }, // 0.5 ETH
  },
});
```

### Signer-Layer

The signer layer applies to one share set. A wallet account has one own share set that signs transactions. It can also have other active share sets, such as delegated access and additional business-account signers.

`addBusinessAccountSigner` returns the `shareSetId` of the signer it grants access to, so capture it when you add a signer. If adding the signer requires quorum approval, it returns a pending action instead. See [Manage signers](/docs/javascript/reference/business-accounts/manage-signers).

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

const result = await addBusinessAccountSigner({
  businessAccountId,
  walletAccount,
  signerType: 'endUser',
  targetIdentity: { identifier: 'signer@example.com', identifierType: 'email' },
});

if (isBusinessAccountActionRequired(result)) {
  // Quorum not met yet — no share set to use until it's approved.
  throw new Error('Adding this signer requires approval.');
}

const { shareSetId } = result;
```

A signer can edit their own signer layer. A business-account owner or admin can edit any signer layer on a business-account wallet. To target a signer, use `scope: { walletId, shareSetId }`. For the caller's own signer, `scope: { shareSetId }` also works if the wallet account is in the client's state.

<Note>
  A `shareSetId` is the current identifier for a signer. It rotates when the wallet shares are refreshed or reshared, so do not store it. The policy is bound to a stable `signerId` that the enclave mints, so a rotated `shareSetId` still points to the same policy. Re-read `shareSetId` before each update. A stale `shareSetId` is rejected with a stale-share-set error.
</Note>

```javascript theme={"system"}
import { WaasChainEnum } from '@dynamic-labs/sdk-api-core';
import { createPolicy } from '@dynamic-labs-sdk/client/waas';
const walletId = walletAccount.verifiedCredentialId;

const layer = await createPolicy({
  scope: { walletId, shareSetId },
  chain: WaasChainEnum.Evm,
  chainIds: [1],
  rules: { blockExport: true },
});
```

The same pattern works for any other share set on the wallet, such as a delegated access share set. To remove or read the signer layer, use the same `scope` with `removePolicyRules` or `getPolicy`.

## Read rules

`getPolicy` fetches a layer and converts the underlying rules back into a `PolicyRules` map. Rules with fields that `PolicyRules` does not include go into `unmapped`, so they are never silently dropped.

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={"system"}
    import { getPolicy } from '@dynamic-labs-sdk/client/waas';

    const { rules, unmapped, layerId, updatedAt } = await getPolicy({
      scope: { businessAccountId },
    });

    // rules.allowAddresses, rules.maxAmountPerTransaction, etc.
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { useGetPolicy } from '@dynamic-labs-sdk/react-hooks';

    const AccountPolicy = ({ businessAccountId }: { businessAccountId: string }) => {
      const { data: policy, isLoading } = useGetPolicy({ scope: { businessAccountId } });

      if (isLoading) return null;

      return (
        <pre>{JSON.stringify(policy?.rules, null, 2)}</pre>
      );
    };
    ```
  </Tab>
</Tabs>

<ParamField path="scope" type="PolicyScope" required>
  The policy layer to read.
</ParamField>

## Remove rules

`removePolicyRules` removes the rules for one or more `PolicyRules` keys in one batch. Pass the keys to remove in the `rules` array.

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={"system"}
    import { removePolicyRules } from '@dynamic-labs-sdk/client/waas';

    const layer = await removePolicyRules({
      scope: { businessAccountId },
      chain: 'EVM',
      chainIds: [1],
      rules: ['maxAmountPerTransaction'],
    });
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { useRemovePolicyRules } from '@dynamic-labs-sdk/react-hooks';

    const RemoveCap = ({ businessAccountId }: { businessAccountId: string }) => {
      const { mutate: removeRules, isPending } = useRemovePolicyRules();

      return (
        <button
          onClick={() =>
            removeRules({
              scope: { businessAccountId },
              chain: 'EVM',
              chainIds: [1],
              rules: ['maxAmountPerTransaction'],
            })
          }
          disabled={isPending}
        >
          Remove cap
        </button>
      );
    };
    ```
  </Tab>
</Tabs>

<ParamField path="scope" type="PolicyScope" required>
  The policy layer to update.
</ParamField>

<ParamField path="chain" type="string" required>
  The chain the rules to remove apply to.
</ParamField>

<ParamField path="chainIds" type="number[]">
  The chain IDs the rules to remove apply to. Omit to target the wildcard rule for `chain`.
</ParamField>

<ParamField path="rules" type="string[]" required>
  The `PolicyRules` keys to remove, for example `['allowAddresses', 'maxAmountPerTransaction']`.
</ParamField>

## Who can update a rule

The enclave knows who makes the request. It uses that identity to decide which layers the caller can change and which rules they can edit or remove.

### Which layers each caller can change

| Caller                          | Can update these layers                      |
| ------------------------------- | -------------------------------------------- |
| Dashboard admin                 | Account, wallet, and signer layers           |
| Business account owner or admin | Their business account, wallets, and signers |
| Wallet owner                    | Their wallet and signer layers               |
| Signer                          | Their own signer layer                       |

### What you can do to a rule

Every rule stores who created it. The enclave uses this to decide what you can change.

* **Rules you created**: You can fully edit or remove them.
* **Rules shared with you**: A rule marked `modifiableBySigner` lets the signer it applies to edit or remove the rule. The signer can change constraint fields, such as addresses and value limits. The signer cannot change security fields.
* **Rules created by someone else**: Admins can revoke or replace these rules. Wallet owners and signers cannot edit or remove rules created by someone else, unless those rules are shared with them.

### Guardrails

* A rule with security fields, such as `disableBlockaidSecurityChecks` or `operationRestrictions`, cannot also be `modifiableBySigner`.
* `modifiableBySigner` only works on wallet and signer layers.
* A rule must enforce at least one of: an address list, a per-call value limit, or an operation restriction.
* `createPolicy` does not lock the layer before it updates. Two calls at the same time can create duplicate rules. Avoid concurrent updates to the same scope.

For the lower-level helpers that expose `modifiableBySigner` and `disableBlockaidSecurityChecks`, see `upsertWalletPolicyRule` and `upsertSignerPolicyRule`.

## Rule fields

A `WaasPolicyRule` has the following fields:

| Field                                      | Description                                                                                                      |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| `name`                                     | Human-readable rule name.                                                                                        |
| `ruleType`                                 | `allow` or `deny`.                                                                                               |
| `chain`                                    | Chain the rule applies to, e.g. `EVM`, `SVM`, `BTC`, `STELLAR`, `SUI`.                                           |
| `chainIds`                                 | Array of chain IDs the rule applies to.                                                                          |
| `addresses`                                | Array of addresses the rule applies to.                                                                          |
| `valueLimit.asset`                         | Asset the per-call cap applies to. Omit to cap the chain's native asset.                                         |
| `valueLimit.maxPerCall`                    | Maximum value per transaction, in the asset's smallest unit.                                                     |
| `operationRestrictions.blockExport`        | When `true`, blocks private key export.                                                                          |
| `operationRestrictions.blockClientSigning` | When `true`, blocks all end-user signing that is not delegated.                                                  |
| `operationRestrictions.blockRevocation`    | When `true`, blocks the end user from revoking delegated access.                                                 |
| `modifiableBySigner`                       | When `true`, the signer the rule applies to can edit or remove the rule. Only valid on wallet and signer layers. |

For allowlist semantics, address evaluation, and value limits, see [Policies & Rules](/docs/overview/wallets/embedded-wallets/mpc/policies/overview).

<Info>
  These helpers do not expose `modifiableBySigner` or `disableBlockaidSecurityChecks`. Use the lower-level `upsertWalletPolicyRule` or `upsertSignerPolicyRule` functions from `@dynamic-labs-sdk/client/waas` when you need those fields.
</Info>

## Next steps

<CardGroup cols={2}>
  <Card title="Manage signers" icon="key" href="/docs/javascript/reference/business-accounts/manage-signers">
    Add and remove signers on a business-account wallet.
  </Card>

  <Card title="Sign transactions" icon="signature" href="/docs/javascript/reference/business-accounts/signing">
    Sign with a business-account wallet.
  </Card>
</CardGroup>
