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

  1. Go to the Security page.
  2. In the Action MFA section, enable your desired methods (TOTP and/or Passkeys).
  3. (Optional) Toggle “Require at onboarding” to force MFA setup during signup.
  4. 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

  • 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.
    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.
    Troubleshooting
    • 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.

Dynamic UI Implementation

The React Native SDK provides a UI method to prompt MFA authentication. Use 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).
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>
  );
}
Note: For Action-Based MFA in React Native, only TOTP authentication is currently supported. Passkey MFA is not available in React Native.