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.
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.Make sure your code is fully updated before accepting the minimum API version at Dashboard > Developers > API & SDK Keys. Once set to
2026_04_01, the backend enforces step-up authentication immediately.AI-assisted upgrade prompts
These prompts are designed for AI coding agents (Cursor, Claude Code, Copilot). Always review generated code before committing — agents can misread your project structure.For best results, install the Dynamic MCP in your agent so it can reference live documentation automatically. If you don’t have it installed, the prompts fall back to
https://www.dynamic.xyz/docs/llms.txt.- React
- JavaScript
- React Native
- Flutter
- Kotlin
- 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 `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.
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.
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.
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.
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.
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.
Code migration: before and after
Using Dynamic’s built-in UI
- React
- React Native
- Flutter (no built-in UI)
Before — After —
usePromptMfaAuth with createMfaToken: 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();
useStepUpAuthentication with requestedScopes: import { useStepUpAuthentication } from '@dynamic-labs/sdk-react-core';
import { TokenScope } from '@dynamic-labs/sdk-api-core';
const { isStepUpRequired, promptStepUpAuth } = useStepUpAuthentication();
if (await isStepUpRequired({ scope: TokenScope.Walletexport })) {
await promptStepUpAuth({
requestedScopes: [TokenScope.Walletexport],
});
}
// Token is stored — SDK attaches it automatically
await primaryWallet.exportWaasPrivateKey();
Before — After —
client.ui.mfa.show with createMfaToken: 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();
client.stepUpAuth.promptStepUpAuth with requestedScopes: import { TokenScope } from '@dynamic-labs/sdk-api-core';
const isRequired = await client.stepUpAuth.isStepUpRequired({
scope: TokenScope.Walletexport,
});
if (isRequired) {
await client.stepUpAuth.promptStepUpAuth({
requestedScopes: [TokenScope.Walletexport],
});
}
// Token is stored — SDK attaches it automatically
await primaryWallet.exportWaasPrivateKey();
Flutter did not have a built-in action-based MFA UI. Use
promptStepUpAuth to add step-up verification: 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();
Headless TOTP
- React
- JavaScript
- React Native
- Flutter
Before — After —
useMfa with authenticateDevice and createMfaToken: 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 },
});
}
useStepUpAuthentication with verifyTotpMfa: import { useStepUpAuthentication } from '@dynamic-labs/sdk-react-core';
import { TokenScope } from '@dynamic-labs/sdk-api-core';
const { isStepUpRequired, verifyTotpMfa } = useStepUpAuthentication();
if (await isStepUpRequired({ scope: TokenScope.Walletexport })) {
await verifyTotpMfa({
code: totpCode,
requestedScopes: [TokenScope.Walletexport],
});
}
Before — After — same function with
authenticateTotpMfaDevice with createMfaTokenOptions: 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 },
});
}
requestedScopes: import { checkStepUpAuth, authenticateTotpMfaDevice } 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],
});
}
Before — After —
client.mfa.authenticateDevice with createMfaToken: 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 },
});
}
client.stepUpAuth.verifyTotpMfa with requestedScopes: import { TokenScope } 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],
});
}
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'],
);
}
Headless Passkey
- React
- JavaScript
- React Native
- Flutter
Before — After —
useAuthenticatePasskeyMFA with createMfaToken: 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 },
});
}
useStepUpAuthentication with verifyPasskeyMfa: import { useStepUpAuthentication } from '@dynamic-labs/sdk-react-core';
import { TokenScope } from '@dynamic-labs/sdk-api-core';
const { isStepUpRequired, verifyPasskeyMfa } = useStepUpAuthentication();
if (await isStepUpRequired({ scope: TokenScope.Walletexport })) {
await verifyPasskeyMfa({
requestedScopes: [TokenScope.Walletexport],
});
}
Before — After — same function with
authenticatePasskeyMFA with createMfaToken: import { isMfaRequiredForAction, authenticatePasskeyMFA, MFAAction } from '@dynamic-labs-sdk/client';
const required = await isMfaRequiredForAction({ mfaAction: MFAAction.WalletWaasExport });
if (required) {
await authenticatePasskeyMFA({
createMfaToken: { singleUse: true },
});
}
requestedScopes: import { checkStepUpAuth, authenticatePasskeyMFA } 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],
});
}
Before — After —
client.passkeys.authenticateMFA with createMfaToken: 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 },
});
}
client.stepUpAuth.verifyPasskeyMfa with requestedScopes: import { TokenScope } from '@dynamic-labs/sdk-api-core';
const isRequired = await client.stepUpAuth.isStepUpRequired({
scope: TokenScope.Walletexport,
});
if (isRequired) {
await client.stepUpAuth.verifyPasskeyMfa({
requestedScopes: [TokenScope.Walletexport],
});
}
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'],
);
}
Migration checklist
- React
- JavaScript
- React Native
- Flutter
- Upgrade to React SDK
4.76.0or later - Replace
useMfa/useAuthenticatePasskeyMFA/usePromptMfaAuthwithuseStepUpAuthentication - Replace
createMfaTokenwithrequestedScopesusing the appropriateTokenScope - Replace
useIsMfaRequiredForActionwithisStepUpRequired - Test the step-up flow end-to-end for each protected action
- Accept the minimum API version
2026_04_01in your dashboard
- Upgrade to JavaScript SDK
0.24.1or later - Replace
isMfaRequiredForActionwithcheckStepUpAuth - Replace
createMfaToken/createMfaTokenOptionswithrequestedScopesonauthenticateTotpMfaDeviceandauthenticatePasskeyMFA - Test the step-up flow end-to-end for each protected action
- Accept the minimum API version
2026_04_01in your dashboard
- Upgrade to React Native SDK
4.76.0or later - Replace
client.mfa.isRequiredForActionwithclient.stepUpAuth.isStepUpRequired - Replace
client.mfa.authenticateDevicewithclient.stepUpAuth.verifyTotpMfa - Replace
client.passkeys.authenticateMFAwithclient.stepUpAuth.verifyPasskeyMfa - Replace
client.ui.mfa.showwithclient.stepUpAuth.promptStepUpAuth - Test the step-up flow end-to-end for each protected action
- Accept the minimum API version
2026_04_01in your dashboard
- Upgrade to Flutter SDK
1.2.10or later - Implement step-up checks with
DynamicSDK.instance.stepUpAuth.isStepUpRequired - Add verification flows using
promptStepUpAuth,verifyTotpMfa, orverifyPasskeyMfa - Test the step-up flow end-to-end for each protected action
- Accept the minimum API version
2026_04_01in your dashboard