SubaccountFactory
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
| Name | Type | Description |
|---|---|---|
_safeSingleton | address | Address of the Safe singleton (logic contract). |
_safeProxyFactory | address | Address of the SafeProxyFactory. |
_safeModuleSetup | address | Address of the helper contract for module setup. |
_multichainSignaturesModule | address | Address of the MultiChainSignaturesModule to enable. |
_allowanceManager | address | Address 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
| Name | Type | Description |
|---|---|---|
owner | address | Token owner that signed the permit. |
token | address | ERC-20 token address. |
subaccount | address | Subaccount that will spend from the allowance. |
quotaAmount | uint256 | Per-timeframe allowance amount. |
quotaTimeframe | uint256 | Seconds per allowance reset window. |
initialFund | uint256 | Optional initial transfer executed by the holder constructor. |
firstCallData | bytes | Optional calldata executed on the subaccount during holder construction. |
permitDeadline | uint256 | Deadline used in the ERC-2612 permit. |
v | uint8 | Signature v component of the permit. |
r | bytes32 | Signature r component of the permit. |
s | bytes32 | Signature s component of the permit. |
Returns
| Name | Type | Description |
|---|---|---|
holder | address | The 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
| Name | Type | Description |
|---|---|---|
owner | address | The future owner of the subaccount. |
appDomain | string | Application domain to namespace the subaccount (e.g. "my.cool.app"). |
accountIndex | uint32 | Sequential index chosen by the owner (use 0 for the first account). |
Returns
| Name | Type | Description |
|---|---|---|
subaccount | address | The 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
| Name | Type | Description |
|---|---|---|
owner | address | The owner that will control the subaccount. |
appDomain | string | Application domain used to namespace the subaccount. |
accountIndex | uint32 | Sequential index for the domain chosen by the owner. |
Returns
| Name | Type | Description |
|---|---|---|
subaccount | address | Predicted Safe proxy address. |
isDeployed | bool | Boolean 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
| Name | Type | Description |
|---|---|---|
owner | address | The token owner. |
token | address | ERC-20 token address. |
subaccount | address | Subaccount that will draw from the allowance. |
quotaAmount | uint256 | Quota amount per timeframe (passed to constructor). |
timeframe | uint256 | Quota reset window in seconds. |
initialFund | uint256 | Optional initial transfer executed by the holder constructor. |
firstCallData | bytes | Optional calldata executed on subaccount by the holder constructor. |
Returns
| Name | Type | Description |
|---|---|---|
holder | address | Predicted AllowanceHolder address. |
isDeployed | bool | Whether 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
| Name | Type | Description |
|---|---|---|
owner | address | Token owner that signed the permit. |
token | address | ERC-20 token address. |
spender | address | Address that receives unlimited allowance (the AllowanceHolder). |
deadline | uint256 | Permit deadline (same as in signature). |
v | uint8 | Signature v value. |
r | bytes32 | Signature r value. |
s | bytes32 | Signature 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
| Name | Type | Description |
|---|---|---|
owner | address | Token owner. |
token | address | ERC-20 token managed by the holder. |
subaccount | address | Subaccount that will draw from the allowance. |
quotaAmount | uint256 | Per-timeframe allowance. |
quotaTimeframe | uint256 | Time window for resets. |
initialFund | uint256 | Optional immediate transfer. |
firstCallData | bytes | Optional call executed on subaccount. |
predictedHolder | address | Pre-computed address where the holder must be deployed. |
Returns
| Name | Type | Description |
|---|---|---|
holder | address | Deployed 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
| Name | Type | Description |
|---|---|---|
owner | address | The address to be set as the sole owner of the Safe. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes | The 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
| Name | Type | Description |
|---|---|---|
appDomain | string | The application domain string (e.g., "my.cool.app"). |
accountIndex | uint32 | The index of the account in the app domain. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | The 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
| Name | Type | Description |
|---|---|---|
owner | address | Token owner. |
token | address | ERC-20 token. |
manager | address | AllowanceManager address. |
subaccount | address | Subaccount address. |
amount | uint256 | Allowance amount. |
timeframe | uint256 | Timeframe seconds for resets. |
initialFund | uint256 | Optional initial fund. |
firstCallHash | bytes32 | Keccak256 hash of firstCallData. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes32 | salt 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
| Name | Type | Description |
|---|---|---|
owner | address | Token owner. |
token | address | ERC-20 token. |
manager | address | AllowanceManager address. |
subaccount | address | Subaccount. |
amount | uint256 | Allowance amount. |
timeframe | uint256 | Timeframe seconds. |
initialFund | uint256 | Initial fund amount. |
firstCallData | bytes | Calldata executed on subaccount. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes | creationCode 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
| Name | Type | Description |
|---|---|---|
deployer | address | Address performing the CREATE2. |
owner | address | Token owner (used in salt). |
token | address | ERC-20 token. |
manager | address | AllowanceManager address. |
subaccount | address | Subaccount. |
amount | uint256 | Allowance amount. |
timeframe | uint256 | Timeframe seconds. |
initialFund | uint256 | Initial fund amount. |
firstCallData | bytes | Encoded first call data. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | addr 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
| Name | Type | Description |
|---|---|---|
owner | address | The address to include in the array. |
Returns
| Name | Type | Description |
|---|---|---|
owners | address[] | 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
| Name | Type | Description |
|---|---|---|
subaccount | address | The deployed Safe proxy address. |
owner | address | The owner address passed to the subaccount. |
appDomain | string | The app-domain string that namespaced this subaccount. |
accountIndex | uint32 | The 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
| Name | Type | Description |
|---|---|---|
holder | address | Deployed AllowanceHolder address. |
owner | address | The token owner that signed the pull-permit. |
token | address | The ERC-20 token the holder controls. |