# Multisig Smart Accounts with Safe

Create and manage multisig Safe accounts with multiple owners, threshold signing, and dynamic owner management for secure shared control.

> For the basics, see the [Getting Started Guide](https://docs.candide.dev/wallet/guides/getting-started.md).

[YouTube video player](https://www.youtube.com/embed/jZFx8cYRR48?si=EsS_DT5NRWyKDSaZ)

## Quickstart[​](#quickstart "Direct link to Quickstart")

> You can also [fork the complete code](https://github.com/candidelabs/abstractionkit-examples/blob/main/multisig/multisig.ts) and follow along.

### Create Multisig Account[​](#create-multisig-account "Direct link to Create Multisig Account")

Set up a multisig account with multiple owners and signature threshold:

* index.ts
* .env

Create multisig account

```
import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit";



const ownerPublicAddress1 = process.env.PUBLIC_ADDRESS1 as string;

const ownerPublicAddress2 = process.env.PUBLIC_ADDRESS2 as string;



// Create a 2/2 multisig account (requires both signatures)

const smartAccount = SafeAccount.initializeNewAccount(

    [ownerPublicAddress1, ownerPublicAddress2],

    { threshold: 2 }

);



console.log("Multisig Account Address:", smartAccount.accountAddress);
```

> Learn more: [`initializeNewAccount`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md)

Fund Your Account

Make sure to fund your multisig account with ETH before sending transactions, or use a [paymaster for gas sponsorship](https://docs.candide.dev/wallet/guides/send-gasless-tx.md).

```
CHAIN_ID=11155111

BUNDLER_URL=https://api.candide.dev/public/v3/11155111

JSON_RPC_NODE_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com



# Multisig owners

PUBLIC_ADDRESS1=

PRIVATE_KEY1=



PUBLIC_ADDRESS2=

PRIVATE_KEY2=
```

### Sign with Multiple Keys[​](#sign-with-multiple-keys "Direct link to Sign with Multiple Keys")

Sign UserOperations with all required private keys:

* signing.ts
* Threshold Examples

Multisig signing

```
const chainId = BigInt(process.env.CHAIN_ID as string);

const privateKey1 = process.env.PRIVATE_KEY1 as string;

const privateKey2 = process.env.PRIVATE_KEY2 as string;



// Sign with both private keys for 2/2 multisig

userOperation.signature = smartAccount.signUserOperation(

    userOperation,

    [privateKey1, privateKey2], // Array of all required signatures

    chainId

);
```

> Learn more: [`signUserOperation`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md)

Different threshold configurations

```
// 1 of 2 multisig (only one signature required)

const smartAccount_1of2 = SafeAccount.initializeNewAccount(

    [ownerAddress1, ownerAddress2],

    { threshold: 1 }

);



// 2 of 3 multisig (two signatures required)

const smartAccount_2of3 = SafeAccount.initializeNewAccount(

    [ownerAddress1, ownerAddress2, ownerAddress3],

    { threshold: 2 }

);



// For 2/3 multisig, sign with any 2 owners:

userOperation.signature = smartAccount.signUserOperation(

    userOperation,

    [privateKey1, privateKey3], // Only 2 out of 3 needed

    chainId

);
```

> Learn more: [`initializeNewAccount`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md) | [`signUserOperation`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md)

#### Complete Runnable Example[​](#complete-runnable-example "Direct link to Complete Runnable Example")

Below is a complete example that demonstrates multisig account creation and signing:

Full Working Example

* index.ts
* .env

```
import * as dotenv from 'dotenv'



import {

    SafeMultiChainSigAccountV1 as SafeAccount,

    MetaTransaction,

    getFunctionSelector,

    createCallData,

} from "abstractionkit";



async function main(): Promise<void> {

    // Load environment variables

    dotenv.config()

    const chainId = BigInt(process.env.CHAIN_ID as string)

    const bundlerUrl = process.env.BUNDLER_URL as string

    const jsonRpcNodeProvider = process.env.JSON_RPC_NODE_PROVIDER as string

    const ownerPublicAddress1 = process.env.PUBLIC_ADDRESS1 as string

    const ownerPrivateKey1 = process.env.PRIVATE_KEY1 as string

    const ownerPublicAddress2 = process.env.PUBLIC_ADDRESS2 as string

    const ownerPrivateKey2 = process.env.PRIVATE_KEY2 as string



    // Create 2/2 multisig account

    const smartAccount = SafeAccount.initializeNewAccount(

        [ownerPublicAddress1, ownerPublicAddress2],

        { threshold: 2 }

    )



    console.log("🔐 Multisig Account Address:", smartAccount.accountAddress)



    // Create example transactions (minting NFTs)

    const nftContractAddress = "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336";

    const mintFunctionSignature = 'mint(address)';

    const mintFunctionSelector = getFunctionSelector(mintFunctionSignature);

    const mintTransactionCallData = createCallData(

        mintFunctionSelector, 

        ["address"],

        [smartAccount.accountAddress]

    );

    

    const transaction1: MetaTransaction = {

        to: nftContractAddress,

        value: 0n,

        data: mintTransactionCallData,

    }



    const transaction2: MetaTransaction = {

        to: nftContractAddress,

        value: 0n,

        data: mintTransactionCallData,

    }



    // Create UserOperation

    let userOperation = await smartAccount.createUserOperation(

        [transaction1, transaction2],

        jsonRpcNodeProvider,

        bundlerUrl

    )



    console.log("📋 UserOperation created, awaiting multisig signatures...")



    // Sign with both private keys (2/2 multisig)

    userOperation.signature = smartAccount.signUserOperation(

        userOperation,

        [ownerPrivateKey1, ownerPrivateKey2], // All required signatures

        chainId

    )



    console.log("✍️ UserOperation signed by all parties")



    // Submit the multisig transaction

    const sendUserOperationResponse = await smartAccount.sendUserOperation(

        userOperation, 

        bundlerUrl

    )



    console.log("📤 Multisig UserOperation sent. Waiting for confirmation...")

    

    // Wait for transaction to be included

    let userOperationReceiptResult = await sendUserOperationResponse.included()



    console.log("📋 UserOperation receipt received.")

    console.log(userOperationReceiptResult)

    

    if (userOperationReceiptResult.success) {

        console.log("🎉 Multisig transaction successful! Hash:", userOperationReceiptResult.receipt.transactionHash)

    } else {

        console.log("❌ UserOperation execution failed")

    }

}



main().catch(console.error)
```

> Learn more: [`initializeNewAccount`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md) | [`createUserOperation`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md) | [`signUserOperation`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md) | [`sendUserOperation`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md)

```
CHAIN_ID=11155111

BUNDLER_URL=https://api.candide.dev/public/v3/11155111

JSON_RPC_NODE_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com



# Multisig owners

PUBLIC_ADDRESS1=your_first_public_address

PRIVATE_KEY1=your_first_private_key



PUBLIC_ADDRESS2=your_second_public_address

PRIVATE_KEY2=your_second_private_key
```

## Managing Multisig Owners[​](#managing-multisig-owners "Direct link to Managing Multisig Owners")

After creating a multisig account, you can dynamically add, remove, or swap owners:

* Add Owner
* Remove Owner
* Swap Owner

Add a new owner

```
import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit";



// Add a new owner with threshold of 3 (now 3/3 multisig)

const newOwnerAddress = "0x..."; // New owner's address

const newThreshold = 3;



const addOwnerTx = await smartAccount.createAddOwnerWithThresholdMetaTransactions(

    newOwnerAddress,

    newThreshold,

    jsonRpcNodeProvider

);



// Create UserOperation with the add owner transaction

let userOperation = await smartAccount.createUserOperation(

    [addOwnerTx],

    jsonRpcNodeProvider,

    bundlerUrl

);



// Sign with existing owners (2/2 required)

userOperation.signature = smartAccount.signUserOperation(

    userOperation,

    [privateKey1, privateKey2],

    chainId

);
```

> Learn more: [`createAddOwnerWithThresholdMetaTransactions`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md)

Remove an owner

```
const ownerToRemove = "0x..."; // Owner address to remove

const newThreshold = 1; // New threshold after removal



const removeOwnerTx = await smartAccount.createRemoveOwnerMetaTransaction(

    ownerToRemove,

    newThreshold,

    jsonRpcNodeProvider

);



// Create UserOperation with the remove owner transaction

let userOperation = await smartAccount.createUserOperation(

    [removeOwnerTx],

    jsonRpcNodeProvider,

    bundlerUrl

);



// Sign with required owners (current threshold)

userOperation.signature = smartAccount.signUserOperation(

    userOperation,

    [privateKey1, privateKey2], // All current owners must sign

    chainId

);
```

> Learn more: [`createRemoveOwnerMetaTransaction`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md)

Replace an owner

```
const oldOwnerAddress = "0x..."; // Owner to replace

const newOwnerAddress = "0x..."; // New owner address



const swapOwnerTxs = await smartAccount.createSwapOwnerMetaTransactions(

    oldOwnerAddress,

    newOwnerAddress,

    jsonRpcNodeProvider

);



// Note: This returns an array of transactions (might include deployment)

let userOperation = await smartAccount.createUserOperation(

    swapOwnerTxs, // Array of meta-transactions

    jsonRpcNodeProvider,

    bundlerUrl

);



// Sign with existing owners

userOperation.signature = smartAccount.signUserOperation(

    userOperation,

    [privateKey1, privateKey2],

    chainId

);
```

> Learn more: [`createSwapOwnerMetaTransactions`](https://docs.candide.dev/wallet/abstractionkit/safe-unified-account.md)

## Multisig Configurations[​](#multisig-configurations "Direct link to Multisig Configurations")

Different threshold configurations serve various real-world scenarios:

#### 2/2 - Maximum Security[​](#22---maximum-security "Direct link to 2/2 - Maximum Security")

* **Two-Factor Authentication**: Phone wallet + hardware wallet / second device for personal accounts
* **Business Partnership**: Joint business account where both partners must approve all transactions
* **High-Value Storage**: Maximum security for large amounts

Account Recovery Required

2/2 setups can lock owners out permanently if they lose any key. Always implement [Account Recovery](https://docs.candide.dev/wallet/plugins/recovery-with-guardians.md).

#### 2/3 - Balanced Control[​](#23---balanced-control "Direct link to 2/3 - Balanced Control")

* **Company Treasury**: CEO, CFO, CTO where any 2 can approve (business continuity)
* **Family Trust**: Parents + adult child with built-in inheritance planning
* **Small Team**: Flexible governance with backup access

#### 3/5+ - Distributed Governance[​](#35---distributed-governance "Direct link to 3/5+ - Distributed Governance")

* **DAO Treasury**: Democratic council governance requiring majority consensus
* **Professional Custody**: Institutional-grade security with distributed validators
* **Large Organizations**: Democratic decision making with higher coordination overhead

#### 1/N - Flexible Access[​](#1n---flexible-access "Direct link to 1/N - Flexible Access")

* **Team Operations**: Shared operational funds for routine expenses
* **Development Teams**: Independent spending for daily operations
