The Safe Singleton Factory is a minimal proxy factory that enables deterministic deployment of Safe (formerly Gnosis Safe) multisig wallets and related contracts. It uses CREATE2 to ensure the same wallet addresses across all EVM chains. Contract Address: 0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7 Deployment Status: Default preinstall Repository: github.com/safe-global/safe-singleton-factory

Key Features

  • Deterministic Deployment: Same Safe addresses across all chains
  • Minimal Implementation: Simple factory pattern using CREATE2
  • Version Agnostic: Deploy any version of Safe contracts
  • Gas Efficient: Optimized proxy deployment pattern
  • Ecosystem Standard: Used by Safe infrastructure globally

How It Works

The factory uses a two-step deployment process:
  1. Deploy Singleton: Deploy the Safe master copy (implementation)
  2. Deploy Proxy: Deploy minimal proxies pointing to the singleton
This pattern enables gas-efficient deployment of multiple Safe wallets sharing the same implementation.

Core Method

function deploy(bytes memory data, bytes32 salt)
    returns (address deploymentAddress)
The factory has a single method that deploys contracts using CREATE2.

Usage Examples

Deploy a Safe Wallet

import { ethers } from "ethers";
import { SafeFactory } from "@safe-global/safe-core-sdk";

const FACTORY_ADDRESS = "0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7";

// Using Safe SDK
async function deploySafeWallet(owners, threshold, signer) {
  const safeFactory = await SafeFactory.create({
    ethAdapter: new EthersAdapter({ ethers, signer }),
    safeVersion: '1.4.1'
  });

  const safeAccountConfig = {
    owners: owners,
    threshold: threshold,
    // Optional parameters
    fallbackHandler: "0x...", // Fallback handler address
    paymentToken: ethers.ZeroAddress,
    payment: 0,
    paymentReceiver: ethers.ZeroAddress
  };

  // Predict address before deployment
  const predictedAddress = await safeFactory.predictSafeAddress(safeAccountConfig);
  console.log("Safe will be deployed to:", predictedAddress);

  // Deploy the Safe
  const safeSdk = await safeFactory.deploySafe({ safeAccountConfig });
  const safeAddress = await safeSdk.getAddress();

  console.log("Safe deployed to:", safeAddress);
  return safeSdk;
}

// Manual deployment without SDK
async function deployManually(signer) {
  const factory = new ethers.Contract(
    FACTORY_ADDRESS,
    ["function deploy(bytes,bytes32) returns (address)"],
    signer
  );

  // Prepare Safe proxy bytecode with initialization
  const proxyBytecode = "0x..."; // Safe proxy bytecode
  const salt = ethers.id("my-safe-v1");

  // Deploy
  const tx = await factory.deploy(proxyBytecode, salt);
  const receipt = await tx.wait();

  // Get deployed address from events
  const deployedAddress = receipt.logs[0].address;
  return deployedAddress;
}

Predict Safe Address

Calculate the deployment address before deploying:
function predictSafeAddress(owners, threshold, saltNonce) {
  const initializer = encodeSafeSetup(owners, threshold);
  const salt = ethers.solidityPackedKeccak256(
    ["bytes32", "uint256"],
    [ethers.keccak256(initializer), saltNonce]
  );

  const initCode = ethers.concat([
    PROXY_CREATION_CODE,
    ethers.AbiCoder.defaultAbiCoder().encode(["address"], [SAFE_SINGLETON])
  ]);

  const deploymentAddress = ethers.getCreate2Address(
    FACTORY_ADDRESS,
    salt,
    ethers.keccak256(initCode)
  );

  return deploymentAddress;
}

Deploy with Custom Configuration

async function deployCustomSafe(config, signer) {
  const {
    owners,
    threshold,
    modules = [],
    guards = [],
    fallbackHandler
  } = config;

  // Encode initialization data
  const setupData = safeSingleton.interface.encodeFunctionData("setup", [
    owners,
    threshold,
    ethers.ZeroAddress, // to
    "0x",               // data
    fallbackHandler,
    ethers.ZeroAddress, // paymentToken
    0,                  // payment
    ethers.ZeroAddress  // paymentReceiver
  ]);

  // Deploy proxy pointing to singleton
  const proxyFactory = new ethers.Contract(
    PROXY_FACTORY_ADDRESS,
    proxyFactoryAbi,
    signer
  );

  const tx = await proxyFactory.createProxyWithNonce(
    SAFE_SINGLETON_ADDRESS,
    setupData,
    Date.now() // saltNonce
  );

  const receipt = await tx.wait();
  const safeAddress = getSafeAddressFromReceipt(receipt);

  // Enable modules if specified
  for (const module of modules) {
    await enableModule(safeAddress, module, signer);
  }

  return safeAddress;
}

