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

# Quorum policies

> Require approvals before a Business Account wallet can sign a message or transaction in Flutter.

<Note>
  Business Accounts are in **early access**. Signing quorum currently covers message signing on all supported chains and transaction signing on EVM. Solana and Bitcoin transaction support is coming soon. For the quorum model itself, the approval lifecycle, and the requirement fields, see [Approvals](/docs/business-accounts/approvals#approvals-for-signing).
</Note>

A signing quorum is a policy rule that turns a signing request on a Business Account wallet into a proposal other members must approve before the signature is produced. Governance requirements apply to account changes instead of signing; see [Members and roles](/docs/flutter/business-accounts/members-and-roles) for actions such as adding a member or transferring ownership.

`approvalRequirements` is a `PolicyRules` field. It composes with other rules in the same policy layer and uses the scope of that layer: set it on the account layer with `PolicyScope.account` to govern every wallet the Business Account owns.

The policy layer can further restrict the applicable chain and chain IDs.

## Create a transaction quorum rule

The following rule requires one approval from a Business Account member with the `admin` role for native-token transfers of at least 1 ETH on Ethereum mainnet.

```dart theme={"system"}
import 'package:dynamic_sdk/dynamic_sdk.dart';

final sdk = DynamicSDK.instance;

await sdk.policies.create(
  chain: WaasChain.evm,
  chainIds: [1],
  scope: PolicyScope.account(businessAccountId: 'ba_123'),
  rules: const PolicyRules(
    approvalRequirements: [
      PolicyApprovalRequirement(
        v: 1,
        id: 'large-native-transfer',
        requiredApprovals: 1,
        eligibleApproverRoles: ['admin'],
        when: PolicyApprovalRequirementWhen(
          minValue: PolicyApprovalRequirementMinValue(
            amount: '1000000000000000000',
            asset: '',
          ),
        ),
      ),
    ],
  ),
);
```

Use a built-in role such as `admin`, or a custom Business Account role. A value such as `anyAdmin` works only when the Business Account defines a custom role with that exact name.

<Warning>
  Set `eligibleApproverRoles` explicitly. If you omit it, any verified approver role can satisfy the requirement. The initiator never counts toward `requiredApprovals`.
</Warning>

### Requirement fields

| Field | Type | Description |
| - | - | - |
| <code className="whitespace-nowrap">v</code> | `int` | The requirement schema version. |
| <code className="whitespace-nowrap">id</code> | `String` | A stable ID for the requirement within the policy layer. |
| <code className="whitespace-nowrap">requiredApprovals</code> | `int` | The number of approvals required. The initiator never counts. |
| <code className="whitespace-nowrap">eligibleApproverRoles</code> | `List<String>?` | The Business Account roles whose members may approve. Set this field to restrict approver eligibility. |
| <code className="whitespace-nowrap">mandatoryApproverRoles</code> | `List<String>?` | Business Account roles that must each be represented among the approvals. |
| <code className="whitespace-nowrap">completeWithinSeconds</code> | `int?` | The time for a collected-approval proposal to remain valid. The default and maximum are 30 days (`2592000` seconds). |
| <code className="whitespace-nowrap">when</code> | `PolicyApprovalRequirementWhen?` | Conditions that limit when the requirement applies. Omit it for an unconditional requirement. |

`sdk.policies.get` returns `approvalRequirements` with the same authoring shape. Approval progress is reported during signing. It is not part of the policy definition.

## Limit a rule by transaction value

Use `when.minValue` to apply the requirement at or above a transaction value. Conditions in the same `when` object are combined with AND logic.

* `amount` is an integer string in the asset's smallest unit.
* `asset: ''` selects the chain's native token.
* An asset address selects that token.

<Warning>
  Do not use `when.initiatorRole` or `when.actionTypes` to narrow a production rule. These fields are accepted but are not resolved during evaluation. A requirement that sets either field applies more broadly instead of being limited by that field.
</Warning>

## The signing intent

Every quorum-gated signing request produces a signing intent: a signed, complete description of the operation that approvers review and that the resume step executes. The SDK prepares and signs it automatically when you initiate signing, and it is stored on the proposal as `BusinessAccountProposal.intent` so approvers see the same object the initiator signed.

`intent` is a raw map. For signing proposals it is signed verbatim, so pass it back unmodified: re-serializing it can change the bytes and invalidate the signature.

## Complete a quorum-gated signing operation

The signing-quorum flow has three stages:

1. The initiator attempts to sign. The SDK prepares and signs an operation-bound intent automatically.
2. Other eligible Business Account members review and approve the resulting proposal.
3. After every requirement is satisfied, the initiator resumes the proposal to produce the signature.

### 1. Start signing

Use a Business Account wallet with its standard signing methods, such as `signMessage` or `signRawMessage`. When a matching quorum rule requires approval, the call throws a `SigningConsentRequiredException`. Save its `proposalId` so you can find the proposal later.

```dart theme={"system"}
import 'package:dynamic_sdk/dynamic_sdk.dart';

final sdk = DynamicSDK.instance;

try {
  final signature = await sdk.wallets.signMessage(
    wallet: wallet,
    message: 'Hello from signing quorum',
  );
} on SigningConsentRequiredException catch (error) {
  print('Proposal awaiting approval: ${error.context.proposalId}');
}
```

The exception's `context.reason` tells you what consent is still missing:

* `SigningConsentRequiredReason.approvalsRequired`: a proposal opened and needs other members' approvals. The context carries `proposalId` and `approvalRequirements`.
* `SigningConsentRequiredReason.intentRequired`: the operation's intent still needs your session-key signature. This is rare because the SDK signs the intent automatically, but it can occur when the local client cannot produce the signature.

For `intentRequired`, sign the returned `context.intent` with `signSigningQuorumIntent` and retry through `prepareSigningQuorumApproval`, passing the signed intent as `approvalQuorum`. Pass the operation's stored payload as `preparedPayload` for message, typed data, and raw signing so the intent is checked against your request before it is signed.

```dart theme={"system"}
} on SigningConsentRequiredException catch (error) {
  final context = error.context;
  if (context.reason != SigningConsentRequiredReason.intentRequired) {
    rethrow;
  }

  final signed = await sdk.businessAccounts.signSigningQuorumIntent(
    intent: context.intent!,
    preparedPayload: context.intent!['operation']['payload'] as String,
    request: {
      'kind': 'message',
      'message': 'Hello from signing quorum',
      'accountAddress': wallet.address,
      'businessAccountId': businessAccountId,
      'chainName': 'EVM',
      'initiatorUserId': context.intent!['initiatorUserId'],
    },
  );

  await sdk.businessAccounts.prepareSigningQuorumApproval(
    walletId: wallet.id!,
    chainName: 'EVM',
    operation: {'kind': 'message', 'message': 'Hello from signing quorum'},
    approvalQuorum: signed.toJson(),
  );
}
```

### 2. Approve the proposal

An eligible Business Account member other than the initiator approves the proposal from their own authenticated session.

```dart theme={"system"}
final proposals =
    await sdk.businessAccounts.listProposalsAwaitingMyApproval();
final proposal = proposals.where((p) => p.id == proposalId).firstOrNull;

if (proposal == null) {
  throw StateError('Proposal is not awaiting this member\'s approval.');
}

final updated = await sdk.businessAccounts.approveProposal(
  businessAccountId: proposal.businessAccountId,
  proposalId: proposal.id,
);

final ready =
    updated.approvalRequirements?.every((r) => r.satisfied) ?? false;
```

The `satisfied` value is authoritative. Do not infer readiness from `approved >= required` because a requirement can also require specific roles.

An approver can inspect the operation before approving it: the signed intent on `proposal.intent` describes the message, typed data, or transaction the quorum is consenting to.

Members can also `vetoProposal` to reject a proposal, `withdrawProposalApproval` to retract a previously recorded approval, and `listProposals` to read every proposal on a Business Account:

```dart theme={"system"}
await sdk.businessAccounts.vetoProposal(
  businessAccountId: proposal.businessAccountId,
  proposalId: proposal.id,
);

await sdk.businessAccounts.withdrawProposalApproval(
  businessAccountId: proposal.businessAccountId,
  proposalId: proposal.id,
);
```

### 3. Resume the approved operation

Read the pending proposal again, verify that every requirement is satisfied, and resume it with the same Business Account wallet. `resumeSigningProposal` consumes the proposal and returns the signature. For an EVM transaction intent the signature is the serialized signed transaction, which you then broadcast yourself.

```dart theme={"system"}
final proposals = await sdk.businessAccounts.listProposals(
  businessAccountId: 'ba_123',
);
final proposal = proposals.where((p) => p.id == proposalId).firstOrNull;

if (proposal == null ||
    !(proposal.approvalRequirements?.every((r) => r.satisfied) ?? false)) {
  throw StateError('The signing proposal is not ready.');
}

final signature = await sdk.businessAccounts.resumeSigningProposal(
  businessAccountId: proposal.businessAccountId,
  proposalId: proposal.id,
  walletId: wallet.id,
);
```

`resumeSigningProposal` verifies that the proposal is pending and fully approved and that the selected wallet belongs to the proposal's Business Account and matches the intent address.

<Warning>
  Review the proposal's stored operation before approving it, and test the complete initiation, approval, resume, and broadcast flow in a non-production environment before enabling a quorum rule in production.
</Warning>

Changes to approver roles, thresholds, or deadlines apply to subsequent evaluations. Start a new signing request after changing a quorum rule so the request is evaluated against the current policy.

## Related

<CardGroup cols={2}>
  <Card title="Signing" icon="signature" href="/docs/flutter/business-accounts/signing">
    Sign with a Business Account wallet.
  </Card>

  <Card title="Approvals" icon="check-double" href="/docs/business-accounts/approvals">
    The quorum model and approval lifecycle.
  </Card>
</CardGroup>


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