> ## 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.

# Multisig

> Set up and use m-of-n ECDSA or passkey multisig accounts.

A multisig account requires signatures from multiple owners to authorize a transaction. It distributes control so no single party can act unilaterally — useful for treasuries and shared accounts — and can also improve UX (e.g. a passkey per device, so a user can sign from any of them).

Both the [ECDSA signer](/smart-wallet/core/ecdsa-signer) (Ownable Validator) and the [passkey signer](/smart-wallet/core/passkeys) (WebAuthn Validator) support multisig. The validator verifies each signature against the stored owner set, and the account enforces the threshold (e.g. 2-of-3).

## Setup

Create a multisig by passing multiple owners and a `threshold` — the number of signatures required to authorize a transaction.

<Tabs>
  <Tab title="ECDSA">
    ```ts {5} theme={null}
    const account = await rhinestone.createAccount({
      owners: {
        type: 'ecdsa',
        accounts: [signerA, signerB, signerC],
        threshold: 2, // 2-of-3
      },
    })
    ```
  </Tab>

  <Tab title="Passkeys">
    ```ts {5} theme={null}
    const account = await rhinestone.createAccount({
      owners: {
        type: 'passkey',
        accounts: [passkeyAccountA, passkeyAccountB],
        threshold: 2, // 2-of-2
      },
    })
    ```
  </Tab>
</Tabs>

By default, `threshold` is `1` (any single owner can sign).

Managing owners after creation — adding or removing signers, changing the threshold, reading the current set — is covered on the signer pages: [ECDSA](/smart-wallet/core/ecdsa-signer#multisig) and [passkeys](/smart-wallet/core/passkeys#multisig).

<Note>To combine *different* signer types (e.g. require both an EOA and a passkey), use [multi-factor authentication](/smart-wallet/advanced/multi-factor-authentication) instead.</Note>

## 1-of-n: sign from any owner

With a 1-of-n threshold, any single owner can authorize a transaction on their own. Pick which owner signs with `signers`. A common use is a passkey per device, so the user can sign from any device without migrating keys.

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

// One passkey per device, each registered via WebAuthn on that device
const passkeyAccountA = toWebAuthnAccount({ credential: credentialFromDeviceA })
const passkeyAccountB = toWebAuthnAccount({ credential: credentialFromDeviceB })

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

// Any single registered device can sign
const account = await rhinestone.createAccount({
  owners: {
    type: 'passkey',
    accounts: [passkeyAccountA, passkeyAccountB],
    threshold: 1,
  },
})

const usdcAmount = parseUnits('0.1', 6)
const prepared = await account.prepareTransaction({
  sourceChains: [base],
  targetChain: arbitrum,
  calls: [
    {
      to: 'USDC',
      value: 0n,
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: 'transfer',
        args: ['0xd8da6bf26964af9d7eed9e03e53415d37aa96045', usdcAmount],
      }),
    },
  ],
  tokenRequests: [{ address: 'USDC', amount: usdcAmount }],
  // Sign from a single device
  signers: {
    type: 'owner',
    kind: 'passkey',
    accounts: [passkeyAccountA],
  },
})

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

ECDSA works the same way — set `threshold: 1` and pass a single account under `signers`.

## m-of-n: collect multiple signatures

When the threshold is greater than one, you need signatures from several owners. There are two ways to collect them.

### Co-located signing

If every key is available in the same environment, sign with a subset of owners in a single call. List the signing owners under `signers` and call `signTransaction` once — the SDK requests a signature from each account in the list in parallel and packs them into the account's multisig signature. Every listed key must be reachable in this environment; if the owners sign from separate devices, use [independent signing](#independent-signing) instead.

```ts theme={null}
const prepared = await account.prepareTransaction({
  sourceChains: [base],
  targetChain: arbitrum,
  calls: [
    // …
  ],
  tokenRequests: [{ address: 'USDC', amount: usdcAmount }],
  // Two of the three owners sign to meet the 2-of-3 threshold
  signers: {
    type: 'owner',
    kind: 'ecdsa',
    accounts: [signerA, signerB],
  },
})

const signed = await account.signTransaction(prepared)
const transaction = await account.submitTransaction(signed)
```

### Independent signing

When owners are separate parties on different devices, each signs the same prepared transaction independently, and a coordinator merges the results. Prepare the transaction once and share it with each owner:

```ts theme={null}
// Coordinator: prepare once, then share `prepared` with each owner
const prepared = await account.prepareTransaction({
  sourceChains: [base],
  targetChain: arbitrum,
  calls: [
    // …
  ],
  tokenRequests: [{ address: 'USDC', amount: usdcAmount }],
})

// Each owner signs in their own environment
const signatureA = await account.signTransaction(prepared, { owner: signerA })
const signatureB = await account.signTransaction(prepared, { owner: signerB })

// Coordinator: assemble the collected signatures and submit
const signed = await account.assembleTransaction(prepared, [signatureA, signatureB])
const transaction = await account.submitTransaction(signed)
const result = await account.waitForExecution(transaction)
```

Passkey owners follow the same flow — pass the passkey account as `owner`. The signature returned by `signTransaction({ owner })` is JSON-serializable, so each owner can return it over the wire:

```ts theme={null}
// On the owner's side
const payload = JSON.stringify(await account.signTransaction(prepared, { owner: signerA }))

// On the coordinator's side
const signatureA = JSON.parse(payload)
```

<Note>
  Signatures can be collected in any order — `assembleTransaction` deduplicates them and orders them to match the account's owner set. Provide enough to meet the threshold, or assembly throws `InsufficientOwnerSignaturesError`. Every owner must sign the same prepared transaction, otherwise assembly throws `MismatchedOwnerSignaturesError`.
</Note>

<Note>
  Independent signing is supported for ECDSA, passkey, and multi-factor owners. For [multi-factor](/smart-wallet/advanced/multi-factor-authentication) accounts, also pass the `validatorId` of the factor the owner belongs to: `signTransaction(prepared, { owner, validatorId })`.
</Note>
