Dashboard Setup
- Go to the Security page.
- In the Action MFA section, enable your desired methods (TOTP and/or Passkeys).
- (Optional) Toggle “Require at onboarding” to force MFA setup during signup.
- Choose which events you want to protect with MFA and toggle them on.
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.
Your UI SDK Implementation
- TOTP
- Passkey
- React
- React Native
- JavaScript SDK
- Check requirement: Use
useIsMfaRequiredForAction()for the specific action. - Check token: Use
useGetMfaToken()if MFA is required. - Create token: If missing, collect OTP and call
authenticateDevice({ code, createMfaToken }). - Perform action: Token is applied automatically after creation.
Copy
Ask AI
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.WalletWaasSign,
});
if (!requires) {
// No MFA required for this action
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 } // or false for persistent
});
// Now perform the action that requires Action-Based MFA
return token;
};
return null;
}
- Only one verified TOTP device per user (device management applies across modes).
- Recovery codes are single-use; regenerate with
getRecoveryCodes(true)if exhausted.
- Error: “MFA required” despite prompting — check
useGetMfaToken()and attach token to backend requests. - Token unexpectedly missing — persistent tokens expire with session; create a new token when needed.
- Check requirement: Use
client.mfa.isRequiredForAction()for the specific action. - Create token: If required, collect OTP and call
client.mfa.authenticateDevice({ code, createMfaToken }). - Perform action: Token is applied automatically after creation.
Copy
Ask AI
import React, { useState } from 'react';
import { Alert, View, TextInput, Button } from 'react-native';
import { MFAAction } from '@dynamic-labs/sdk-api-core';
import { useDynamic } from './path-to-your-client';
export function EnsureTotpMfaToken() {
const client = useDynamic();
const [code, setCode] = useState('');
const [loading, setLoading] = useState(false);
const promptForMfaIfRequired = async () => {
try {
setLoading(true);
// Check if MFA is required for this action
const required = await client.mfa.isRequiredForAction({
mfaAction: MFAAction.WalletWaasSign,
});
if (!required) {
// No MFA required, proceed with action
Alert.alert('Success', 'No MFA required for this action');
return;
}
if (!code.trim()) {
Alert.alert('Error', 'Please enter your 6-digit code');
return;
}
// Create MFA token with the TOTP code
await client.mfa.authenticateDevice({
code,
createMfaToken: { singleUse: true },
});
// Now perform the action that requires Action-Based MFA
// The token is automatically included in subsequent requests
Alert.alert('Success', 'MFA token created, proceed with action');
} catch (error) {
console.error('Failed to authenticate with MFA:', error);
Alert.alert('Error', 'Failed to authenticate with MFA');
} finally {
setLoading(false);
}
};
return (
<View>
<TextInput
placeholder="Enter 6-digit code"
value={code}
onChangeText={setCode}
keyboardType="numeric"
maxLength={6}
/>
<Button
title="Authenticate & Proceed"
onPress={performSensitiveAction}
disabled={loading}
/>
</View>
);
}
- Only one verified TOTP device per user (device management applies across modes).
- Recovery codes are single-use; regenerate with
client.mfa.getNewRecoveryCodes()if exhausted.
- Error: “MFA required” despite prompting — ensure you authenticate before performing the action.
- Token unexpectedly missing — tokens expire with session; create a new token when needed.
- Authenticate:
authenticateTotpMfaDevice({ code, createMfaTokenOptions }). - Single-use token: For action-based MFA, set
createMfaTokenOptions.singleUse = true.
Copy
Ask AI
import { isMfaRequiredForAction, authenticateTotpMfaDevice, MFAAction } from '@dynamic-labs-sdk/client';
const onExportPrivateKeyClick = async () => {
const required = await isMfaRequiredForAction({ mfaAction: MFAAction.WalletWaasExport });
if (required) {
await authenticateTotpMfaDevice({ code: '123456', createMfaTokenOptions: { singleUse: true } });
}
await exportWaasPrivateKey(params);
};
- React
- React Native
- JavaScript SDK
- Check requirement: Use
useIsMfaRequiredForAction()for the specific action. - Check token: Use
useGetMfaToken()if MFA is required. - Register if needed: If the user has no passkeys (common when “Require at onboarding” is off), call
registerPasskey()first. - Create token: Call
authenticatePasskeyMFA({ createMfaToken })if missing. - Perform action: Token is applied automatically after creation.
Copy
Ask AI
import {
useAuthenticatePasskeyMFA,
useGetMfaToken,
useGetPasskeys,
useIsMfaRequiredForAction,
useRegisterPasskey,
} from "@dynamic-labs/sdk-react-core";
import { MFAAction } from '@dynamic-labs/sdk-api-core';
export function EnsurePasskeyMfaToken() {
const authenticatePasskeyMFA = useAuthenticatePasskeyMFA();
const { getMfaToken } = useGetMfaToken();
const getPasskeys = useGetPasskeys();
const registerPasskey = useRegisterPasskey();
const isMfaRequiredForAction = useIsMfaRequiredForAction();
const promptForMfaIfRequired = async () => {
const requires = await isMfaRequiredForAction({
mfaAction: MFAAction.WalletWaasSign,
});
if (!requires) {
// No MFA required for this action
return;
}
// Important: if you don't have MFA required at onboarding and you want to force the user
// to setup MFA prior to performing the action, you should skip any checks for MFA requirement above (isMfaRequiredForAction)
// and force a registration or authentication of the MFA method.
// If the user doesn't have a passkey yet, register one first
const passkeys = await getPasskeys();
if (passkeys.length === 0) {
await registerPasskey();
}
await authenticatePasskeyMFA({
createMfaToken: { singleUse: false } // or true for one-time
});
// Now perform the action that requires Action-Based MFA
};
}
- Passkeys require WebAuthn support; production must be over HTTPS.
- Consider persistent tokens for multiple sensitive actions in-session.
- Error: “MFA required” when performing actions — ensure you check
getMfaToken()before creating a new token and include it in the request. - Error: “Passkey not supported” — verify WebAuthn support and HTTPS in production.
- Check requirement: Use
client.mfa.isRequiredForAction()for the specific action. - Check token: Use
client.mfa.getMfaToken()if MFA is required. - Register if needed: If the user has no passkeys (common when “Require at onboarding” is off), call
client.passkeys.register()first. - Create token: Call
client.passkeys.authenticateMFA({ createMfaToken })if missing. - Perform action: Token is applied automatically after creation.
Copy
Ask AI
import React, { useState } from 'react';
import { Alert, View, Button } from 'react-native';
import { MFAAction } from '@dynamic-labs/sdk-api-core';
import { useDynamic } from './path-to-your-client';
export function EnsurePasskeyMfaToken() {
const client = useDynamic();
const [loading, setLoading] = useState(false);
const promptForMfaIfRequired = async () => {
try {
setLoading(true);
// Check if MFA is required for this action
const requires = await client.mfa.isRequiredForAction({
mfaAction: MFAAction.WalletWaasSign,
});
if (!requires) {
// No MFA required for this action
return;
}
// Important: if you don't have MFA required at onboarding and you want to force the user
// to setup MFA prior to performing the action, you should skip any checks for MFA requirement above (isMfaRequiredForAction)
// and force a registration or authentication of the MFA method.
// If the user doesn't have a passkey yet, register one first
const passkeys = await client.passkeys.get();
if (passkeys.length === 0) {
await client.passkeys.register();
}
// Create MFA token using passkey
await client.passkeys.authenticateMFA({
createMfaToken: { singleUse: false }, // or true for one-time
});
// Now perform the action that requires Action-Based MFA
} catch (error) {
console.error('Failed to create MFA token:', error);
Alert.alert('Error', 'Failed to authenticate with passkey');
throw error;
} finally {
setLoading(false);
}
};
return (
<View>
<Button
title="Authenticate & Proceed"
onPress={ensureToken}
disabled={loading}
/>
</View>
);
}
- Passkeys require platform setup. See the React Native Passkey Setup guide.
- Consider persistent tokens for multiple sensitive actions in-session.
- Error: “MFA required” when performing actions — ensure you check
getMfaToken()before creating a new token and include it in the request. - Error: “Passkey not supported” — verify platform setup and passkey support.
- Check requirement:
isMfaRequiredForAction({ mfaAction }). - Create token:
authenticatePasskeyMFA({ createMfaToken: { singleUse: true } }).
Copy
Ask AI
import { isMfaRequiredForAction, authenticatePasskeyMFA, MFAAction } from '@dynamic-labs-sdk/client';
const onExportPrivateKeyClick = async () => {
const required = await isMfaRequiredForAction({ mfaAction: MFAAction.WalletWaasExport });
if (required) {
await authenticatePasskeyMFA({ createMfaToken: { singleUse: true } });
}
await exportWaasPrivateKey(params);
};
Dynamic UI Implementation
- React
- React Native
- JavaScript SDK
Note: The Dynamic UI is method-agnostic. It automatically prompts with whichever MFA method(s) you have enabled (TOTP and/or Passkeys).
For action-based MFA, check if the specific action requires MFA and then open the Dynamic UI to create an MFA token.
Copy
Ask AI
import { usePromptMfaAuth, useIsMfaRequiredForAction } from "@dynamic-labs/sdk-react-core";
import { MFAAction } from '@dynamic-labs/sdk-api-core';
const isMfaRequiredForAction = useIsMfaRequiredForAction();
const promptMfaAuth = usePromptMfaAuth();
// Check if MFA is required for the action
const isMfaRequired = await isMfaRequiredForAction({
mfaAction: MFAAction.WalletWaasSign, // Specify action configured for Action-Based MFA
});
if (isMfaRequired) {
// Opens the Dynamic UI to prompt the user to authenticate with MFA
await promptMfaAuth({ createMfaToken: true }); // Create a single-use token for the action
}
// Perform the action
await primaryWallet.signMessage('Hello, world');
- usePromptMfaAuth hook to prompt the user to authenticate with MFA
- useIsMfaRequiredForAction hook to check if MFA is required for the action that the user is attempting to perform
The React Native SDK provides a UI method to prompt MFA authentication. Use Note: For Action-Based MFA in React Native, only TOTP authentication is currently supported. Passkey MFA is not available in React Native.
client.mfa.isRequiredForAction() to check if MFA is required, then use client.ui.mfa.show() to prompt the user.Note: The Dynamic UI is method-agnostic. It automatically prompts with whichever MFA method(s) you have enabled (TOTP only for React Native).Copy
Ask AI
import React, { useState } from 'react';
import { Alert, View, Button } from 'react-native';
import { MFAAction } from '@dynamic-labs/sdk-api-core';
import { useDynamic } from './path-to-your-client';
export function PerformSensitiveAction() {
const client = useDynamic();
const [loading, setLoading] = useState(false);
const performAction = async () => {
try {
setLoading(true);
// Check if MFA is required for the action
const isMfaRequired = await client.mfa.isRequiredForAction({
mfaAction: MFAAction.WalletWaasSign, // Specify action configured for Action-Based MFA
});
if (isMfaRequired) {
// Opens the Dynamic UI to prompt the user to authenticate with MFA
await client.ui.mfa.show({ createMfaToken: true }); // Create a single-use token for the action
}
// Perform the action
// await primaryWallet.signMessage('Hello, world');
Alert.alert('Success', 'Action completed successfully');
} catch (error) {
console.error('Failed to perform action:', error);
Alert.alert('Error', 'Failed to perform action');
} finally {
setLoading(false);
}
};
return (
<View>
<Button
title="Perform Sensitive Action"
onPress={performAction}
disabled={loading}
/>
</View>
);
}
The JavaScript SDK does not provide any Dynamic UI. For action-based MFA, check if the action requires MFA and, if so, authenticate with the user’s chosen method and optionally create a single-use token.
Copy
Ask AI
import { isMfaRequiredForAction, authenticatePasskeyMFA, authenticateTotpMfaDevice, MFAAction } from '@dynamic-labs-sdk/client';
const onSensitiveAction = async (codeFromUser) => {
const required = await isMfaRequiredForAction({ mfaAction: MFAAction.WalletWaasSign });
if (!required) return;
// Prefer passkey if available in your UI; otherwise use TOTP code provided by the user
try {
await authenticatePasskeyMFA({ createMfaToken: { singleUse: true } });
} catch {
await authenticateTotpMfaDevice({ code: codeFromUser, createMfaTokenOptions: { singleUse: true } });
}
// Now perform the action
// await exportWaasPrivateKey(params);
};