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

# Business Accounts Quickstart

> Go from a signed-in user to a quorum-approved signature on a business-account wallet, with a second signer and an admin approval along the way.

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

This quickstart runs one end-to-end path in a React app: create a business account, give it an EVM wallet, add a teammate as a second signer and an admin member (which exercises step-up authentication and the intent-signing retry), attach a quorum rule, then sign a message through the propose, approve, and resume flow. Each step links to the full guide for that operation.

## Prerequisites

* A Dynamic client initialized with your environment ID (see [Creating a Dynamic Client](/docs/javascript/reference/client/create-dynamic-client) and [Initializing the Dynamic Client](/docs/javascript/reference/client/initialize-dynamic-client))
* An authenticated user, because every business account call acts on behalf of the signed-in user
* Business Accounts enabled for your environment (early access)
* `@dynamic-labs-sdk/react-hooks` installed and the app wrapped in `DynamicProvider`, as in the [React quickstart](/docs/javascript/reference/react-quickstart)

<Steps>
  <Step title="Create the business account">
    `useCreateBusinessAccount` returns a mutation hook; `mutateAsync` resolves to the new account, whose `id` you need for every later call. The signed-in user becomes the owner.

    ```tsx theme={"system"}
    import { useCreateBusinessAccount } from '@dynamic-labs-sdk/react-hooks';

    const { mutateAsync: createBusinessAccount } = useCreateBusinessAccount();

    const account = await createBusinessAccount({ name: 'Acme Treasury' });
    // account.id → the businessAccountId for all later steps
    ```

    Expected outcome: `account.id` is set, and the caller is the account's owner.
  </Step>

  <Step title="Create a wallet for the account">
    `createWalletForBusinessAccount` mints an embedded wallet owned by the account and seats the caller as its first signer. Use the `useCreateWalletForBusinessAccount` hook.

    ```tsx theme={"system"}
    import { useCreateWalletForBusinessAccount } from '@dynamic-labs-sdk/react-hooks';

    const { mutateAsync: createWalletForBusinessAccount } =
      useCreateWalletForBusinessAccount();

    await createWalletForBusinessAccount({
      businessAccountId: account.id,
      chain: 'EVM',
    });
    ```

    Expected outcome: the account owns an EVM wallet, and the caller can already sign with it.
  </Step>

  <Step title="Add a second signer">
    Adding a signer exercises three SDK behaviors in one call:

    1. **Step-up authentication.** Adding a signer requires an elevated token scoped to `business_account:signer:add` (see [Step-Up Authentication](/docs/business-accounts/step-up-auth) for the model). Check first with `checkStepUpAuth`, then verify with `authenticatePasskeyMFA` (or another MFA method) passing `requestedScopes`.
    2. **Intent signing.** When the SDK cannot construct the intent locally, for example when the target is identified by email, `addBusinessAccountSigner` throws `BusinessAccountIntentRequiredError`. Sign `error.intent` with `signBusinessAccountIntent` and retry with the returned `intent` and `intentSignature`.
    3. **Governance.** When the account requires approvals for signer changes, the call returns a pending action instead of a share set. Narrow it with `isBusinessAccountActionRequired` and surface the proposal for approval.

    ```tsx theme={"system"}
    import {
      authenticatePasskeyMFA,
      checkStepUpAuth,
      TokenScope,
    } from '@dynamic-labs-sdk/client';
    import {
      BusinessAccountIntentRequiredError,
      isBusinessAccountActionRequired,
      signBusinessAccountIntent,
    } from '@dynamic-labs-sdk/client/waas';
    import {
      useAddBusinessAccountSigner,
      useGetWalletAccounts,
    } from '@dynamic-labs-sdk/react-hooks';

    const { data: walletAccounts = [] } = useGetWalletAccounts();
    const { mutateAsync: addBusinessAccountSigner } =
      useAddBusinessAccountSigner();

    // The wallet the caller signs with, from the wallet created in step 2.
    const walletAccount = walletAccounts[0];

    const scope = TokenScope.BusinessAccountsigneradd;
    const { isRequired } = await checkStepUpAuth({ scope });
    if (isRequired) {
      await authenticatePasskeyMFA({ requestedScopes: [scope] });
    }

    const params = {
      businessAccountId: account.id,
      walletAccount,
      signerType: 'endUser' as const, // only 'endUser' is supported today
      targetIdentity: {
        identifier: 'teammate@acme.com',
        identifierType: 'email' as const,
      },
    };

    let result;
    try {
      result = await addBusinessAccountSigner(params);
    } catch (error) {
      if (!(error instanceof BusinessAccountIntentRequiredError)) {
        throw error;
      }
      const { intent, intentSignature } = await signBusinessAccountIntent({
        intent: error.intent,
      });
      result = await addBusinessAccountSigner({
        ...params,
        intent,
        intentSignature,
      });
    }

    if (isBusinessAccountActionRequired(result)) {
      // Quorum not met: show the proposal for approval before the reshare runs.
      console.log('Approval required', result.proposalId);
    } else {
      // result.shareSetId → use it to set an initial policy for the signer.
      console.log('Signer added', result.shareSetId);
    }
    ```

    Expected outcome: the teammate's email resolves to a user, the reshare mints their key share, and the call resolves to `{ shareSetId }` (or a pending proposal when governance applies). See [Manage signers](/docs/javascript/reference/business-accounts/manage-signers) for `password` and `targetSignerPassword`, which are required when your environment mandates wallet passwords.
  </Step>

  <Step title="Give the teammate an admin role">
    A signer can sign, but only a **member** can approve proposals or administer the account. Add the same teammate as an `admin` member so they can approve the quorum in the next steps. This is the same pattern as adding a signer, scoped to `business_account:member:add` instead.

    ```tsx theme={"system"}
    import {
      authenticatePasskeyMFA,
      checkStepUpAuth,
      TokenScope,
    } from '@dynamic-labs-sdk/client';
    import {
      BusinessAccountIntentRequiredError,
      signBusinessAccountIntent,
    } from '@dynamic-labs-sdk/client/waas';
    import { useAddBusinessAccountMember } from '@dynamic-labs-sdk/react-hooks';

    const { mutateAsync: addBusinessAccountMember } =
      useAddBusinessAccountMember();

    const scope = TokenScope.BusinessAccountmemberadd;
    const { isRequired } = await checkStepUpAuth({ scope });
    if (isRequired) {
      await authenticatePasskeyMFA({ requestedScopes: [scope] });
    }

    const params = {
      businessAccountId: account.id,
      targetIdentity: {
        identifier: 'teammate@acme.com',
        identifierType: 'email' as const,
      },
      role: 'admin' as const,
    };

    try {
      await addBusinessAccountMember(params);
    } catch (error) {
      if (!(error instanceof BusinessAccountIntentRequiredError)) {
        throw error;
      }
      // Same intent-signing retry as adding a signer, but the parameter is
      // named `signedIntent` here.
      const signedIntent = await signBusinessAccountIntent({
        intent: error.intent,
      });
      await addBusinessAccountMember({ ...params, signedIntent });
    }
    ```

    Expected outcome: the teammate is now an `admin` member of the account and a signer on its wallet. Members and signers are independent, so both grants are explicit.
  </Step>

  <Step title="Require an approval before signing">
    `createPolicy` attaches rules to the account. An `approvalRequirements` rule turns signing into a propose, approve, and resume flow. This rule requires one approval from an `admin` member for every signing operation on the account's EVM wallets.

    ```tsx theme={"system"}
    import { WaasChainEnum } from '@dynamic-labs-sdk/client';
    import { useCreatePolicy } from '@dynamic-labs-sdk/react-hooks';

    const { mutateAsync: createPolicy } = useCreatePolicy();

    await createPolicy({
      scope: { businessAccountId: account.id },
      chain: WaasChainEnum.Evm,
      rules: {
        approvalRequirements: [
          {
            v: 1,
            id: 'sign-with-approval',
            eligibleApproverRole: 'admin',
            requiredApprovals: 1,
          },
        ],
      },
    });
    ```

    Expected outcome: the account-layer policy now requires one `admin` approval before the wallet signs. Set `eligibleApproverRole` explicitly; omitting it lets any verified approver role satisfy the requirement, and the initiator never counts toward `requiredApprovals`.
  </Step>

  <Step title="Sign a message through the quorum">
    With the quorum rule active, `signMessage` rejects with `SigningConsentRequiredError` instead of returning a signature: the SDK signs the operation's intent automatically and opens a proposal carrying that intent.

    ```tsx theme={"system"}
    import { signMessage } from '@dynamic-labs-sdk/client';
    import { isSigningConsentRequiredError } from '@dynamic-labs-sdk/client/waas';
    import { useGetWalletAccounts } from '@dynamic-labs-sdk/react-hooks';

    const { data: walletAccounts = [] } = useGetWalletAccounts();
    const walletAccount = walletAccounts[0];

    let proposalId: string | undefined;
    try {
      const { signature } = await signMessage({
        walletAccount,
        message: 'gm from Acme Treasury',
      });
    } catch (error) {
      if (!isSigningConsentRequiredError(error)) {
        throw error;
      }
      // APPROVALS_REQUIRED: a proposal opened and needs another member.
      proposalId = error.proposalId;
    }
    ```

    The teammate then approves from their own session, reading the proposals awaiting their approval.

    ```tsx theme={"system"}
    import {
      useApproveBusinessAccountProposal,
      useListBusinessAccountProposalsAwaitingMyApproval,
    } from '@dynamic-labs-sdk/react-hooks';

    const { data: awaiting = [] } =
      useListBusinessAccountProposalsAwaitingMyApproval();
    const { mutateAsync: approve } = useApproveBusinessAccountProposal();

    const proposal = awaiting.find(({ id }) => id === proposalId);
    await approve({
      businessAccountId: proposal.businessAccountId,
      proposal,
    });
    ```

    Back in the initiator's session, `resumeSigningProposal` consumes the satisfied proposal and returns the signature.

    ```tsx theme={"system"}
    import { useListBusinessAccountProposals } from '@dynamic-labs-sdk/react-hooks';

    const { data: pending = [] } = useListBusinessAccountProposals({
      businessAccountId: account.id,
      status: 'pending',
    });

    const proposal = pending.find(({ id }) => id === proposalId);
    ```

    ```tsx theme={"system"}
    import { useResumeSigningProposal } from '@dynamic-labs-sdk/react-hooks';

    const { mutateAsync: resume } = useResumeSigningProposal();

    const { signature } = await resume({ proposal, walletAccount });
    ```

    Expected outcome: `signature` is a hex signature from the business-account wallet, produced only after the admin approved the proposal. For the full quorum flow, including intent inspection and transaction signing, see [Quorum policies](/docs/javascript/reference/business-accounts/policies/quorum-policies).
  </Step>
