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

# 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 [Business Accounts](https://docs.dynamic.xyz/docs/business-accounts/accounts.md) for the model.

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](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/creating-rules.md) in the general [Policies & Rules](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/overview.md) 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](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/overview.md).

> [!NOTE]
>
> The helpers on this page write legacy rules. A layer holds legacy rules or [declarative rules](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/declarative-rules.md), never both, and a write that would mix them is refused. To write declarative rules from the SDK, see [Declarative rules for business accounts](https://docs.dynamic.xyz/docs/javascript/reference/business-accounts/policies/declarative-rules.md). For the underlying API, see the [account layer](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/account-layer.md), [wallet layer](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/wallet-layer.md), and [signer layer](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/signer-layer.md) pages, and for wallet and signer layers outside a business account, [Manage declarative rules](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/manage-policies-declarative.md).

## Before you start

Before this: 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)).

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. |
| `approvalRequirements` | `PolicyApprovalRequirement[]` | Require approvals before a business-account wallet signs. See [Quorum policies](https://docs.dynamic.xyz/docs/javascript/reference/business-accounts/policies/quorum-policies.md). |
| `names` | `object` | Set a custom name for each rule kind. Keys are `allowAddresses`, `blockExport`, `denyAddresses`, `maxAmountPerTransaction`, and `approvalRequirements`. |

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. For a layer of declarative rules, use `upsertAccountPolicyRule`, `upsertWalletPolicyRule`, or `upsertSignerPolicyRule` instead; see [Declarative rules for business accounts](https://docs.dynamic.xyz/docs/javascript/reference/business-accounts/policies/declarative-rules.md).

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

> [tab] React
>
> ```tsx theme={"system"}
> import { useCreatePolicy } from '@dynamic-labs-sdk/react-hooks';
> import { WaasChainEnum } from '@dynamic-labs/sdk-api-core';
>
> const SaveAccountPolicy = ({ businessAccountId }: { businessAccountId: string }) => {
>   const { mutate: createPolicy, isPending } = useCreatePolicy();
>
>   return (
>     <button
>       onClick={() =>
>         createPolicy({
>           scope: { businessAccountId },
>           chain: WaasChainEnum.Evm,
>           chainIds: [1],
>           rules: {
>             allowAddresses: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'],
>             maxAmountPerTransaction: { amount: '100000000000' },
>           },
>         })
>       }
>       disabled={isPending}
>     >
>       Save account policy
>     </button>
>   );
> };
> ```

**scope** `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.

**chain** `string` *required*

The chain the rules apply to, for example `EVM`, `SVM`, or `SUI`.

**chainIds** `number[]`

The chain IDs the rules apply to. Omit this to target the wildcard rule that matches every chain ID for `chain`.

**rules** `PolicyRules` *required*

The desired rules as a `PolicyRules` map.

### 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](https://docs.dynamic.xyz/docs/javascript/reference/business-accounts/manage-signers.md).

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

```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. Declarative rules are not part of that map; read them with `readPolicyLayerRules` as shown in [Declarative rules for business accounts](https://docs.dynamic.xyz/docs/javascript/reference/business-accounts/policies/declarative-rules.md#read-rules). Rules with fields that `PolicyRules` does not include go into `unmapped`, so they are never silently dropped.

> [tab] JavaScript
>
> ```javascript theme={"system"}
> import { getPolicy } from '@dynamic-labs-sdk/client/waas';
> import { WaasChainEnum } from '@dynamic-labs/sdk-api-core';
>
> const { rules, unmapped, layerId, updatedAt } = await getPolicy({
>   scope: { businessAccountId },
>   chain: WaasChainEnum.Evm,
> });
>
> // rules.allowAddresses, rules.maxAmountPerTransaction, etc.
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import { useGetPolicy } from '@dynamic-labs-sdk/react-hooks';
> import { WaasChainEnum } from '@dynamic-labs/sdk-api-core';
>
> const AccountPolicy = ({ businessAccountId }: { businessAccountId: string }) => {
>   const { data: policy, isLoading } = useGetPolicy({
>     scope: { businessAccountId },
>     chain: WaasChainEnum.Evm,
>   });
>
>   if (isLoading) return null;
>
>   return (
>     <pre>{JSON.stringify(policy?.rules, null, 2)}</pre>
>   );
> };
> ```

**scope** `PolicyScope` *required*

The policy layer to read.

## Remove rules

`removePolicyRules` removes the rules for one or more `PolicyRules` keys in one batch. To remove a declarative rule, remove it by `ruleId`; see [Declarative rules for business accounts](https://docs.dynamic.xyz/docs/javascript/reference/business-accounts/policies/declarative-rules.md#remove-a-rule). Pass the keys to remove in the `rules` array.

> [tab] JavaScript
>
> ```javascript theme={"system"}
> import { removePolicyRules } from '@dynamic-labs-sdk/client/waas';
> import { WaasChainEnum } from '@dynamic-labs/sdk-api-core';
>
> const layer = await removePolicyRules({
>   scope: { businessAccountId },
>   chain: WaasChainEnum.Evm,
>   chainIds: [1],
>   rules: ['maxAmountPerTransaction'],
> });
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import { useRemovePolicyRules } from '@dynamic-labs-sdk/react-hooks';
> import { WaasChainEnum } from '@dynamic-labs/sdk-api-core';
>
> const RemoveCap = ({ businessAccountId }: { businessAccountId: string }) => {
>   const { mutate: removeRules, isPending } = useRemovePolicyRules();
>
>   return (
>     <button
>       onClick={() =>
>         removeRules({
>           scope: { businessAccountId },
>           chain: WaasChainEnum.Evm,
>           chainIds: [1],
>           rules: ['maxAmountPerTransaction'],
>         })
>       }
>       disabled={isPending}
>     >
>       Remove cap
>     </button>
>   );
> };
> ```

**scope** `PolicyScope` *required*

The policy layer to update.

**chain** `string` *required*

The chain the rules to remove apply to.

**chainIds** `number[]`

The chain IDs the rules to remove apply to. Omit to target the wildcard rule for `chain`.

**rules** `string[]` *required*

The `PolicyRules` keys to remove, for example `['allowAddresses', 'maxAmountPerTransaction']`.

## 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 |
| - | - |
| Console 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](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/policies/overview.md).

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

## Next steps

> [card] [Declarative rules](https://docs.dynamic.xyz/docs/javascript/reference/business-accounts/policies/declarative-rules.md)
>
> Write declarative rules and approval rules on business-account layers.

> [card] [Manage signers](https://docs.dynamic.xyz/docs/javascript/reference/business-accounts/manage-signers.md)
>
> Add and remove signers on a business-account wallet.

> [card] [Sign transactions](https://docs.dynamic.xyz/docs/javascript/reference/business-accounts/signing.md)
>
> Sign with a business-account wallet.


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