Deployment Patterns

Organization Wallets

Deploy consistent treasury addresses across chains:
async function deployOrgTreasury(orgId, chains) {
  const salt = ethers.id(`org-treasury-${orgId}`);
  const results = {};

  for (const chain of chains) {
    const provider = new ethers.JsonRpcProvider(chain.rpc);
    const signer = new ethers.Wallet(deployerKey, provider);

    // Same salt = same address on all chains
    const address = await deploySafeWithSalt(salt, signer);
    results[chain.name] = address;
  }

  return results;
}

Counterfactual Wallets

Create wallets that can receive funds before deployment:
class CounterfactualSafe {
  constructor(owners, threshold) {
    this.owners = owners;
    this.threshold = threshold;
    this.address = this.predictAddress();
  }

  predictAddress() {
    // Calculate address without deploying
    return predictSafeAddress(
      this.owners,
      this.threshold,
      0 // saltNonce
    );
  }

  async deploy(signer) {
    // Only deploy when needed
    const code = await signer.provider.getCode(this.address);
    if (code !== "0x") {
      console.log("Already deployed");
      return this.address;
    }

    return deploySafeWallet(
      this.owners,
      this.threshold,
      signer
    );
  }
}

Integration with Safe Ecosystem

Safe Modules

Deploy and enable Safe modules:
// Deploy module via factory
async function deployModule(moduleCode, salt) {
  const tx = await factory.deploy(moduleCode, salt);
  const receipt = await tx.wait();
  return receipt.contractAddress;
}

// Enable module on Safe
async function enableModule(safeAddress, moduleAddress, signer) {
  const safe = new ethers.Contract(safeAddress, safeAbi, signer);
  const tx = await safe.enableModule(moduleAddress);
  await tx.wait();
}

Safe Guards

Deploy transaction guards for additional security:
async function deployAndSetGuard(safeAddress, guardCode, signer) {
  // Deploy guard
  const salt = ethers.id(`guard-${safeAddress}`);
  const guardAddress = await factory.deploy(guardCode, salt);

  // Set as Safe guard
  const safe = new ethers.Contract(safeAddress, safeAbi, signer);
  const tx = await safe.setGuard(guardAddress);
  await tx.wait();

  return guardAddress;
}

Best Practices

Salt Management

// Structured salt generation
function generateSalt(context, nonce) {
  return ethers.solidityPackedKeccak256(
    ["string", "uint256"],
    [context, nonce]
  );
}

// Examples
const userSalt = generateSalt(`user-${userId}`, 0);
const orgSalt = generateSalt(`org-${orgId}`, iteration);
const appSalt = generateSalt(`app-${appId}-${version}`, 0);

Version Control

const SAFE_VERSIONS = {
  "1.3.0": "0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552",
  "1.4.0": "0x41675C099F32341bf84BFc5382aF534df5C7461a",
  "1.4.1": "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762"
};

async function deploySafeVersion(version, owners, threshold) {
  const singleton = SAFE_VERSIONS[version];
  if (!singleton) throw new Error(`Unknown version: ${version}`);

  return deployWithSingleton(singleton, owners, threshold);
}

Gas Optimization

  • Deploy singleton once per chain
  • Reuse proxy bytecode
  • Batch deployments when possible
  • Use minimal initializers

Security Considerations

  • Initialization: Ensure Safes are properly initialized after deployment
  • Salt Uniqueness: Use unique salts to prevent address collisions
  • Singleton Verification: Verify singleton contract before deployment
  • Access Control: The factory itself has no access control - anyone can deploy

Troubleshooting

IssueSolution
Address mismatchVerify salt and bytecode are identical
Deployment failsCheck sufficient gas and valid bytecode
Safe not workingEnsure proper initialization after deployment
Cross-chain inconsistencyVerify same singleton and salt used

Safe Infrastructure

const SAFE_CONTRACTS = {
  factory: "0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7",
  singleton_1_4_1: "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762",
  proxyFactory: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67",
  multiSend: "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526",
  fallbackHandler: "0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99"
};

Further Reading