</Steps>

## What just happened

You created a business account with yourself as owner, gave it an EVM wallet on which you are the first signer, elevated your session for the `business_account:signer:add` and `business_account:member:add` scopes, added a teammate as a second signer and an admin member (signing the server-returned intents when the target was identified by email), attached a quorum rule requiring an admin approval to sign, and produced a signature through the propose, approve, and resume flow. The account, not any single user, owns the wallet and its signing rights.

## Next steps

<CardGroup cols={2}>
  <Card title="Members & roles" icon="users" href="/docs/javascript/reference/business-accounts/members-and-roles">
    Give the teammate admin rights, or invite viewers.
  </Card>

  <Card title="Governance" icon="landmark" href="/docs/javascript/reference/business-accounts/governance">
    Require approvals for signer, member, and wallet changes.
  </Card>

  <Card title="Quorum policies" icon="users-between-lines" href="/docs/javascript/reference/business-accounts/policies/quorum-policies">
    The full propose, approve, and resume signing flow, including transactions.
  </Card>

  <Card title="Policies" icon="shield-check" href="/docs/javascript/reference/business-accounts/policies/overview">
    Constrain what signers and wallets may do.
  </Card>

  <Card title="Step-up auth" icon="key" href="/docs/javascript/reference/business-accounts/step-up-auth">
    Every scope that requires an elevated token, and every way to get one.
  </Card>
</CardGroup>
