Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ out/

# Dotenv file
.env

node_modules/
25 changes: 23 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,33 @@ forge build

### Deploy

**Option 1: Using shell script**

```bash
# Deploy to a specific network
./deploy/deploy.sh <network>

# Example
# Examples:
./deploy/deploy.sh sepolia
./deploy/deploy.sh arbitrum-sepolia
./deploy/deploy.sh base-sepolia
```

**Option 2: Using forge directly**

```bash
source .env
forge script script/Deploy.s.sol --rpc-url $RPC_URL_SEPOLIA --broadcast -vvv
```

### Verify

After deployment, verify the contract on the block explorer:

```bash
forge verify-contract <DEPLOYED_ADDRESS> src/CREATE3Factory.sol:CREATE3Factory \
--rpc-url $RPC_URL_SEPOLIA \
--etherscan-api-key $ETHERSCAN_KEY \
--watch
```

## Important Technical Details
Expand Down
58 changes: 56 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ One could use a `CREATE2` factory that deterministically deploys contracts to an

A `CREATE3` factory offers the best solution: the address of the deployed contract is determined by only the deployer address and the salt. This makes it far easier to deploy contracts to multiple chains at the same addresses.

## How This Factory is Deployed

This factory uses **CREATE2** for deployment to ensure the same factory address across all chains without nonce synchronization:

```solidity
bytes32 salt = keccak256("intmax");
factory = new CREATE3Factory{salt: salt}();
```

The factory address is determined by:
- Deployer address (from PRIVATE_KEY)
- Salt ("intmax")
- Factory bytecode (deterministic via `bytecode_hash = "none"` in foundry.toml)

**Nonce does not affect the address**, so you can deploy to new chains at any time.

## Supported Chains

| Mainnet | Testnet |
Expand Down Expand Up @@ -62,8 +78,46 @@ forge build

### Deployment

Make sure that the network is defined in foundry.toml, then run:
1. Copy `.env.example` to `.env` and fill in the values:

```bash
cp .env.example .env
```

2. Set up your environment variables:

```bash
PRIVATE_KEY=<your-deployer-private-key>
RPC_URL_SEPOLIA=<rpc-url>
ETHERSCAN_KEY=<api-key>
# ... other networks as needed
```

3. Deploy using the shell script:

```bash
./deploy/deploy.sh <network>

# Examples:
./deploy/deploy.sh sepolia
./deploy/deploy.sh arbitrum-sepolia
./deploy/deploy.sh base-sepolia
```

4. Or deploy directly with forge:

```bash
source .env
forge script script/Deploy.s.sol --rpc-url $RPC_URL_SEPOLIA --broadcast -vvv
```

### Verification

After deployment, verify the contract:

