SubaccountFactory

Git Source

Inherits: ReentrancyGuard

Author: Ultrasound Labs

Deploys deterministic Safe-based subaccounts and their AllowanceHolders, and wires them up for multi-chain use.

Note: security-contact: security@ultrasoundlabs.org

State Variables

SAFE_SINGLETON

Canonical Safe singleton (implementation) used for new subaccounts.

address public immutable SAFE_SINGLETON;

SAFE_PROXY_FACTORY

Factory for deploying Safe proxies.

address public immutable SAFE_PROXY_FACTORY;

SAFE_MODULE_SETUP

Helper contract for enabling modules during Safe setup.

address public immutable SAFE_MODULE_SETUP;

MULTICHAIN_SIGNATURES_MODULE

Module providing multi-chain batched signatures.

address public immutable MULTICHAIN_SIGNATURES_MODULE;

ALLOWANCE_MANAGER

Address of the AllowanceManager used by holders.

address public immutable ALLOWANCE_MANAGER;

_enableModulesCalldata

Pre-computed calldata for enabling the MultiChainSignaturesModule via the helper in the Safe setup. This is computed once in the constructor to avoid rebuilding the dynamic array on every subaccount creation.

bytes private _enableModulesCalldata;

isDeployedSubaccount

Records whether a given subaccount proxy has already been deployed.

Keyed by subaccount address.

mapping(address => bool) public isDeployedSubaccount;

isDeployedAllowanceHolder

Records whether the AllowanceHolder for (owner,token) has been deployed.

Keyed by AllowanceHolder address.

mapping(address => bool) public isDeployedAllowanceHolder;

Functions

constructor

Initializes the factory with required contract addresses.

constructor(
    address _safeSingleton,
    address _safeProxyFactory,
    address _safeModuleSetup,
    address _multichainSignaturesModule,
    address _allowanceManager
);

Parameters

NameTypeDescription
_safeSingletonaddressAddress of the Safe singleton (logic contract).
_safeProxyFactoryaddressAddress of the SafeProxyFactory.
_safeModuleSetupaddressAddress of the helper contract for module setup.
_multichainSignaturesModuleaddressAddress of the MultiChainSignaturesModule to enable.
_allowanceManageraddressAddress of the AllowanceManager to use for allowance holders.

createAllowanceHolder

Ensures the AllowanceHolder for (owner, token) exists and has allowance via Permit2.

Deploys the holder deterministically via CREATE2 if not yet deployed, immediately executes permit, and optionally pushes initial quota/fund/first call.

function createAllowanceHolder(
    address owner,
    address token,
    address subaccount,
    uint256 quotaAmount,
    uint256 quotaTimeframe,
    uint256 initialFund,
    bytes calldata firstCallData,
    uint256 permitDeadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) public nonReentrant returns (address holder);

Parameters

NameTypeDescription
owneraddressToken owner that signed the permit.
tokenaddressERC-20 token address.
subaccountaddressSubaccount that will spend from the allowance.
quotaAmountuint256Per-timeframe allowance amount.
quotaTimeframeuint256Seconds per allowance reset window.
initialFunduint256Optional initial transfer executed by the holder constructor.
firstCallDatabytesOptional calldata executed on the subaccount during holder construction.
permitDeadlineuint256Deadline used in the ERC-2612 permit.
vuint8Signature v component of the permit.
rbytes32Signature r component of the permit.
sbytes32Signature s component of the permit.

Returns

NameTypeDescription
holderaddressThe AllowanceHolder address (newly deployed or existing).

createSubaccount

Deploys a new Safe-based subaccount for owner under appDomain.

function createSubaccount(address owner, string calldata appDomain, uint32 accountIndex)
    public
    returns (address subaccount);

Parameters

NameTypeDescription
owneraddressThe future owner of the subaccount.
appDomainstringApplication domain to namespace the subaccount (e.g. "my.cool.app").
accountIndexuint32Sequential index chosen by the owner (use 0 for the first account).

Returns

NameTypeDescription
subaccountaddressThe deployed Safe proxy address (or the existing one if already deployed).

generateSubaccountAddress

Predicts the deterministic address of a subaccount without deploying it.

function generateSubaccountAddress(address owner, string calldata appDomain, uint32 accountIndex)
    public
    view
    returns (address subaccount, bool isDeployed);

Parameters

NameTypeDescription
owneraddressThe owner that will control the subaccount.
appDomainstringApplication domain used to namespace the subaccount.
accountIndexuint32Sequential index for the domain chosen by the owner.

Returns

NameTypeDescription
subaccountaddressPredicted Safe proxy address.
isDeployedboolBoolean indicating whether the subaccount already exists.

generateAllowanceHolderAddress

Predicts the AllowanceHolder address for a given (owner, token) pair.

function generateAllowanceHolderAddress(
    address owner,
    address token,
    address subaccount,
    uint256 quotaAmount,
    uint256 timeframe,
    uint256 initialFund,
    bytes memory firstCallData
) public view returns (address holder, bool isDeployed);

Parameters

NameTypeDescription
owneraddressThe token owner.
tokenaddressERC-20 token address.
subaccountaddressSubaccount that will draw from the allowance.
quotaAmountuint256Quota amount per timeframe (passed to constructor).
timeframeuint256Quota reset window in seconds.
initialFunduint256Optional initial transfer executed by the holder constructor.
firstCallDatabytesOptional calldata executed on subaccount by the holder constructor.

Returns

NameTypeDescription
holderaddressPredicted AllowanceHolder address.
isDeployedboolWhether the holder is already deployed.

_executePermit

Executes an unlimited permit using native ERC-2612 if available, otherwise falls back to Uniswap Permit2.

