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

# Governance

> Require approvals for account actions like adding a member, transferring ownership, or changing signers.

<Note>
  Business Accounts are in **early access**. See the [overview](/docs/business-accounts/overview) for the model. For the governance model itself, the approval lifecycle, and the requirement fields, see [Governance](/docs/business-accounts/governance).
</Note>

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

## Initiate a governed action

The following example uses a known user ID. The SDK can construct and sign this intent locally.

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

    const result = await addBusinessAccountMember({
      businessAccountId: 'ba_123',
      targetIdentity: { userId: 'user_123' },
      role: 'admin',
    });

    if (isBusinessAccountActionRequired(result)) {
      console.log(result.proposalId, result.approvalRequirements);
    }
    ```
  </Tab>
</Tabs>

A signed mutation that has not met quorum returns `BusinessAccountActionRequired`. The `satisfied` value on each entry in `approvalRequirements` is authoritative. Do not infer readiness from `approved >= required`.

### Approve, veto, or execute a proposal

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

    const [proposal] = await listBusinessAccountProposalsAwaitingMyApproval();

    if (proposal) {
      const updated = await approveBusinessAccountProposal({
        businessAccountId: proposal.businessAccountId,
        proposal,
      });

      if (updated.status === 'executed') {
        console.log('The account change is active.');
      }
    }
    ```
  </Tab>
</Tabs>

Use `vetoBusinessAccountProposal` to reject a proposal permanently. Use `withdrawBusinessAccountProposalApproval` to revoke an approval. Use `executeBusinessAccountProposal` to apply a satisfied proposal when the account does not execute it automatically.

<Info>
  When the account executes matching proposals automatically, the approval that satisfies the final requirement also applies the account change. The returned proposal has `status: 'executed'`.
</Info>

For proposal lifecycle events, see [Business account webhooks](/docs/business-accounts/webhooks#approvals).

## Set governance requirements

`setBusinessAccountGovernance` replaces the account's entire governance section. It does not merge with the current value. Omitting an action removes that action's approval requirements. Read the current value first when you want to change only one action.

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

    const businessAccountId = 'ba_123';
    const current = await getBusinessAccountGovernance({ businessAccountId });

    await setBusinessAccountGovernance({
      businessAccountId,
      governance: {
        ...current,
        addMember: [
          {
            id: 'one-admin',
            schemaVersion: 1,
            requiredApprovals: 1,
            eligibleApproverRoles: ['admin'],
          },
        ],
      },
    });
    ```
  </Tab>
</Tabs>

<Warning>
  Omitting `eligibleApproverRoles` uses the default approver roles, `owner` and `admin`. An empty `eligibleApproverRoles` array does not allow every member. A positive `requiredApprovals` value with no eligible approvers is rejected as unsatisfiable.
</Warning>

### Requirement fields

| Field | Type | Description |
| - | - | - |
| <code className="whitespace-nowrap">id</code> | `string` | A stable ID for the requirement within the action. |
| <code className="whitespace-nowrap">schemaVersion</code> | `number` | The requirement schema version. The current value is `1`. |
| <code className="whitespace-nowrap">requiredApprovals</code> | `number` | The number of approvals required. The initiator never counts. |
| <code className="whitespace-nowrap">eligibleApproverRoles</code> | `string[]` | The roles that may approve. Omit this field to use `owner` and `admin`. |
| <code className="whitespace-nowrap">mandatoryApproverRoles</code> | `string[]` | The roles that must be represented among the approvers. A member can satisfy a mandatory role without matching `eligibleApproverRoles`. |
| <code className="whitespace-nowrap">eligibleInitiatorRoles</code> | `string[]` | The roles that may propose the action. Omit this field to use the action's default capability. |
| <code className="whitespace-nowrap">approverCapabilities</code> | `string[]` | Additional approver constraints. The supported value is `signerOnTargetWallet`, which limits approvals to signers on the wallet affected by the change. |
| <code className="whitespace-nowrap">completeWithinSeconds</code> | `number` | The time from the initiator's signature until the proposal must be approved and executed. The default and maximum are 30 days (`2592000` seconds). |
| <code className="whitespace-nowrap">when</code> | `object` | Conditions that limit when the requirement applies. |

### Scope a requirement with `when`

Omit `when` to apply a requirement to every change for that action. Set it to restrict the requirement to matching changes.

```javascript theme={"system"}
const requirement = {
  id: 'wallet-changes-need-review',
  schemaVersion: 1,
  requiredApprovals: 1,
  eligibleApproverRoles: ['admin'],
  when: { walletIds: ['wlt_treasury'] },
};
```

Each condition applies to these actions:

* `initiatorRoles` and `initiatorUserIds` apply to every governed action.
* `targetUserIds` applies to `addMember`, `removeMember`, `updateMemberRole`, `transferOwnership`, `addSignerToWallet`, and `removeSignerFromWallet`.
* `walletIds` applies to `addWallet`, `linkWallet`, `removeWallet`, `addSignerToWallet`, and `removeSignerFromWallet`.
* `role` matches the role granted by `addMember`. For `removeMember`, `updateMemberRole`, `transferOwnership`, `addSignerToWallet`, and `removeSignerFromWallet`, it matches the target member's current role.
* `newRole` applies only to `updateMemberRole`.

Fields in the same `when` object use AND logic. A requirement does not match when its action does not contain the field it needs.

`eligibleApproverRoles` and `mandatoryApproverRoles` can contain a built-in role (`owner`, `admin`, `viewer`) or a custom role created with `defineBusinessAccountRole`. For the full list of governed actions, see [Governance](/docs/business-accounts/governance#governed-actions).

## Handle `BusinessAccountIntentRequiredError`

<Note>
  Business Account mutation helpers construct and sign intents automatically when the request contains enough information. If a mutation throws `BusinessAccountIntentRequiredError`, sign `error.intent` and retry the same mutation with `signedIntent`.
</Note>

A mutation throws `BusinessAccountIntentRequiredError` when the SDK cannot construct the exact intent locally or the server rejects a locally constructed intent. This occurs when you add a member by email or another identifier because the server must resolve or create the target user first.

Sign the returned intent without changing it. Retry the same mutation with the resulting `signedIntent`.

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

    try {
      await addBusinessAccountMember({
        businessAccountId: 'ba_123',
        targetIdentity: {
          identifier: 'dana@acme.com',
          identifierType: 'email',
        },
        role: 'admin',
      });
    } catch (error) {
      if (!(error instanceof BusinessAccountIntentRequiredError)) {
        throw error;
      }

      const signedIntent = await signBusinessAccountIntent({
        intent: error.intent,
      });
      const result = await addBusinessAccountMember({
        businessAccountId: 'ba_123',
        targetIdentity: {
          identifier: 'dana@acme.com',
          identifierType: 'email',
        },
        role: 'admin',
        signedIntent,
      });
      console.log(result);
    }
    ```
  </Tab>
</Tabs>

The retry returns the completed account change or a proposal that still needs approvals. When a proposal is satisfied and the account does not execute it automatically, call `executeBusinessAccountProposal`. Do not rebuild or modify the signed intent.
