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

# Migrating from action-based MFA to step-up authentication

<Info>
  This guide is only relevant if you previously used **action-based MFA** to protect sensitive wallet operations. If you didn't, you can skip it.
</Info>

If you previously used **action-based MFA** to protect sensitive wallet operations, step-up authentication replaces and extends that functionality. The core concept is the same — require users to re-verify before sensitive actions — but step-up auth adds **scoped elevated access tokens**, **re-authentication methods** for non-MFA users, and **automatic token handling** by the SDK.

## What changed

|                    | Action-based MFA (before)                  | Step-up authentication (after)                                                                    |
| ------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| **Hook**           | `useMfa` / `useAuthenticatePasskeyMFA`     | `useStepUpAuthentication`                                                                         |
| **Token type**     | Unscoped MFA token (`createMfaToken`)      | Scoped elevated access token (`requestedScopes`)                                                  |
| **Token handling** | SDK-managed MFA token                      | SDK-managed elevated access token (scoped JWT)                                                    |
| **Who can verify** | Only users with MFA methods (TOTP/Passkey) | All users — MFA methods, email OTP, SMS OTP, wallet signature, or social OAuth                    |
| **Scope**          | None — token accepted for any action       | Bound to specific scopes (`wallet:export`, `wallet:sign`, `credential:link`, `credential:unlink`) |
| **Expiration**     | Single-use, no time limit                  | Time-limited (5 min single-use, 10 min multi-use)                                                 |

## Dashboard changes

No dashboard changes are required — your existing action-based MFA event
configuration (WaaS Export, WaaS Refresh, WaaS Sign, WaaS Reshare) carries over.

