A Python wrapper for Foundry's forge scripts with dynamic interface generation and Safe transaction support.
- Create, sign, and queue Safe multisig transactions using Foundry scripts
- Dynamic interface generation for Foundry scripts
- Cache and manage interfaces for reuse
Astral's uv is the most convenient way to install and run safesmith. You're up and running immediately with the following commands:
# Install uv if you don't have it
curl -LsSf https://astral.sh/uv/install.sh | sh
# Run safesmith commands directly
uvx safesmith --help
This creates a temporary isolated environment with all dependencies installed.
You can create an alias for uvx safesmith
to make it easier to call in your shell. These docs will assume an alias of ss
has been set. For example:
ss --help
To initialize a new safesmith project, run the init command at your project root:
ss init
This will:
- Check if you're in a Foundry project (has
script
andsrc
directories) - Create a
safesmith.toml
configuration file - Add
safesmith.toml
to.gitignore
if it exists, or create one
After initialization, populate the .toml configuration with your project's Safe information, including the Safe address, your personal address with proposer permissions, and the alias it can be loaded from from your cast wallet.
# /path/to/project/safesmith.toml
[foundry]
interfaces_path = "~/.safesmith/interfaces"
rpc_url = "https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"
[safe]
safe_address = "0x1234...5678"
safe_proposer = "0xabcd...ef01"
proposer_alias = "my_alias_in_cast_wallet"
chain_id = 1
You can also use the config set
command to create or update values within your project-level configuration.
# Set project-level configuration values
ss config set safe.safe_address 0x1234...5678
ss config set rpc.url https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY
safesmith uses a multi-level configuration system with the following priority (highest to lowest):
- Command-line arguments
- Environment variables (prefixed with
SAFESMITH_
) - Project-local config file (
./safesmith.toml
) - Global config file (
~/.safesmith/config.toml
)
The global configuration is created automatically on first run and stored at ~/.safesmith.config.toml
.
You can also edit the global config file directly.
All settings can be overridden with environment variables prefixed with SAFESMITH_
, for example:
# Nested settings use double underscores between section and key
export SAFESMITH_WRAP_RPC__URL="https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"
export SAFESMITH_WRAP_SAFE__SAFE_ADDRESS="0x1234...5678"
export SAFESMITH_WRAP_SAFE__PROPOSER="0xabcd...ef01"
safesmith provides several commands for working with scripts and interfaces:
ss run SCRIPT
: Run a Foundry script with dynamic interface handling (alias forsafe
)ss list
: List all cached interfacesss clear-cache
: Clear the global interface cachess config
: Display and edit configurationss delete NONCE
: Delete a pending Safe transaction by noncess process-interfaces SCRIPT
: Process interface directives in a scriptss sync-presets
: Synchronize interface presets with latest from user directory
# Post Safe transaction
ss run script/MyScript.s.sol --post
# Post Safe transaction with custom nonce
ss run script/MyScript.s.sol --nonce 42 --post
# Delete transaction with nonce 42 from Safe queue
ss delete 42
# List all cached interfaces
ss list
# Update presets when you've added new interface files
ss sync-presets
safesmith introduces "Interface Directives" to automatically fetch and generate correct Solidity interfaces for your scripts. This feature streamlines interaction with external contracts without manual interface work.
Interface directives are special annotations in your Solidity script that safesmith processes before compilation. When safesmith encounters these directives, it:
- Identifies all
@InterfaceName
directives in your script - Generates or retrieves the appropriate interface files
- Injects imports for these interfaces into your script
- Replaces the directive syntax with the actual interface names
safesmith supports two types of interface directives:
The format is @InterfaceName(0xAddress)
where:
InterfaceName
is your chosen name for the interface (must start with a capital letter)0xAddress
is the contract address to fetch the ABI from
Example:
// This directive tells safesmith to generate an interface for the DAI token
contract MyScript is Script {
@DAI public daiToken = @DAI(0x6B175474E89094C44Da98b954EedeAC495271d0F);
function run() public {
// After processing, daiToken will be a valid interface to the DAI contract
daiToken.transfer(recipient, amount);
}
}
The format is simply @InterfaceName
(without an address) where:
InterfaceName
matches a known preset interface
Example:
// This directive uses the built-in IERC20 preset
contract MyScript is Script {
@IERC20 public token = @IERC20(tokenAddress);
function run() external {
// token will use the standard IERC20 interface
token.approve(spender, amount);
}
}
A valid directive must follow these rules:
- Must start with
@
followed by a single capital letter (e.g.,@IERC20
) - Must be within contract body code, not in comments
- For address-based directives, must be associated with a valid Ethereum address (0x...)
safesmith comes with several common interface presets. The advantage of using presets is primarily to allow the developer to use directives on code lines which do not also contain an address literal (e.g. uint256 balance = @IERC20(token).balanceOf(address(this));
)
The following presets come as part of any safesmith install:
IERC20
- Standard ERC20 token interfaceIWETH
- Wrapped ETH interfaceIERC721
- Standard NFT interfaceIERC1155
- Multi-token standard interfaceIUniswapV2Pair
- Uniswap V2 pair interfaceIUniswapV2Router
- Uniswap V2 router interfaceIUniswapV3Pool
- Uniswap V3 pool interfaceISafe
- Gnosis Safe interface
You can use these presets without having to specify an address on the same line:
// Multiple presets in one script
contract MultiTokenScript is Script {
@IERC20 public token = @IERC20(tokenAddress);
@IWETH public weth = @IWETH(wethAddress);
@IERC721 public nft = @IERC721(nftAddress);
function run() external {
// All interfaces are properly typed
token.transfer(recipient, amount);
weth.deposit{value: 1 ether}();
nft.transferFrom(owner, recipient, tokenId);
}
}
You can create your own preset interfaces in ~/.safesmith/presets/
directory. Any .sol
file added there will be available as a preset.
Remember to run ss sync-presets
after adding new preset files to update the preset index.
When you run a script with interface directives, safesmith creates:
interfaces/