Prerequisites
Ensure you have Foundry installed:
curl -L https://foundry.paradigm.xyz | bash
foundryup
Navigate to the contracts directory:
Environment Variables
The deployment script requires the following environment variables:
Address of the Fishnet backend signer. This address will be authorized to sign permits. Get this from your Fishnet server’s /api/onchain/status endpoint.
RPC endpoint for Base Sepolia testnet. Example : https://sepolia.base.org
RPC endpoint for Base mainnet. Example : https://mainnet.base.org
Private key of the deployer account (becomes the wallet owner). Never commit private keys to version control!
Local Deployment (Anvil)
Deploy to a local Anvil node for development and testing.
Start Anvil
In a separate terminal, start a local Ethereum node: Anvil will start on http://127.0.0.1:8545 and display 10 test accounts with private keys.
Set Signer Address
Use Anvil account #1 as the signer for testing: export SIGNER_ADDRESS = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
Deploy the Contract
Deploy using Anvil account #0 as the deployer: forge script script/Deploy.s.sol:DeployFishnetWallet \
--rpc-url http://127.0.0.1:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcast
The private key 0xac09...ff80 is Anvil’s default account #0. This is safe for local testing.
Verify Deployment
The script will output: FishnetWallet deployed at: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Signer: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
Owner: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Chain ID: 31337
Deployment info written to: deployments/localhost.json
The deployment details are saved to deployments/localhost.json.
Base Sepolia (Testnet)
Deploy to Base Sepolia for testnet integration.
Get Testnet ETH
Get Base Sepolia ETH from a faucet:
Set Environment Variables
Configure your deployment environment: export BASE_SEPOLIA_RPC_URL = "https://sepolia.base.org"
export SIGNER_ADDRESS = "<your-fishnet-signer-address>"
export DEPLOYER_PRIVATE_KEY = "<your-deployer-private-key>"
export BASESCAN_API_KEY = "<your-basescan-api-key>"
Deploy and Verify
Run the deployment script with verification: forge script script/Deploy.s.sol:DeployFishnetWallet \
--rpc-url base_sepolia \
--private-key $DEPLOYER_PRIVATE_KEY \
--broadcast \
--verify
The --verify flag automatically verifies the contract on Basescan after deployment.
Save Deployment Info
The script saves deployment details to deployments/base-sepolia.json: {
"wallet" : "0x..." ,
"signer" : "0x..." ,
"owner" : "0x..." ,
"chainId" : 84532 ,
"deployBlock" : 12345678 ,
"timestamp" : 1234567890
}
Base Mainnet (Production)
Production Deployment : Ensure your signer address is correct and your private key is secure before deploying to mainnet.
Double-Check Configuration
Verify your environment variables: echo "Signer: $SIGNER_ADDRESS "
echo "RPC: $BASE_MAINNET_RPC_URL "
The signer address CANNOT be changed after deployment without redeploying. Verify this is your production Fishnet signer!
Set Environment Variables
export BASE_MAINNET_RPC_URL = "https://mainnet.base.org"
export SIGNER_ADDRESS = "<production-fishnet-signer>"
export DEPLOYER_PRIVATE_KEY = "<secure-deployer-key>"
export BASESCAN_API_KEY = "<basescan-api-key>"
Deploy to Mainnet
forge script script/Deploy.s.sol:DeployFishnetWallet \
--rpc-url base_mainnet \
--private-key $DEPLOYER_PRIVATE_KEY \
--broadcast \
--verify
Verify on Basescan
After deployment, verify your contract is visible on Basescan . The deployment info is saved to deployments/base-mainnet.json.
Multi-Chain Deployments
Fishnet supports deployments across multiple chains. The deployment script automatically detects the chain and saves deployment info to the correct file:
Base Sepolia (84532)
Base Mainnet (8453)
Arbitrum Sepolia (421614)
Localhost (31337)
deployments/base-sepolia.json
The chain detection logic is in script/Deploy.s.sol:36-43:
script/Deploy.s.sol:36-43
function _getNetworkName () internal view returns ( string memory ) {
if ( block .chainid == 84532 ) return "base-sepolia" ;
if ( block .chainid == 8453 ) return "base-mainnet" ;
if ( block .chainid == 421614 ) return "arbitrum-sepolia" ;
if ( block .chainid == 42161 ) return "arbitrum-one" ;
if ( block .chainid == 31337 ) return "localhost" ;
return vm. toString ( block .chainid);
}
Deployment Script Details
The deployment script (script/Deploy.s.sol) performs the following steps:
Read Signer Address
address signerAddress = vm. envAddress ( "SIGNER_ADDRESS" );
Reads SIGNER_ADDRESS from environment. Reverts if not set.
Deploy Contract
FishnetWallet wallet = new FishnetWallet (signerAddress);
Deploys the contract with the signer address. msg.sender becomes the owner.
Log Deployment Info
script/Deploy.s.sol:14-17
console. log ( "FishnetWallet deployed at:" , address (wallet));
console. log ( "Signer:" , signerAddress);
console. log ( "Owner:" , msg.sender );
console. log ( "Chain ID:" , block .chainid);
Write JSON Deployment File
script/Deploy.s.sol:21-33
string memory networkName = _getNetworkName ();
string memory json = "deployment" ;
vm. serializeAddress (json, "wallet" , address (wallet));
vm. serializeAddress (json, "signer" , signerAddress);
vm. serializeAddress (json, "owner" , msg.sender );
vm. serializeUint (json, "chainId" , block .chainid);
vm. serializeUint (json, "deployBlock" , block .number);
string memory finalJson = vm. serializeUint (json, "timestamp" , block .timestamp);
string memory path = string . concat ( "deployments/" , networkName, ".json" );
vm. writeJson (finalJson, path);
Saves all deployment metadata to a JSON file for future reference.
Post-Deployment Steps
Update Fishnet Config Add the deployed wallet address to your Fishnet server’s config.toml: [ onchain ]
enabled = true
wallet_address = "0x..." # Deployed contract address
chain_ids = [ 84532 ] # Or [8453] for mainnet
Fund the Wallet Send ETH to the wallet for transaction execution: cast send < WALLET_ADDRES S > --value 1ether \
--private-key $YOUR_PRIVATE_KEY \
--rpc-url $BASE_SEPOLIA_RPC_URL
Verify Signer Confirm the signer address matches your Fishnet backend: cast call < WALLET_ADDRES S > "fishnetSigner()" \
--rpc-url $BASE_SEPOLIA_RPC_URL
Test Execution Run an end-to-end test to verify the deployment: bash scripts/sc3-integration-test.sh
Troubleshooting
Error: SIGNER_ADDRESS not set
The deployment script requires SIGNER_ADDRESS to be set: export SIGNER_ADDRESS = 0x ...
Get your signer address from the Fishnet server status endpoint.
Error: Insufficient funds for gas
Your deployer account needs ETH to pay for gas:
Testnet : Get ETH from a faucet
Mainnet : Send ETH to your deployer address
If Basescan verification fails, manually verify: forge verify-contract < CONTRACT_ADDRES S > \
src/FishnetWallet.sol:FishnetWallet \
--constructor-args $( cast abi-encode "constructor(address)" $SIGNER_ADDRESS ) \
--etherscan-api-key $BASESCAN_API_KEY \
--chain base-sepolia
Wrong chain ID in deployment file
The script auto-detects the chain. If deploying to a custom network, update _getNetworkName() in Deploy.s.sol.
Next Steps
Test Your Deployment Run the full test suite to verify your deployment
EIP-712 Signing Learn how to generate and sign permits