Skip to main content

Prerequisites

Ensure you have Foundry installed:
curl -L https://foundry.paradigm.xyz | bash
foundryup
Navigate to the contracts directory:
cd contracts

Environment Variables

The deployment script requires the following environment variables:
SIGNER_ADDRESS
address
required
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.
BASE_SEPOLIA_RPC_URL
string
RPC endpoint for Base Sepolia testnet.Example: https://sepolia.base.org
BASE_MAINNET_RPC_URL
string
RPC endpoint for Base mainnet.Example: https://mainnet.base.org
BASESCAN_API_KEY
string
Basescan API key for contract verification.Get one at basescan.org/apis
DEPLOYER_PRIVATE_KEY
hex
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.
1

Start Anvil

In a separate terminal, start a local Ethereum node:
anvil
Anvil will start on http://127.0.0.1:8545 and display 10 test accounts with private keys.
2

Set Signer Address

Use Anvil account #1 as the signer for testing:
export SIGNER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
3

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.
4

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.
1

Get Testnet ETH

Get Base Sepolia ETH from a faucet:
2

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>"
3

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.
4

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.
1

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!
2

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>"
3

Deploy to Mainnet

forge script script/Deploy.s.sol:DeployFishnetWallet \
  --rpc-url base_mainnet \
  --private-key $DEPLOYER_PRIVATE_KEY \
  --broadcast \
  --verify
4

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:
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:
1

Read Signer Address

script/Deploy.s.sol:9
address signerAddress = vm.envAddress("SIGNER_ADDRESS");
Reads SIGNER_ADDRESS from environment. Reverts if not set.
2

Deploy Contract

script/Deploy.s.sol:13
FishnetWallet wallet = new FishnetWallet(signerAddress);
Deploys the contract with the signer address. msg.sender becomes the owner.
3

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);
4

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_ADDRESS> --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_ADDRESS> "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

The deployment script requires SIGNER_ADDRESS to be set:
export SIGNER_ADDRESS=0x...
Get your signer address from the Fishnet server status endpoint.
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_ADDRESS> \
  src/FishnetWallet.sol:FishnetWallet \
  --constructor-args $(cast abi-encode "constructor(address)" $SIGNER_ADDRESS) \
  --etherscan-api-key $BASESCAN_API_KEY \
  --chain base-sepolia
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

Build docs developers (and LLMs) love