Solidity smart contracts built with Foundry. Part of the FlowForge stack—deployables and on-chain logic used by the backend and frontend (e.g. Safe factory, Safe module, relay).
| Contract(s) | Chain(s) | Note |
|---|---|---|
| FlowForgeSafeFactory, FlowForgeSafeModule | Arbitrum Sepolia, Arbitrum One (and optionally Ethereum) | Your app chain(s). Deploy where you run relay and DeFi. |
| FlowForgeSubdomainRegistry, FlowForgeEthUsdcPricer | Ethereum mainnet only | ENS lives on Ethereum L1; subdomains (e.g. alice.flowforge.eth) are registered there. Cannot be deployed on Arbitrum. |
Arbitrum-only product: Deploy Safe factory + module on Arbitrum Sepolia and Arbitrum One only (use script 1 L2 with ARB_RPC_URL and ARB_SAFE_* for each). Deploy the ENS registry + pricer once on Ethereum mainnet so users can claim subdomains; your backend reads subdomain expiry from Ethereum and grants sponsorship on Arbitrum.
contracts/
├── src/ # Solidity sources
├── script/ # Deployment scripts (Forge scripts)
├── test/ # Forge tests
├── lib/ # Dependencies (forge-std, safe-contracts, openzeppelin-contracts)
├── foundry.toml # Foundry config
└── foundry.lockPrerequisites: Foundry (Forge, Cast, Anvil, Chisel)
# Install dependencies (git submodules)
forge installCopy env example and set variables used by the deploy scripts:
cp .env.example .env
# Edit .env: set PRIVATE_KEY, RPC_URL, EXECUTOR_ADDRESS, and RELAYER_ADDRESS (see below)forge script script/1_deployFlowForgeSafeContracts.s.sol:DeployFlowForgeSafeContracts --broadcastIf the combined script is not supported due to multiple forks, run per chain:
forge script script/1_deployFlowForgeSafeContracts.s.sol:DeployFlowForgeSafeContractsL1 --broadcast
forge script script/1_deployFlowForgeSafeContracts.s.sol:DeployFlowForgeSafeContractsL2 --broadcastRelayer must be factory owner: createSafeWallet is onlyOwner. The backend relayer sends the create-Safe tx, so it must be the owner. The deploy script uses CREATE3; with CREATE3, msg.sender in the factory constructor is the CREATE3 proxy (not your EOA), so you must set RELAYER_ADDRESS in .env to your relayer EOA (the address from RELAYER_PRIVATE_KEY). The script passes it as the factory’s initial owner—no transferOwnership step needed.
If you already deployed with an older script (no RELAYER_ADDRESS), the factory owner is the CREATE3 proxy and you cannot transfer from it. Redeploy using the current script (with RELAYER_ADDRESS set); the factory uses salt v1_1 so it gets a new address. Update contracts/deployments.json and the backend chain config with the new factory address.
Requires ENS_NAME_WRAPPER, USDC_ADDRESS, CHAINLINK_ETH_USD_FEED, and ETH_RPC_URL in .env (see .env.example).
forge script script/2_deployFlowForgeSubdomainRegistry.s.sol:DeployFlowForgeEnsRegistryAndPricer --broadcastDeploys FlowForgeSubdomainRegistry and FlowForgeEthUsdcPricer (2 USDC per 4 weeks; ETH or USDC via Chainlink). After deployment:
- As owner of the wrapped parent name (e.g.
flowforge.eth), call Name Wrapper:setApprovalForAll(registry, true). - Call Registry:
setupDomain(parentNode, pricer, beneficiary, true).
Users pay in ETH or USDC for the same expiry (e.g. 5 USDC or equivalent ETH ⇒ 10 weeks). Use registry.registerWithToken(..., address(0)) to pay in ETH, registerWithToken(..., USDC) to pay in USDC; same for renewWithToken and batch variants.
The registry supports Option A from the ENS gas-sponsorship plan: users register/renew subdomains; expiry gates off-chain sponsorship (e.g. remaining_sponsored_txs).
To support only Arbitrum Sepolia and Arbitrum One, run the L2 script once per chain (no L1 Safe deploy needed):
# Arbitrum Sepolia: set ARB_RPC_URL and ARB_SAFE_PROXY_FACTORY / ARB_SAFE_SINGLETON for Sepolia
forge script script/1_deployFlowForgeSafeContracts.s.sol:DeployFlowForgeSafeContractsL2 --broadcast
# Arbitrum One: switch .env to mainnet ARB_* and run again
forge script script/1_deployFlowForgeSafeContracts.s.sol:DeployFlowForgeSafeContractsL2 --broadcastENS registry and pricer stay on Ethereum mainnet (see Chains above).