Uses SafeTransferLib.permit2, which first attempts token.permit and gracefully falls back to Permit2 if the token does not support ERC-2612 (or the call fails).

function _executePermit(address owner, address token, address spender, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
    internal;

Parameters

NameTypeDescription
owneraddressToken owner that signed the permit.
tokenaddressERC-20 token address.
spenderaddressAddress that receives unlimited allowance (the AllowanceHolder).
deadlineuint256Permit deadline (same as in signature).
vuint8Signature v value.
rbytes32Signature r value.
sbytes32Signature s value.

_deployAllowanceHolder

Performs the deterministic deployment of the AllowanceHolder using CREATE2.

function _deployAllowanceHolder(
    address owner,
    address token,
    address subaccount,
    uint256 quotaAmount,
    uint256 quotaTimeframe,
    uint256 initialFund,
    bytes calldata firstCallData,
    address predictedHolder
) internal returns (address holder);

Parameters

NameTypeDescription
owneraddressToken owner.
tokenaddressERC-20 token managed by the holder.
subaccountaddressSubaccount that will draw from the allowance.
quotaAmountuint256Per-timeframe allowance.
quotaTimeframeuint256Time window for resets.
initialFunduint256Optional immediate transfer.
firstCallDatabytesOptional call executed on subaccount.
predictedHolderaddressPre-computed address where the holder must be deployed.

Returns

NameTypeDescription
holderaddressDeployed AllowanceHolder address (equal to predictedHolder).

_constructInitializer

Constructs the initializer data for Safe setup.

This prepares the calldata for Safe.setup, setting the owner, threshold, and enabling the module.

function _constructInitializer(address owner) internal view returns (bytes memory);

Parameters

NameTypeDescription
owneraddressThe address to be set as the sole owner of the Safe.

Returns

NameTypeDescription
<none>bytesThe ABI-encoded initializer data for Safe.setup.

_generateSaltNonce

Converts an app domain string and account index to a salt nonce.

Uses NameCoder to encode and namehash the domain.

function _generateSaltNonce(string calldata appDomain, uint32 accountIndex) internal pure returns (uint256);

Parameters

NameTypeDescription
appDomainstringThe application domain string (e.g., "my.cool.app").
accountIndexuint32The index of the account in the app domain.

Returns

NameTypeDescription
<none>uint256The salt nonce.

_salt

Computes the salt used for CREATE2 deployments.

function _salt(
    address owner,
    address token,
    address manager,
    address subaccount,
    uint256 amount,
    uint256 timeframe,
    uint256 initialFund,
    bytes32 firstCallHash
) internal pure returns (bytes32);

Parameters

NameTypeDescription
owneraddressToken owner.
tokenaddressERC-20 token.
manageraddressAllowanceManager address.
subaccountaddressSubaccount address.
amountuint256Allowance amount.
timeframeuint256Timeframe seconds for resets.
initialFunduint256Optional initial fund.
firstCallHashbytes32Keccak256 hash of firstCallData.

Returns

NameTypeDescription
<none>bytes32salt Bytes32 salt value.

_creationCode

Builds the creation bytecode for the AllowanceHolder constructor params.

function _creationCode(
    address owner,
    address token,
    address manager,
    address subaccount,
    uint256 amount,
    uint256 timeframe,
    uint256 initialFund,
    bytes memory firstCallData
) internal pure returns (bytes memory);

Parameters

NameTypeDescription
owneraddressToken owner.
tokenaddressERC-20 token.
manageraddressAllowanceManager address.
subaccountaddressSubaccount.
amountuint256Allowance amount.
timeframeuint256Timeframe seconds.
initialFunduint256Initial fund amount.
firstCallDatabytesCalldata executed on subaccount.

Returns

NameTypeDescription
<none>bytescreationCode ABI encoded constructor + bytecode snippet.

_computeAddress

Computes the deterministic contract address as in EIP-1014.

function _computeAddress(
    address deployer,
    address owner,
    address token,
    address manager,
    address subaccount,
    uint256 amount,
    uint256 timeframe,
    uint256 initialFund,
    bytes memory firstCallData
) internal pure returns (address);

Parameters

NameTypeDescription
deployeraddressAddress performing the CREATE2.
owneraddressToken owner (used in salt).
tokenaddressERC-20 token.
manageraddressAllowanceManager address.
subaccountaddressSubaccount.
amountuint256Allowance amount.
timeframeuint256Timeframe seconds.
initialFunduint256Initial fund amount.
firstCallDatabytesEncoded first call data.

Returns

NameTypeDescription
<none>addressaddr Predicted contract address.

_ownersArray

Helper to create a single-element owners array in memory.

Used internally to format the input for Safe.setup.

function _ownersArray(address owner) private pure returns (address[] memory owners);

Parameters

NameTypeDescription
owneraddressThe address to include in the array.

Returns

NameTypeDescription
ownersaddress[]A dynamic array containing only the owner address.

Events

SubaccountCreated

Emitted after a successful subaccount deployment.

event SubaccountCreated(
    address indexed subaccount, address indexed owner, string indexed appDomain, uint32 accountIndex
);

Parameters

NameTypeDescription
subaccountaddressThe deployed Safe proxy address.
owneraddressThe owner address passed to the subaccount.
appDomainstringThe app-domain string that namespaced this subaccount.
accountIndexuint32The sequential index chosen by the owner for this domain.

AllowanceHolderCreated

Emitted when the canonical AllowanceHolder for (owner,token) has been deployed.

event AllowanceHolderCreated(address indexed holder, address indexed owner, address indexed token);

Parameters

NameTypeDescription
holderaddressDeployed AllowanceHolder address.
owneraddressThe token owner that signed the pull-permit.
tokenaddressThe ERC-20 token the holder controls.