```bash
./deploy/deploy.sh [network]
forge verify-contract <DEPLOYED_ADDRESS> src/CREATE3Factory.sol:CREATE3Factory \
--rpc-url $RPC_URL_SEPOLIA \
--etherscan-api-key $ETHERSCAN_KEY \
--watch
```
85 changes: 47 additions & 38 deletions deploy/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,51 @@ deploy() {

# get deployer address
DEPLOYER_ADDRESS=$(cast wallet address "$PRIVATE_KEY")
echo "You are deploying from address: $DEPLOYER_ADDRESS (should be 0x11F11121DF7256C40339393b0FB045321022ce44 for 0x123 diamond address)"

# get balance in given network
RPC_KEY="RPC_URL_$(tr '[:lower:]' '[:upper:]' <<<"$NETWORK")"
BALANCE=$(cast balance "$DEPLOYER_ADDRESS" --rpc-url "${!RPC_KEY}")

# return formatted balance
echo "Deployer Wallet balance: $(echo "scale=10;$BALANCE / 1000000000000000000" | bc)"

echo ""
echo "@DEV: You may run into an error about verification (missing Etherscan key for chainId ... or other errors)."
echo " If you cannot fix it, remove the --verify flag and verify the contract manually afterwards. This needs to be fixed."
echo ""
# Ticket for this issue: https://lifi.atlassian.net/browse/LF-12359

RAW_RETURN_DATA=$(forge script script/Deploy.s.sol -f $NETWORK -vvvv --json --legacy --broadcast --skip-simulation --gas-limit 2000000)
RETURN_CODE=$?
echo "RAW_RETURN_DATA: $RAW_RETURN_DATA"
CLEAN_RETURN_DATA=$(echo $RAW_RETURN_DATA | sed 's/^.*{\"logs/{\"logs/')
echo "RAW_RETURN_DATA: $RAW_RETURN_DATA"
RETURN_DATA=$(echo $CLEAN_RETURN_DATA | jq -r '.returns' 2>/dev/null)
echo ""
echo "RETURN_DATA: $RETURN_DATA"
echo "Deployer address: $DEPLOYER_ADDRESS"

# get RPC URL for the network
RPC_KEY="RPC_URL_$(echo "$NETWORK" | tr '[:lower:]' '[:upper:]' | tr '-' '_')"
RPC_URL="${!RPC_KEY}"

if [[ -z "$RPC_URL" ]]; then
echo "❌ Error: RPC URL not found for network: $NETWORK"
echo " Please set $RPC_KEY in your .env file"
exit 1
fi

# get balance
BALANCE=$(cast balance "$DEPLOYER_ADDRESS" --rpc-url "$RPC_URL")
echo "Balance: $(echo "scale=6;$BALANCE / 1000000000000000000" | bc) ETH"
echo ""

# deploy
echo "Deploying to $NETWORK..."
RAW_RETURN_DATA=$(forge script script/Deploy.s.sol --rpc-url "$RPC_URL" --broadcast --json 2>&1)
RETURN_CODE=$?

if [[ $RETURN_CODE -ne 0 ]]; then
echo "❌ Error: deployment was not successful"
echo "$RAW_RETURN_DATA"
exit 1
fi

FACTORY_ADDRESS=$(echo $RETURN_DATA | jq -r '.factory.value')
echo "✅ Successfully deployed to address $FACTORY_ADDRESS"
# extract factory address
CLEAN_RETURN_DATA=$(echo "$RAW_RETURN_DATA" | grep -o '{.*}' | tail -1)
FACTORY_ADDRESS=$(echo "$CLEAN_RETURN_DATA" | jq -r '.returns.factory.value' 2>/dev/null)

# verify contract
API_KEY="$(tr '[:lower:]' '[:upper:]' <<<$NETWORK)_API_KEY"
API_KEY="${!API_KEY}"
echo ""
# not working as intended, we need to fix this
# echo "Trying to verify contract now with API key: $API_KEY"
# forge verify-contract "$FACTORY_ADDRESS" src/CREATE3Factory.sol:CREATE3Factory --watch --etherscan-api-key "$API_KEY" --chain "$NETWORK"
echo ""
if [[ -z "$FACTORY_ADDRESS" || "$FACTORY_ADDRESS" == "null" ]]; then
echo "⚠️ Could not parse factory address from output"
echo "Please check the broadcast folder for the deployed address"
else
echo "✅ Successfully deployed to address: $FACTORY_ADDRESS"

echo ""
echo "Creating deploy log"
saveContract $NETWORK CREATE3Factory $FACTORY_ADDRESS
# save to deployments
saveContract "$NETWORK" "CREATE3Factory" "$FACTORY_ADDRESS"
fi

echo "✅ Deployment successfully completed"
echo ""
echo "To verify the contract, run:"
echo " forge verify-contract $FACTORY_ADDRESS src/CREATE3Factory.sol:CREATE3Factory --rpc-url \$RPC_URL_$(echo "$NETWORK" | tr '[:lower:]' '[:upper:]' | tr '-' '_') --etherscan-api-key <API_KEY> --watch"
}

saveContract() {
Expand All @@ -70,6 +68,17 @@ saveContract() {
fi
result=$(cat "$ADDRESSES_FILE" | jq -r ". + {\"$CONTRACT\": \"$ADDRESS\"}")
printf %s "$result" >"$ADDRESSES_FILE"
echo "📝 Saved to $ADDRESSES_FILE"
}

deploy $1
# check if network argument is provided
if [[ -z "$1" ]]; then
echo "Usage: ./deploy/deploy.sh <network>"
echo ""
echo "Available networks:"
echo " Mainnet: mainnet, arbitrum, base, bsc, scroll"
echo " Testnet: sepolia, arbitrum-sepolia, base-sepolia, bsc-testnet, scroll-sepolia"
exit 1
fi

deploy "$1"
Loading