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

# Multi-factor authentication

> Using multiple validators in tandem

MFA (multi-factor authorization) validator lets you use multiple validators for a single transaction.

MFA works as a layer on top of other validator modules. It works as a multiplexer for the validator modules, calling the underlying validator implementations to validate the transaction. It also lets you have multiple configurations for the same validator module.

For example, you can set up an MFA validator so that the user needs to sign a transaction with both an EOA and a passkey.

## Subvalidators

*Subvalidators* are the validator modules that are used under the hood to validate the signature. Any ERC-7579 validator module can serve as a subvalidator.

Subvalidator ID is the index of the validator in the validator list. For example, if you installed the ECDSA and the passkey modules as subvalidators, the ECDSA validator will have index 0 and the passkey validator will have index 1.

## Module selection

There are two MFA validator modules: the [registry-free one](/home/resources/address-book) and the [legacy one](/home/resources/address-book#legacy), which is the SDK default. The legacy module checks the ERC-7484 registry on installation, so installing it on a new account reverts. Select the registry-free module explicitly with `MULTI_FACTOR_VALIDATOR_V2_ADDRESS`.

The selection is not inherited from the account config, so repeat it in the explicit signer selection as well. Otherwise the signature is routed to the legacy module.

## Initialization

To create an account with MFA:

```ts theme={null}
import { MULTI_FACTOR_VALIDATOR_V2_ADDRESS } from '@rhinestone/sdk'

const rhinestone = new RhinestoneSDK()
const rhinestoneAccount = await rhinestone.createAccount({
  owners: {
    type: 'multi-factor',
    module: MULTI_FACTOR_VALIDATOR_V2_ADDRESS,
    // Require a valid signature from both an EOA and a passkey signer
    threshold: 2,
    // List of subvalidators to use; multiple validators can have the same type
    validators: [
      {
        type: 'ecdsa',
        accounts: [accountA, accountB],
      },
      {
        type: 'passkey',
        accounts: [passkeyAccount],
      },
    ],
  },
})
```

You can also install the MFA module on an existing account:

```ts theme={null}
import { MULTI_FACTOR_VALIDATOR_V2_ADDRESS } from '@rhinestone/sdk'
import { enable as enableMultiFactor } from '@rhinestone/sdk/actions/mfa'

const rhinestone = new RhinestoneSDK()
const rhinestoneAccount = await rhinestone.createAccount({
  // …
});

const transaction = await rhinestoneAccount.prepareTransaction({
  chain: sourceChain,
  calls: [
    enableMultiFactor(
      [
        {
          type: 'ecdsa',
          accounts: [accountA, accountB],
        },
        {
          type: 'passkey',
          accounts: [passkeyAccount],
        },
      ],
      2,
      MULTI_FACTOR_VALIDATOR_V2_ADDRESS,
    ),
  ],
})
```

## Usage

### Signer Selection

```ts {12-29} theme={null}
import { MULTI_FACTOR_VALIDATOR_V2_ADDRESS } from '@rhinestone/sdk'

const transactionData = await rhinestoneAccount.prepareTransaction({
  sourceChains: [sourceChain],
  targetChain,
  calls: [
    {
      to: zeroAddress,
      data: '0xdeadbeef',
    },
  ],
  signers: {
    type: 'owner',
    kind: 'multi-factor',
    module: MULTI_FACTOR_VALIDATOR_V2_ADDRESS,
    validators: [
      {
        type: 'ecdsa',
        id: 0,
        // Since we didn't set the "threshold" for the ECDSA validator, we can sign with a single account here
        accounts: [accountB],
      },
      {
        type: 'passkey',
        id: 1,
        accounts: [passkeyAccount],
      },
    ],
  },
})
```

## Management

### Adding a validator

<Note>You can also use this to update the config of the existing subvalidator.</Note>

```ts theme={null}
import { MULTI_FACTOR_VALIDATOR_V2_ADDRESS } from '@rhinestone/sdk'
import { setSubValidator } from '@rhinestone/sdk/actions/mfa'

const validatorId = 2;

const transaction = await rhinestoneAccount.prepareTransaction({
  chain: sourceChain,
  calls: [
    setSubValidator(
      validatorId,
      {
        type: 'ecdsa',
        accounts: [accountC],
      },
      MULTI_FACTOR_VALIDATOR_V2_ADDRESS,
    ),
  ],
})
```

### Removing a validator

```ts theme={null}
import { MULTI_FACTOR_VALIDATOR_V2_ADDRESS } from '@rhinestone/sdk'
import { removeSubValidator } from '@rhinestone/sdk/actions/mfa'

const validatorId = 2;

const transaction = await rhinestoneAccount.prepareTransaction({
  chain: sourceChain,
  calls: [
    removeSubValidator(
      validatorId,
      {
        type: 'ecdsa',
        accounts: [accountC],
      },
      MULTI_FACTOR_VALIDATOR_V2_ADDRESS,
    ),
  ],
})
```

## Example: EOA + passkey (2-of-2)

End to end: create an account that requires both an EOA and a passkey, then approve a transaction with both factors.

```ts theme={null}
import {
  MULTI_FACTOR_VALIDATOR_V2_ADDRESS,
  RhinestoneSDK,
} from '@rhinestone/sdk'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import { toWebAuthnAccount } from 'viem/account-abstraction'
import { base, arbitrum } from 'viem/chains'
import { encodeFunctionData, erc20Abi, parseUnits } from 'viem'

const eoaAccount = privateKeyToAccount(generatePrivateKey())
const passkeyAccount = toWebAuthnAccount({ credential })

const rhinestone = new RhinestoneSDK({
  apiKey: process.env.RHINESTONE_API_KEY as string,
})

// Require a signature from both the EOA and the passkey
const rhinestoneAccount = await rhinestone.createAccount({
  owners: {
    type: 'multi-factor',
    module: MULTI_FACTOR_VALIDATOR_V2_ADDRESS,
    threshold: 2,
    validators: [
      { type: 'ecdsa', accounts: [eoaAccount] },
      { type: 'passkey', accounts: [passkeyAccount] },
    ],
  },
})

const usdcAmount = parseUnits('0.1', 6)
const usdc = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' // USDC on Arbitrum
const prepared = await rhinestoneAccount.prepareTransaction({
  sourceChains: [base],
  targetChain: arbitrum,
  calls: [
    {
      to: usdc,
      value: 0n,
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: 'transfer',
        args: ['0xd8da6bf26964af9d7eed9e03e53415d37aa96045', usdcAmount],
      }),
    },
  ],
  tokenRequests: [{ address: usdc, amount: usdcAmount }],
  // Provide both factors. `id` is each validator's index in the list above.
  signers: {
    type: 'owner',
    kind: 'multi-factor',
    module: MULTI_FACTOR_VALIDATOR_V2_ADDRESS,
    validators: [
      { type: 'ecdsa', id: 0, accounts: [eoaAccount] },
      { type: 'passkey', id: 1, accounts: [passkeyAccount] },
    ],
  },
})

const signed = await rhinestoneAccount.signTransaction(prepared)
const transaction = await rhinestoneAccount.submitTransaction(signed)
const result = await rhinestoneAccount.waitForExecution(transaction)
```
