> ## Documentation Index
> Fetch the complete documentation index at: https://docs.privy.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up granular MFA

> Enable granular MFA, create a policy with ALLOW_WITH_MFA rules, and add it to wallets.

To set up [granular MFA](https://docs.privy.io/authentication/user-authentication/mfa/granular/overview.md), enable transaction MFA, create a policy that requires MFA, add it to wallets, then update your app to granular MFA.

## Request access

Granular MFA is available on Enterprise plans. Contact Privy to enable it for your app's account.

## Enable transaction MFA

In the [Privy Dashboard](https://dashboard.privy.io/apps?logins=mfa\&page=login-methods), enable transaction MFA and choose your app's MFA methods. The cache duration sets how long a completed MFA verification lasts. See [MFA setup](https://docs.privy.io/authentication/user-authentication/mfa/setup.md).

## Create a policy

This policy requires MFA to export a wallet's private key or seed phrase, and allows every other action. See [granular MFA policies](https://docs.privy.io/controls/policies/example-policies/granular-mfa.md) for more examples.

**React**

Create policies on your app's backend with a server SDK. Create the policy once, and store its ID to apply to each user's wallet. With the NodeJS SDK:

```ts {skip-check} theme={"system"}
const policy = await privy.policies().create({
  version: '1.0',
  name: 'Require MFA for wallet export',
  chain_type: 'ethereum',
  rules: [
    {
      name: 'Require MFA for private key export',
      method: 'exportPrivateKey',
      action: 'ALLOW_WITH_MFA',
      conditions: []
    },
    {
      name: 'Require MFA for seed phrase export',
      method: 'exportSeedPhrase',
      action: 'ALLOW_WITH_MFA',
      conditions: []
    },
    {
      name: 'Allow everything else',
      method: '*',
      action: 'ALLOW',
      conditions: []
    }
  ]
});
```

**NodeJS**

This requires `@privy-io/node` version `0.39.0` or later.

```ts {skip-check} theme={"system"}
const policy = await privy.policies().create({
  version: '1.0',
  name: 'Require MFA for wallet export',
  chain_type: 'ethereum',
  rules: [
    {
      name: 'Require MFA for private key export',
      method: 'exportPrivateKey',
      action: 'ALLOW_WITH_MFA',
      conditions: []
    },
    {
      name: 'Require MFA for seed phrase export',
      method: 'exportSeedPhrase',
      action: 'ALLOW_WITH_MFA',
      conditions: []
    },
    {
      name: 'Allow everything else',
      method: '*',
      action: 'ALLOW',
      conditions: []
    }
  ]
});
```

**Java**

This requires `privy-java` version `0.61.0` or later.

```java theme={"system"}
PrivyClient privyClient = PrivyClient.builder()
    .appId("insert-app-id")
    .appSecret("insert-app-secret")
    .build();

Rule requireMfaForPrivateKey = Rule.builder()
    .name("Require MFA for private key export")
    .method(PolicyMethod.EXPORT_PRIVATE_KEY)
    .action(PolicyAction.ALLOW_WITH_MFA)
    .conditions(List.of())
    .build();

Rule requireMfaForSeedPhrase = Rule.builder()
    .name("Require MFA for seed phrase export")
    .method(PolicyMethod.EXPORT_SEED_PHRASE)
    .action(PolicyAction.ALLOW_WITH_MFA)
    .conditions(List.of())
    .build();

Rule allowEverythingElse = Rule.builder()
    .name("Allow everything else")
    .method(PolicyMethod.WILDCARD)
    .action(PolicyAction.ALLOW)
    .conditions(List.of())
    .build();

PolicyCreateResponse policyResponse = privyClient.policies().create(
    PolicyCreateRequestBody.builder()
        .version(Version.ONE_DOT0)
        .name("Require MFA for wallet export")
        .chainType(WalletChainType.ETHEREUM)
        .rules(List.of(requireMfaForPrivateKey, requireMfaForSeedPhrase, allowEverythingElse))
        .build());
String policyId = policyResponse.policy().orElseThrow().id();
```

## Add the policy to a wallet

Attach the policy to each wallet it should protect. Set `policy_ids` when creating a wallet, or [update an existing wallet](https://docs.privy.io/wallets/wallets/update-a-wallet.md).

**React**

Apply the policy to the user's wallet with the `updateWallet` method from the `useUpdateWallet` hook. The React SDK signs the update with the user's authorization signature.

```tsx theme={"system"}
import {useUpdateWallet} from '@privy-io/react-auth';

const {updateWallet} = useUpdateWallet();

// policyId is the policy ID from your app's backend
await updateWallet('insert-wallet-id', {
  policy_ids: [policyId]
});
```

With granular MFA on, changing a wallet's policies requires the user's MFA. The React SDK prompts for it starting in `@privy-io/react-auth` version `3.51.4`.

Once a wallet has the policy, the React SDK prompts the user to enroll or verify when an action requires MFA, then retries the request once. This covers `useSignMessage`, `useSignTypedData`, `useSendTransaction`, `useSignTransaction`, and the EIP-1193 provider, starting in version `3.51.1`. It also covers the Solana `signMessage`, `signTransaction`, and `signAndSendTransaction` methods. Starting in version `3.51.4`, it also covers `useTransfer`, `useUpdateWallet`, `useSigners`, `useSignRawHash`, swaps, and earn.

If your app uses custom MFA UIs, register both `onMfaRequired` and `onMfaEnrollmentRequired` with [`useRegisterMfaListener`](https://docs.privy.io/authentication/user-authentication/mfa/listener). Without `onMfaEnrollmentRequired`, a request from an unenrolled user waits five minutes, then fails.

**NodeJS**

```ts {skip-check} theme={"system"}
const wallet = await privy.wallets().create({
  chain_type: 'ethereum',
  policy_ids: [policy.id],
  owner: {user_id: userId}
});
```

**Java**

```java theme={"system"}
WalletCreateResponse walletResponse = privyClient.wallets().create(
    WalletCreateRequestBody.builder()
        .chainType(WalletChainType.ETHEREUM)
        .policyIds(List.of(policyId))
        .owner(OwnerInput.of(OwnerInputUser.builder().userId(userId).build()))
        .build());
String walletId = walletResponse.wallet().orElseThrow().id();
```

## Update to granular MFA

In the dashboard's transaction MFA settings, select **Update to granular MFA** and confirm. Wallet actions that match no `ALLOW_WITH_MFA` rule stop requiring MFA right away.

> [!WARNING]
>
> Updating to granular MFA cannot be undone. Your app cannot switch back to standard transaction MFA
> or disable transaction MFA. The app's MFA methods and cache duration carry over and stay editable.
