Deploy Your First Contract

Deploy a vulnerable vault to BattleChain, create a Safe Harbor agreement, and open it for attack.

🏛
You are the Protocol
Your contract is audited. Time to stress-test it for real.
You'll deploy a deliberately vulnerable vault, put it under a Safe Harbor agreement, and open it to whitehat attack — before anything like this can happen on mainnet.

Prerequisites

Git

git --version

If not installed: git-scm.com

Foundry

curl -L https://foundry.paradigm.xyz | bash
foundryup
forge --version

If you hit issues, see the Foundry installation guide.

Just

just is a command runner used to execute the repo's scripts cleanly. Install it with:

brew install just

Verify: just --version

A Wallet with BattleChain Testnet ETH

You'll need an EVM wallet with ETH on BattleChain to pay for gas. Three steps:

1. Add BattleChain to your wallet

FieldValue
Network NameBattleChain Testnet
RPC URLhttps://testnet.battlechain.com:3051
Chain ID627
â„šī¸

See Add BattleChain to MetaMask for a step-by-step guide.

2. Get Sepolia ETH

You need Sepolia ETH to bridge to BattleChain. Go to the Google Cloud faucet, paste your wallet address, and request testnet ETH. You only need a small amount — 0.05 ETH is plenty for this tutorial.

3. Bridge to BattleChain

Open the BattleChain Portal, connect your wallet, and bridge Sepolia ETH to BattleChain Testnet:

  1. Select Sepolia as the source network and BattleChain Testnet as the destination
  2. Enter the amount to bridge
  3. Confirm the transaction in your wallet

Wait for the bridge transaction to finalize — this usually takes a few minutes. Once your BattleChain balance shows ETH, you're ready to deploy.


Setup

Clone the starter repo and install dependencies:

git clone https://github.com/Cyfrin/battlechain-starter
cd battlechain-starter
forge install

Import your key into Foundry's keystore

Never put your private key in a file. Instead, import it into Foundry's encrypted keystore:

cast wallet import battlechain --interactive

You'll be prompted to paste your private key and set an encryption password. The key is stored encrypted at ~/.foundry/keystores/battlechain — the password is required every time you run a script. Your raw private key is never written to disk.

Verify it imported correctly:

cast wallet list

Configure .env

cp .env.example .env

Open .env and set your values. Note that SENDER_ADDRESS is your public wallet address — no private key goes here:

# Your public wallet address
SENDER_ADDRESS=0x...your_wallet_address...

# Filled in after: just setup
TOKEN_ADDRESS=
VAULT_ADDRESS=

# Filled in after: just create-agreement
AGREEMENT_ADDRESS=

# Set to your wallet address (same as SENDER_ADDRESS for this tutorial)
RECOVERY_ADDRESS=

What You're Deploying

Before running anything, understand what you're putting on-chain. Open src/VulnerableVault.sol and look at withdrawAll():

function withdrawAll() external {
    uint256 amount = balances[msg.sender];
    require(amount > 0, "nothing to withdraw");

    // ❌ INTERACTION before EFFECT
    TOKEN.transfer(msg.sender, amount); // external call — may trigger a hook

    // ❌ Balance cleared after the transfer — too late
    balances[msg.sender] = 0;
}

This is a CEI (Checks-Effects-Interactions) violation. The vault transfers tokens before zeroing the caller's balance. If the token notifies the recipient on transfer — which MockToken does via its hook system — an attacker can re-enter withdrawAll() before the balance ever hits zero, draining the vault entirely.

The correct order is:

// ✅ Effect first, then interaction
balances[msg.sender] = 0;
TOKEN.transfer(msg.sender, amount);

You've shipped the wrong version. That's the point.


Step 1 — Deploy

âš ī¸

Known issue: out-of-gas failures

Forge estimates gas locally rather than via an RPC call, which can significantly underestimate costs on BattleChain. Transactions may fail with a generic error that doesn't mention gas.

If any just command fails unexpectedly, retry with one of these workarounds:

  • -g 300 — tells Forge to use 3× the estimated gas: forge script ... -g 300
  • --skip-simulation — skips gas estimation entirely: forge script ... --skip-simulation

If the justfile doesn't expose these flags directly, add the flag to the underlying forge script call in the relevant justfile recipe.

just setup deploys MockToken and VulnerableVault, then seeds the vault with 1,000 tokens to simulate protocol liquidity:

just setup

Expected output:

MockToken deployed: 0x...
VulnerableVault deployed: 0x...
Vault seeded with 1000 tokens

--- Add to your .env ---
TOKEN_ADDRESS=0x...
VAULT_ADDRESS=0x...

Copy both addresses into your .env.

✅

Your vault is live on BattleChain and registered with the AttackRegistry.

â„šī¸

To verify your contracts on the block explorer, see Verifying Contracts.


Step 2 — Create a Safe Harbor Agreement

The Safe Harbor agreement defines the rules of engagement: which contracts are in scope, what bounty whitehats earn, and where recovered funds go.

Make sure VAULT_ADDRESS is set in .env, then:

just create-agreement

This script:

  • Puts your vault in scope on BattleChain (eip155:627)
  • Sets a 10% bounty, retainable from recovered funds
  • Sets your wallet as the recovery address — drained funds come back here
  • Locks terms for 30 days

Expected output:

Agreement created: 0x...
Commitment window extended 30 days
Safe Harbor adopted

--- Add to your .env ---
AGREEMENT_ADDRESS=0x...

Copy AGREEMENT_ADDRESS into your .env and set RECOVERY_ADDRESS to your deployer wallet address.


Step 3 — Request Attack Mode

Submit your vault for DAO review:

just request-attack-mode

Your agreement is now in ATTACK_REQUESTED state. The DAO will verify:

  • This is a new contract, not a copy of a live mainnet deployment
  • Bounty terms are reasonable
  • Scope is clearly defined

Poll for approval:

just check-state
OutputMeaning
2ATTACK_REQUESTED — waiting for DAO
3UNDER_ATTACK — approved, vault is open
â„šī¸

Approval on testnet typically happens within a few hours. Reach out in Discord if you're waiting.

Once you see 3, your vault is live and attackable. Head to Execute Your First Attack to see what happens next.


What You Just Accomplished

You deployed a contract, created a Safe Harbor agreement, and opened it to whitehat attack — all on a testnet built for exactly this workflow.

This matters beyond BattleChain. Safe Harbor agreements protect protocols and whitehats on mainnet too. Every production contract holding real funds benefits from a clear agreement that defines scope, bounties, and recovery rules before something goes wrong. The process you just walked through is the same one you'd follow to protect a live deployment.

You now know how to:

  • Deploy contracts to a custom chain with Foundry
  • Import keys securely using cast wallet import (no plaintext private keys)
  • Create and configure a Safe Harbor agreement on-chain
  • Register contracts for coordinated security testing
✅

You've deployed your first contract to BattleChain and set up a Safe Harbor agreement. These are skills you'll use on mainnet — not just here.