<Warning>
  Make sure your code is fully updated before accepting the minimum API version at [Dashboard > Developers > API & SDK Keys](https://app.dynamic.xyz/dashboard/developer/api). Once set to `2026_04_01`, the backend enforces step-up authentication immediately.
</Warning>

## AI-assisted upgrade prompts

<Tip>
  These prompts are designed for AI coding agents (Cursor, Claude Code, Copilot). Always review generated code before committing — agents can misread your project structure.

  Install the Dynamic MCP below so your agent can reference live documentation automatically. If you don't have it installed, the prompts fall back to `https://www.dynamic.xyz/docs/llms.txt`.
</Tip>

<Tabs>
  <Tab title="Cursor">
    [Click here to add the MCP to Cursor.](https://cursor.com/en/install-mcp?name=dynamic\&config=eyJ1cmwiOiJodHRwczovL3d3dy5keW5hbWljLnh5ei9kb2NzL21jcCJ9)
  </Tab>

  <Tab title="Claude Code">
    ```bash theme={"system"}
    claude mcp add --transport http dynamic https://www.dynamic.xyz/docs/mcp
    ```
  </Tab>

  <Tab title="Codex">
    ```bash theme={"system"}
    codex mcp add dynamic --url https://www.dynamic.xyz/docs/mcp
    ```
  </Tab>

  <Tab title="Other">
    If your editor or agent supports remote HTTP MCP, add a server that points at `https://www.dynamic.xyz/docs/mcp`. The exact steps depend on the product; use its MCP documentation for URL-based or HTTP transport setup.
  </Tab>
</Tabs>

Copy the prompt for your SDK and run it in your agent.

<Tabs>
  <Tab title="React">
    ```
    I'm migrating my Dynamic SDK headless integration from action-based MFA to step-up authentication as part of the 2026_04_01 API upgrade.

    If you have the Dynamic MCP installed, use it to reference the latest API.
    Otherwise reference: https://www.dynamic.xyz/docs/llms.txt

    In my codebase:
    1. Find all usages of `useMfa`, `useAuthenticatePasskeyMFA`, and `usePromptMfaAuth`
    2. Replace them with `useStepUpAuthentication` from `@dynamic-labs/sdk-react-core`
    3. Replace all `createMfaToken` options with `requestedScopes` using the
       appropriate `TokenScope` from `@dynamic-labs/sdk-api-core`
    4. Replace all `useIsMfaRequiredForAction` calls with `isStepUpRequired`
    5. Do not modify any files outside of the auth flow

    After making changes, summarize what was updated and flag anything that
    needs manual review.
    ```
  </Tab>

  <Tab title="JavaScript">
    ```
    I'm migrating my Dynamic SDK headless integration from action-based MFA
    to step-up authentication as part of the 2026_04_01 API upgrade.

    If you have the Dynamic MCP installed, use it to reference the latest API.
    Otherwise reference: https://www.dynamic.xyz/docs/llms.txt

    In my codebase:
    1. Find all usages of `isMfaRequiredForAction` from `@dynamic-labs-sdk/client`
    2. Replace them with `checkStepUpAuth`
    3. Replace all `createMfaToken` and `createMfaTokenOptions` with `requestedScopes`
       using the appropriate `TokenScope` from `@dynamic-labs/sdk-api-core`
       on both `authenticateTotpMfaDevice` and `authenticatePasskeyMFA`
    4. Do not modify any files outside of the auth flow

    After making changes, summarize what was updated and flag anything that
    needs manual review.
    ```
  </Tab>

  <Tab title="React Native">
    ```
    I'm migrating my Dynamic SDK headless integration from action-based MFA
    to step-up authentication as part of the 2026_04_01 API upgrade.

    If you have the Dynamic MCP installed, use it to reference the latest API.
    Otherwise reference: https://www.dynamic.xyz/docs/llms.txt

    In my codebase:
    1. Find all usages of `client.mfa.isRequiredForAction` and replace with
       `client.stepUpAuth.isStepUpRequired`
    2. Find all usages of `client.mfa.authenticateDevice` and replace with
       `client.stepUpAuth.verifyTotpMfa`
    3. Find all usages of `client.passkeys.authenticateMFA` and replace with
       `client.stepUpAuth.verifyPasskeyMfa`
    4. Find all usages of `client.ui.mfa.show` and replace with
       `client.stepUpAuth.promptStepUpAuth`
    5. Replace all `createMfaToken` options with `requestedScopes` using
       the appropriate `TokenScope`
    6. Do not modify any files outside of the auth flow

    After making changes, summarize what was updated and flag anything that
    needs manual review.
    ```
  </Tab>

  <Tab title="Flutter">
    ```
    I'm migrating my Dynamic SDK headless integration from action-based MFA
    to step-up authentication as part of the 2026_04_01 API upgrade.

    If you have the Dynamic MCP installed, use it to reference the latest API.
    Otherwise reference: https://www.dynamic.xyz/docs/llms.txt

    In my codebase:
    1. Find all usages of DynamicSDK.instance.mfa related to action-based MFA checks
    2. Replace them with DynamicSDK.instance.stepUpAuth.isStepUpRequired
    3. Replace verification calls with the appropriate method:
       - TOTP: DynamicSDK.instance.stepUpAuth.verifyTotpMfa
       - Passkey: DynamicSDK.instance.stepUpAuth.verifyPasskeyMfa
       - Prompt: DynamicSDK.instance.stepUpAuth.promptStepUpAuth
    4. Replace all scope arguments with the string equivalents:
       'wallet:export', 'wallet:sign', 'credential:link', 'credential:unlink'
    5. Do not modify any files outside of the auth flow

    After making changes, summarize what was updated and flag anything that
    needs manual review.
    ```
  </Tab>

  <Tab title="Kotlin">
    ```
    I'm migrating my Dynamic SDK headless integration from action-based MFA
    to step-up authentication as part of the 2026_04_01 API upgrade.

    If you have the Dynamic MCP installed, use it to reference the latest API.
    Otherwise reference: https://www.dynamic.xyz/docs/llms.txt

    In my codebase:
    1. Find all usages of action-based MFA checks in my Kotlin integration
    2. Replace them with sdk.stepUpAuth.isStepUpRequired(scope)
    3. Replace verification calls with the appropriate method:
       - TOTP: sdk.stepUpAuth.verifyTotpMfa(code, deviceId, requestedScopes)
       - Passkey: sdk.stepUpAuth.verifyPasskeyMfa(requestedScopes)
       - OTP: sdk.stepUpAuth.verifyOtp(verificationToken, requestedScopes)
       - Or let the SDK decide: sdk.stepUpAuth.promptStepUpAuth(requestedScopes)
    4. Replace all scope arguments with the appropriate string equivalents:
       'wallet:export', 'wallet:sign', 'credential:link', 'credential:unlink'
    5. Do not modify any files outside of the auth flow

    After making changes, summarize what was updated and flag anything that
    needs manual review.
    ```
  </Tab>

  <Tab title="Swift">
    ```
    I'm migrating my Dynamic SDK headless integration from action-based MFA
    to step-up authentication as part of the 2026_04_01 API upgrade.

    If you have the Dynamic MCP installed, use it to reference the latest API.
    Otherwise reference: https://www.dynamic.xyz/docs/llms.txt

    In my codebase:
    1. Find all usages of action-based MFA checks in my Swift integration
    2. Replace them with sdk.stepUpAuth.isStepUpRequired(scope:)
    3. Replace verification calls with the appropriate method:
       - TOTP: sdk.stepUpAuth.verifyTotpMfa(code:deviceId:requestedScopes:)
       - Passkey: sdk.stepUpAuth.verifyPasskeyMfa(requestedScopes:)
       - OTP: sdk.stepUpAuth.verifyOtp(verificationToken:requestedScopes:)
       - Or let the SDK decide: sdk.stepUpAuth.promptStepUpAuth(requestedScopes:)
    4. Replace all scope arguments with the appropriate string equivalents:
       'wallet:export', 'wallet:sign', 'credential:link', 'credential:unlink'
    5. Do not modify any files outside of the auth flow

    After making changes, summarize what was updated and flag anything that
    needs manual review.
    ```
  </Tab>
</Tabs>

## Code migration: before and after

<Note>
  The **After** examples include an `else` branch that falls through to legacy action-based MFA. It is only needed if some of your users already have legacy MFA enabled **and** you're deploying the updated SDK before the `2026_04_01` API version is confirmed. Once both the SDK and the minimum API version are upgraded, the transitional `else` branches can be removed since they are no longer needed.
</Note>

#### Using Dynamic's built-in UI

<Tabs>
  <Tab title="React">
    **Before** — `usePromptMfaAuth` with `createMfaToken`:

    ```tsx theme={"system"}
        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();
    ```

    **After** — `useStepUpAuthentication` with `requestedScopes`. The `else` branch is only needed if some of your users already have legacy MFA enabled — it handles the window when the frontend is deployed before the `2026_04_01` API version is confirmed. Remove it once both the SDK and the minimum API version are upgraded:

    ```tsx theme={"system"}
        import {
          useStepUpAuthentication,
          usePromptMfaAuth,
          useIsMfaRequiredForAction,
        } from '@dynamic-labs/sdk-react-core';
        import { TokenScope, MFAAction } from '@dynamic-labs/sdk-api-core';

        const { isStepUpRequired, promptStepUpAuth } = useStepUpAuthentication();
        const promptMfaAuth = usePromptMfaAuth();
        const isMfaRequiredForAction = useIsMfaRequiredForAction();

        if (await isStepUpRequired({ scope: TokenScope.Walletexport })) {
          await promptStepUpAuth({
            requestedScopes: [TokenScope.Walletexport],
          });
        } else {
          // Transitional: 2026_04_01 API version not yet confirmed.
          // Legacy MFA prompt for users with an existing passkey / TOTP / device.
          const isMfaRequired = await isMfaRequiredForAction({
            mfaAction: MFAAction.WalletWaasExport,
          });
          if (isMfaRequired) {
            await promptMfaAuth({ createMfaToken: true });
          }
        }

        // Token is stored — SDK attaches it automatically
        await primaryWallet.exportWaasPrivateKey();
    ```
  </Tab>

  <Tab title="React Native">
    **Before** — `client.ui.mfa.show` with `createMfaToken`:

    ```typescript theme={"system"}
        import { MFAAction } from '@dynamic-labs/sdk-api-core';

        const isMfaRequired = await client.mfa.isRequiredForAction({
          mfaAction: MFAAction.WalletWaasExport,
        });

        if (isMfaRequired) {
          await client.ui.mfa.show({ createMfaToken: true });
        }

        await primaryWallet.exportWaasPrivateKey();
    ```

    **After** — `client.stepUpAuth.promptStepUpAuth` with `requestedScopes`. The `else` branch is only needed if some of your users already have legacy MFA enabled — it handles the window when the frontend is deployed before the `2026_04_01` API version is confirmed. Remove it once both the SDK and the minimum API version are upgraded:

    ```typescript theme={"system"}
        import { TokenScope, MFAAction } from '@dynamic-labs/sdk-api-core';

        const isRequired = await client.stepUpAuth.isStepUpRequired({
          scope: TokenScope.Walletexport,
        });

        if (isRequired) {
          await client.stepUpAuth.promptStepUpAuth({
            requestedScopes: [TokenScope.Walletexport],
          });
        } else {
          // Transitional: 2026_04_01 API version not yet confirmed.
          // Legacy MFA prompt for users with an existing passkey / TOTP / device.
          const mfaRequired = await client.mfa.isRequiredForAction({
            mfaAction: MFAAction.WalletWaasExport,
          });
          if (mfaRequired) {
            await client.ui.mfa.show({ createMfaToken: true });
          }
        }

        // Token is stored — SDK attaches it automatically
        await primaryWallet.exportWaasPrivateKey();
    ```
  </Tab>

  <Tab title="Flutter (no built-in UI)">
    Flutter did not have a built-in action-based MFA UI. Use `promptStepUpAuth` to add step-up verification:

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

        final isRequired = await DynamicSDK.instance.stepUpAuth
            .isStepUpRequired('wallet:export');

        if (isRequired) {
          await DynamicSDK.instance.stepUpAuth.promptStepUpAuth(
            requestedScopes: ['wallet:export'],
          );
        }

        // Token is stored — SDK attaches it automatically
        await performExport();
    ```
  </Tab>
</Tabs>

#### Headless TOTP

<Tabs>
  <Tab title="React">
    **Before** — `useMfa` with `authenticateDevice` and `createMfaToken`:

    ```tsx theme={"system"}
        import { useMfa, useIsMfaRequiredForAction } from "@dynamic-labs/sdk-react-core";
        import { MFAAction } from '@dynamic-labs/sdk-api-core';

        const { authenticateDevice } = useMfa();
        const isMfaRequiredForAction = useIsMfaRequiredForAction();

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

        if (requires) {
          await authenticateDevice({
            code: totpCode,
            createMfaToken: { singleUse: true },
          });
        }
    ```

    **After** — `useStepUpAuthentication` with `verifyTotpMfa`. The `else` branch is only needed if some of your users already have legacy MFA enabled — it handles the window before the `2026_04_01` API version is confirmed. Remove it once both the SDK and the minimum API version are upgraded:

    ```tsx theme={"system"}
        import {
          useStepUpAuthentication,
          useMfa,
          useIsMfaRequiredForAction,
        } from '@dynamic-labs/sdk-react-core';
        import { TokenScope, MFAAction } from '@dynamic-labs/sdk-api-core';

        const { isStepUpRequired, verifyTotpMfa } = useStepUpAuthentication();
        const { authenticateDevice } = useMfa();
        const isMfaRequiredForAction = useIsMfaRequiredForAction();

        if (await isStepUpRequired({ scope: TokenScope.Walletexport })) {
          await verifyTotpMfa({
            code: totpCode,
            requestedScopes: [TokenScope.Walletexport],
          });
        } else {
          // Transitional: 2026_04_01 API version not yet confirmed.
          // Legacy TOTP verification for users with an existing MFA device.
          const requires = await isMfaRequiredForAction({
            mfaAction: MFAAction.WalletWaasExport,
          });
          if (requires) {
            await authenticateDevice({
              code: totpCode,
              createMfaToken: { singleUse: true },
            });
          }
        }
    ```
  </Tab>

  <Tab title="JavaScript">
    **Before** — `authenticateTotpMfaDevice` with `createMfaTokenOptions`:

    ```javascript theme={"system"}
        import { isMfaRequiredForAction, authenticateTotpMfaDevice, MFAAction } from '@dynamic-labs-sdk/client';

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

        if (required) {
          await authenticateTotpMfaDevice({
            code: '123456',
            createMfaTokenOptions: { singleUse: true },
          });
        }
    ```

    **After** — same function with `requestedScopes`. The `else` branch is only needed if some of your users already have legacy MFA enabled — it handles the window before the `2026_04_01` API version is confirmed. Remove it once both the SDK and the minimum API version are upgraded:

    ```javascript theme={"system"}
        import {
          checkStepUpAuth,
          authenticateTotpMfaDevice,
          isMfaRequiredForAction,
          MFAAction,
        } from '@dynamic-labs-sdk/client';
        import { TokenScope } from '@dynamic-labs/sdk-api-core';

        const { isRequired } = await checkStepUpAuth({ scope: TokenScope.Walletexport });

        if (isRequired) {
          await authenticateTotpMfaDevice({
            code: '123456',
            requestedScopes: [TokenScope.Walletexport],
          });
        } else {
          // Transitional: 2026_04_01 API version not yet confirmed.
          // Legacy TOTP verification for users with an existing MFA device.
          const required = await isMfaRequiredForAction({ mfaAction: MFAAction.WalletWaasExport });
          if (required) {
            await authenticateTotpMfaDevice({
              code: '123456',
              createMfaTokenOptions: { singleUse: true },
            });
          }
        }
    ```
  </Tab>

  <Tab title="React Native">
    **Before** — `client.mfa.authenticateDevice` with `createMfaToken`:

    ```typescript theme={"system"}
        import { MFAAction } from '@dynamic-labs/sdk-api-core';

        const requires = await client.mfa.isRequiredForAction({
          mfaAction: MFAAction.WalletWaasExport,
        });

        if (requires) {
          await client.mfa.authenticateDevice({
            code: totpCode,
            createMfaToken: { singleUse: true },
          });
        }
    ```

    **After** — `client.stepUpAuth.verifyTotpMfa` with `requestedScopes`. The `else` branch is only needed if some of your users already have legacy MFA enabled — it handles the window before the `2026_04_01` API version is confirmed. Remove it once both the SDK and the minimum API version are upgraded:

    ```typescript theme={"system"}
        import { TokenScope, MFAAction } from '@dynamic-labs/sdk-api-core';

        const isRequired = await client.stepUpAuth.isStepUpRequired({
          scope: TokenScope.Walletexport,
        });

        if (isRequired) {
          await client.stepUpAuth.verifyTotpMfa({
            code: totpCode,
            requestedScopes: [TokenScope.Walletexport],
          });
        } else {
          // Transitional: 2026_04_01 API version not yet confirmed.
          // Legacy TOTP verification for users with an existing MFA device.
          const requires = await client.mfa.isRequiredForAction({
            mfaAction: MFAAction.WalletWaasExport,
          });
          if (requires) {
            await client.mfa.authenticateDevice({
              code: totpCode,
              createMfaToken: { singleUse: true },
            });
          }
        }
    ```
  </Tab>

  <Tab title="Flutter">
    ```dart theme={"system"}
        import 'package:dynamic_sdk/dynamic_sdk.dart';

        final isRequired = await DynamicSDK.instance.stepUpAuth
            .isStepUpRequired('wallet:export');

        if (isRequired) {
          await DynamicSDK.instance.stepUpAuth.verifyTotpMfa(
            code: totpCode,
            requestedScopes: ['wallet:export'],
          );
        }
    ```
  </Tab>
</Tabs>

#### Headless Passkey

<Tabs>
  <Tab title="React">
    **Before** — `useAuthenticatePasskeyMFA` with `createMfaToken`:

    ```tsx theme={"system"}
        import {
          useAuthenticatePasskeyMFA,
          useGetPasskeys,
          useIsMfaRequiredForAction,
          useRegisterPasskey,
        } from "@dynamic-labs/sdk-react-core";
        import { MFAAction } from '@dynamic-labs/sdk-api-core';

        const authenticatePasskeyMFA = useAuthenticatePasskeyMFA();
        const getPasskeys = useGetPasskeys();
        const registerPasskey = useRegisterPasskey();
        const isMfaRequiredForAction = useIsMfaRequiredForAction();

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

        if (requires) {
          const passkeys = await getPasskeys();
          if (passkeys.length === 0) {
            await registerPasskey();
          }

          await authenticatePasskeyMFA({
            createMfaToken: { singleUse: false },
          });
        }
    ```

    **After** — `useStepUpAuthentication` with `verifyPasskeyMfa`. The `else` branch is only needed if some of your users already have legacy MFA enabled — it handles the window before the `2026_04_01` API version is confirmed. Remove it once both the SDK and the minimum API version are upgraded:

    ```tsx theme={"system"}
        import {
          useStepUpAuthentication,
          useAuthenticatePasskeyMFA,
          useGetPasskeys,
          useRegisterPasskey,
          useIsMfaRequiredForAction,
        } from '@dynamic-labs/sdk-react-core';
        import { TokenScope, MFAAction } from '@dynamic-labs/sdk-api-core';

        const { isStepUpRequired, verifyPasskeyMfa } = useStepUpAuthentication();
        const authenticatePasskeyMFA = useAuthenticatePasskeyMFA();
        const getPasskeys = useGetPasskeys();
        const registerPasskey = useRegisterPasskey();
        const isMfaRequiredForAction = useIsMfaRequiredForAction();

        if (await isStepUpRequired({ scope: TokenScope.Walletexport })) {
          await verifyPasskeyMfa({
            requestedScopes: [TokenScope.Walletexport],
          });
        } else {
          // Transitional: 2026_04_01 API version not yet confirmed.
          // Legacy passkey MFA for users with an existing registered passkey.
          const requires = await isMfaRequiredForAction({
            mfaAction: MFAAction.WalletWaasExport,
          });
          if (requires) {
            const passkeys = await getPasskeys();
            if (passkeys.length === 0) {
              await registerPasskey();
            }
            await authenticatePasskeyMFA({
              createMfaToken: { singleUse: false },
            });
          }
        }
    ```
  </Tab>

  <Tab title="JavaScript">
    **Before** — `authenticatePasskeyMFA` with `createMfaToken`:

    ```javascript theme={"system"}
        import { isMfaRequiredForAction, authenticatePasskeyMFA, MFAAction } from '@dynamic-labs-sdk/client';

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

        if (required) {
          await authenticatePasskeyMFA({
            createMfaToken: { singleUse: true },
          });
        }
    ```

    **After** — same function with `requestedScopes`. The `else` branch is only needed if some of your users already have legacy MFA enabled — it handles the window before the `2026_04_01` API version is confirmed. Remove it once both the SDK and the minimum API version are upgraded:

    ```javascript theme={"system"}
        import {
          checkStepUpAuth,
          authenticatePasskeyMFA,
          isMfaRequiredForAction,
          MFAAction,
        } from '@dynamic-labs-sdk/client';
        import { TokenScope } from '@dynamic-labs/sdk-api-core';

        const { isRequired } = await checkStepUpAuth({ scope: TokenScope.Walletexport });

        if (isRequired) {
          await authenticatePasskeyMFA({
            requestedScopes: [TokenScope.Walletexport],
          });
        } else {
          // Transitional: 2026_04_01 API version not yet confirmed.
          // Legacy passkey MFA for users with an existing registered passkey.
          const required = await isMfaRequiredForAction({ mfaAction: MFAAction.WalletWaasExport });
          if (required) {
            await authenticatePasskeyMFA({
              createMfaToken: { singleUse: true },
            });
          }
        }
    ```
  </Tab>

  <Tab title="React Native">
    **Before** — `client.passkeys.authenticateMFA` with `createMfaToken`:

    ```typescript theme={"system"}
        import { MFAAction } from '@dynamic-labs/sdk-api-core';

        const requires = await client.mfa.isRequiredForAction({
          mfaAction: MFAAction.WalletWaasExport,
        });

        if (requires) {
          const passkeys = await client.passkeys.get();
          if (passkeys.length === 0) {
            await client.passkeys.register();
          }

          await client.passkeys.authenticateMFA({
            createMfaToken: { singleUse: false },
          });
        }
    ```

    **After** — `client.stepUpAuth.verifyPasskeyMfa` with `requestedScopes`. The `else` branch is only needed if some of your users already have legacy MFA enabled — it handles the window before the `2026_04_01` API version is confirmed. Remove it once both the SDK and the minimum API version are upgraded:

    ```typescript theme={"system"}
        import { TokenScope, MFAAction } from '@dynamic-labs/sdk-api-core';

        const isRequired = await client.stepUpAuth.isStepUpRequired({
          scope: TokenScope.Walletexport,
        });

        if (isRequired) {
          await client.stepUpAuth.verifyPasskeyMfa({
            requestedScopes: [TokenScope.Walletexport],
          });
        } else {
          // Transitional: 2026_04_01 API version not yet confirmed.
          // Legacy passkey MFA for users with an existing registered passkey.
          const requires = await client.mfa.isRequiredForAction({
            mfaAction: MFAAction.WalletWaasExport,
          });
          if (requires) {
            const passkeys = await client.passkeys.get();
            if (passkeys.length === 0) {
              await client.passkeys.register();
            }
            await client.passkeys.authenticateMFA({
              createMfaToken: { singleUse: false },
            });
          }
        }
    ```
  </Tab>

  <Tab title="Flutter">
    ```dart theme={"system"}
        import 'package:dynamic_sdk/dynamic_sdk.dart';

        final isRequired = await DynamicSDK.instance.stepUpAuth
            .isStepUpRequired('wallet:export');

        if (isRequired) {
          await DynamicSDK.instance.stepUpAuth.verifyPasskeyMfa(
            requestedScopes: ['wallet:export'],
          );
        }
    ```
  </Tab>
</Tabs>

## Migration checklist

<Tabs>
  <Tab title="React">
    1. Upgrade to React SDK `4.76.0` or later
    2. Replace `useMfa` / `useAuthenticatePasskeyMFA` / `usePromptMfaAuth` with `useStepUpAuthentication`
    3. Replace `createMfaToken` with `requestedScopes` using the appropriate `TokenScope`
    4. Replace `useIsMfaRequiredForAction` with `isStepUpRequired`
    5. If you have existing legacy MFA users and you're deploying the SDK update before accepting the new API version, keep the transitional `else` branch shown in each **After** example so those users still get prompted
    6. Test the step-up flow end-to-end for each protected action
    7. Accept the minimum API version `2026_04_01` in your [dashboard](https://app.dynamic.xyz/dashboard/developer/api)
    8. Remove the transitional `else` branches

    For the full step-up authentication reference (all verification methods, scopes, token lifecycle), see [Step-up authentication](/react/authentication-methods/step-up-auth/overview).
  </Tab>

  <Tab title="JavaScript">
    1. Upgrade to JavaScript SDK `0.24.1` or later
    2. Replace `isMfaRequiredForAction` with `checkStepUpAuth`
    3. Replace `createMfaToken` / `createMfaTokenOptions` with `requestedScopes` on `authenticateTotpMfaDevice` and `authenticatePasskeyMFA`
    4. If you have existing legacy MFA users and you're deploying the SDK update before accepting the new API version, keep the transitional `else` branch shown in each **After** example so those users still get prompted
    5. Test the step-up flow end-to-end for each protected action
    6. Accept the minimum API version `2026_04_01` in your [dashboard](https://app.dynamic.xyz/dashboard/developer/api)
    7. Remove the transitional `else` branches

    For the full step-up authentication reference (all verification methods, scopes, token lifecycle), see [Step-up authentication](/javascript/authentication-methods/step-up-auth/overview).
  </Tab>

  <Tab title="React Native">
    1. Upgrade to React Native SDK `4.76.0` or later
    2. Replace `client.mfa.isRequiredForAction` with `client.stepUpAuth.isStepUpRequired`
    3. Replace `client.mfa.authenticateDevice` with `client.stepUpAuth.verifyTotpMfa`
    4. Replace `client.passkeys.authenticateMFA` with `client.stepUpAuth.verifyPasskeyMfa`
    5. Replace `client.ui.mfa.show` with `client.stepUpAuth.promptStepUpAuth`
    6. If you have existing legacy MFA users and you're deploying the SDK update before accepting the new API version, keep the transitional `else` branch shown in each **After** example so those users still get prompted
    7. Test the step-up flow end-to-end for each protected action
    8. Accept the minimum API version `2026_04_01` in your [dashboard](https://app.dynamic.xyz/dashboard/developer/api)
    9. Remove the transitional `else` branches

    For the full step-up authentication reference (all verification methods, scopes, token lifecycle), see [Step-up authentication](/react-native/authentication-methods/step-up-auth/overview).
  </Tab>

  <Tab title="Flutter">
    1. Upgrade to Flutter SDK `1.2.10` or later
    2. Implement step-up checks with `DynamicSDK.instance.stepUpAuth.isStepUpRequired`
    3. Add verification flows using `promptStepUpAuth`, `verifyTotpMfa`, or `verifyPasskeyMfa`
    4. Test the step-up flow end-to-end for each protected action
    5. Accept the minimum API version `2026_04_01` in your [dashboard](https://app.dynamic.xyz/dashboard/developer/api)

    For the full step-up authentication reference (all verification methods, scopes, token lifecycle), see [Step-up authentication](/flutter/authentication-methods/step-up-auth/overview).
  </Tab>
</Tabs>
