Skip to main content

Overview

Some operations, like signing transactions or exporting keys, are protected by MFA. Knowing which operations trigger MFA helps you design better UX (like showing lock icons) and handle edge cases.
You might need this guide to:
  • Show lock icons on buttons that trigger MFA
  • Build a custom MFA modal instead of the default
  • Handle MFA differently in different parts of your app
  • Disable automatic MFA prompts entirely
If you’re using CDPReactProvider with default settings, MFA verification is handled automatically for enrolled users. Note that MFA is not enforced until users enroll (add the EnrollMfaModal component so users can set up MFA).

MFA-protected operations

These operations trigger MFA verification when the user is enrolled:
  • signEvmHash
  • signEvmTypedData
  • signEvmMessage
  • signEvmTransaction
  • sendEvmTransaction
  • sendUserOperation
  • createEvmKeyExportIframe
  • signSolanaMessage
  • signSolanaTransaction
  • sendSolanaTransaction
  • createSolanaKeyExportIframe

Checking MFA status

Use isMfaRequired() to check if the user needs to verify before protected operations:
import { isMfaRequired } from "@coinbase/cdp-core";

if (isMfaRequired()) {
  console.log("MFA verification required");
}
Use isMfaProtectedAction() to check if a specific action triggers MFA (useful for showing lock icons):
import { isMfaProtectedAction } from "@coinbase/cdp-core";

if (isMfaProtectedAction("signEvmTransaction")) {
  // Show a lock icon on the button
}

Custom handling

For applications using the SDK directly (without CDPReactProvider), or if you want a custom MFA UI, register a listener to intercept MFA requirements.
import { useRegisterMfaListener, useCancelMfaVerification } from "@coinbase/cdp-hooks";

function App() {
  const { cancelMfaVerification } = useCancelMfaVerification();

  // Register a global listener for MFA requirements
  useRegisterMfaListener(({ methods }) => {
    // methods: available MFA methods (e.g., ["totp", "sms"])
    // Show your custom MFA UI here
  });

  // To cancel (user closes modal without verifying):
  // cancelMfaVerification();
}
For scoped handling (different MFA UX in different parts of your app):
import { useRegisterMfaListener } from "@coinbase/cdp-hooks";
import { useRef } from "react";

function ExportSection() {
  const containerRef = useRef(null);

  // Only responds to MFA triggers from within this container
  useRegisterMfaListener(
    ({ methods }) => {
      // Show inline MFA UI for this section
    },
    { scope: containerRef }
  );

  return <div ref={containerRef}>{/* Export UI */}</div>;
}
For complete UI implementations with verification flow, error handling, and state management, see the MFA section in the cdp-hooks reference.

Disabling defaults

To handle MFA entirely yourself, disable the automatic modal at the provider level:
<CDPReactProvider
  config={{
    projectId: "your-project-id",
    mfa: {
      disableAutoPrompt: true,
    },
  }}
>
  <App />
</CDPReactProvider>
Some components like ExportWalletModal have built-in MFA flows. To handle MFA separately, use skipMfa:
<ExportWalletModal
  address={evmAccount}
  skipMfa={true}
/>
When skipMfa and disableAutoPrompt are both true, you must complete MFA verification before the protected action or it will fail.

Troubleshooting

Check:
  • You’re using CDPReactProvider from @coinbase/cdp-react
  • mfa.disableAutoPrompt is not set to true
  • The user is enrolled in MFA (isEnrolledInMfa(user) returns true)
  • If using custom handling, verify you’ve registered a listener
  • Ensure the verification completed successfully before retrying
  • Check that the user hasn’t been signed out
  • Verify the operation parameters are still valid
  • Ensure the ref is attached to a DOM element that contains the triggering action
  • Verify the component with useRegisterMfaListener is mounted when the action occurs