Skip to main content
Action-Based MFA requires users to verify their identity for sensitive actions like transactions. By default, we only require action-based MFA once the user already has a MFA method registered.

Dashboard Setup

Configure Action-Based MFA in the dashboard before implementing in your application. See End-User MFA Configuration - Action-Based MFA for dashboard setup instructions.

Events that trigger Action-Based MFA

  • Waas Export - When exporting a private key on an MPC wallet.
  • Waas Refresh - When a wallet is delegated, or when a user claims a pregenerated MPC wallet for the first time.
  • WaaS Sign - When any signature is performed i.e. a message, a transaction, typed data, authorization, etc.
  • WaaS Reshare - When a wallet is approved or revoked from delegated access and the user next signs in.
Use the useStepUpAuthentication hook with requestedScopes to receive elevated access tokens instead of legacy MFA tokens. This is the recommended approach and will be required in the next major version.
import { useStepUpAuthentication } from "@dynamic-labs/sdk-react-core";
import { useIsMfaRequiredForAction } from "@dynamic-labs/sdk-react-core";
import { MFAAction, TokenScope } from '@dynamic-labs/sdk-api-core';
import { getElevatedAccessToken } from '@dynamic-labs-sdk/client';

export function EnsureTotpStepUp() {
  const { verifyTotpMfa, state } = useStepUpAuthentication();
  const isMfaRequiredForAction = useIsMfaRequiredForAction();

  const ensureElevatedAccess = async (code: string) => {
    const requires = await isMfaRequiredForAction({
      mfaAction: MFAAction.WalletWaasExport,
    });

    if (!requires) return;

    const existing = getElevatedAccessToken({ scope: 'wallet:export' });
    if (existing) return;

    await verifyTotpMfa({
      code,
      requestedScopes: [TokenScope.Walletexport],
    });

    // Elevated token is now stored in SDK state
    // Perform the action that requires elevated access
  };

  return null;
}
For a complete guide on step-up authentication including all verification methods, see Step-Up Authentication.

Using MFA tokens (deprecated)

createMfaToken is deprecated and will be removed in the next major version. Use requestedScopes with useStepUpAuthentication instead. See migration guide.
import { useMfa, useIsMfaRequiredForAction } from "@dynamic-labs/sdk-react-core";
import { useGetMfaToken } from "@dynamic-labs/sdk-react-core";
import { MFAAction } from '@dynamic-labs/sdk-api-core';

export function EnsureTotpMfaToken() {
  const { authenticateDevice } = useMfa();
  const { getMfaToken } = useGetMfaToken();
  const isMfaRequiredForAction = useIsMfaRequiredForAction();

  const ensureToken = async (code?: string) => {
    const requires = await isMfaRequiredForAction({
      mfaAction: MFAAction.WalletWaasExport,
    });
    if (!requires) return;

    const existing = await getMfaToken();
    if (existing) return existing;

    if (!code) throw new Error("OTP code required");
    const token = await authenticateDevice({
      code,
      createMfaToken: { singleUse: true },
    });
    return token;
  };

  return null;
}

Dynamic UI Implementation

The Dynamic UI is method-agnostic. It automatically prompts with whichever MFA method(s) you have enabled (TOTP and/or Passkeys). Pass requestedScopes to usePromptMfaAuth to receive an elevated access token through the Dynamic UI:
import { usePromptMfaAuth, useIsMfaRequiredForAction } from "@dynamic-labs/sdk-react-core";
import { MFAAction, TokenScope } from '@dynamic-labs/sdk-api-core';

const isMfaRequiredForAction = useIsMfaRequiredForAction();
const promptMfaAuth = usePromptMfaAuth();

const isMfaRequired = await isMfaRequiredForAction({
  mfaAction: MFAAction.WalletWaasExport,
});

if (isMfaRequired) {
  await promptMfaAuth({
    requestedScopes: [TokenScope.Walletexport],
  });
}

// Elevated token is now stored — the SDK attaches it automatically
await primaryWallet.exportWaasPrivateKey();

With MFA token (deprecated)

import { usePromptMfaAuth, useIsMfaRequiredForAction } from "@dynamic-labs/sdk-react-core";
import { MFAAction } from '@dynamic-labs/sdk-api-core';

const isMfaRequiredForAction = useIsMfaRequiredForAction();
const promptMfaAuth = usePromptMfaAuth();

const isMfaRequired = await isMfaRequiredForAction({
  mfaAction: MFAAction.WalletWaasExport,
});

if (isMfaRequired) {
  await promptMfaAuth({ createMfaToken: true });
}

await primaryWallet.exportWaasPrivateKey();