Skip to content

Commit c5e5aad

Browse files
committed
feat: add Base Token deployment
1 parent 67af5df commit c5e5aad

5 files changed

Lines changed: 299 additions & 1 deletion

File tree

claim_contracts/Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ deploy-token-testnet: ## 🚀 Deploy the token contract
4343
--private-key $(DEPLOYER_PRIVATE_KEY) \
4444
--rpc-url $(RPC_URL) \
4545
--broadcast \
46-
--verbosity 3 \
4746
--verify \
4847
--etherscan-api-key $(ETHERSCAN_API_KEY)
4948

claim_contracts/base/.env.example

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Base L2 ALIGN Token Deployment
2+
# Copy this file to .env and fill in the values
3+
4+
# Deployer private key (must have ETH on the target Base network for gas)
5+
# Used for deploying the L2 token via the factory
6+
DEPLOYER_PRIVATE_KEY=
7+
8+
# User private key (must hold ALIGN tokens on L1 and ETH for gas)
9+
# Used for bridging tokens from L1 to Base
10+
USER_PRIVATE_KEY=
11+
12+
# L1 token addresses
13+
L1_TOKEN_SEPOLIA=0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A
14+
L1_TOKEN_MAINNET=
15+
16+
# L2 token addresses
17+
L2_TOKEN_SEPOLIA=0x4AAcFbc2C31598a560b285dB20966E00B73F9F81
18+
L2_TOKEN_MAINNET=
19+
20+
# RPC URLs
21+
BASE_SEPOLIA_RPC_URL=https://sepolia.base.org
22+
BASE_MAINNET_RPC_URL=https://mainnet.base.org
23+
L1_SEPOLIA_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
24+
L1_MAINNET_RPC_URL=https://ethereum-rpc.publicnode.com

claim_contracts/base/.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Compiler files
2+
cache/
3+
out/
4+
5+
# Ignores development broadcast logs
6+
!/broadcast
7+
/broadcast/*/31337/
8+
/broadcast/**/dry-run/
9+
10+
# Docs
11+
docs/
12+
13+
# Dotenv file
14+
.env

claim_contracts/base/Makefile

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
-include .env
2+
3+
.PHONY: help deploy-base-sepolia deploy-base-mainnet verify-base-sepolia verify-base-mainnet bridge-l1-to-base-sepolia bridge-l1-to-base-mainnet
4+
5+
help: ## Show help for each of the Makefile recipes
6+
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
7+
8+
# OptimismMintableERC20Factory predeploy address (same on all OP Stack chains)
9+
FACTORY=0x4200000000000000000000000000000000000012
10+
TOKEN_NAME="Aligned Token"
11+
TOKEN_SYMBOL="ALIGN"
12+
13+
# Default values
14+
DEPLOYER_PRIVATE_KEY?=
15+
BASE_SEPOLIA_RPC_URL?=https://sepolia.base.org
16+
BASE_MAINNET_RPC_URL?=https://mainnet.base.org
17+
18+
# L1 token addresses (set these or pass via env/CLI)
19+
L1_TOKEN_SEPOLIA?=0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A
20+
L1_TOKEN_MAINNET?=0x0000000000000000000000000000000000000000
21+
22+
# L2 token addresses
23+
L2_TOKEN_SEPOLIA?=0x4AAcFbc2C31598a560b285dB20966E00B73F9F81
24+
L2_TOKEN_MAINNET?=0x0000000000000000000000000000000000000000
25+
26+
# L1 Standard Bridge addresses
27+
L1_BRIDGE_SEPOLIA=0xfd0Bf71F60660E2f608ed56e1659C450eB113120
28+
L1_BRIDGE_MAINNET=0x3154Cf16ccdb4C6d922629664174b904d80F2C35
29+
30+
# L1 RPC URLs
31+
L1_SEPOLIA_RPC_URL?=https://ethereum-sepolia-rpc.publicnode.com
32+
L1_MAINNET_RPC_URL?=https://ethereum-rpc.publicnode.com
33+
34+
# Deployments
35+
# Following: https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token#create-an-l2-erc-20-token
36+
# The deployed L2 token address is extracted from the OptimismMintableERC20Created event logs.
37+
38+
deploy-base-sepolia: ## Deploy ALIGN token on BaseSepolia via OptimismMintableERC20Factory
39+
@echo "Deploying ALIGN on BaseSepolia..."
40+
@echo "L1 Remote Token: $(L1_TOKEN_SEPOLIA)"
41+
@echo "Factory: $(FACTORY)"
42+
@L2_TOKEN=$$(cast send $(FACTORY) \
43+
"createOptimismMintableERC20(address,string,string)" \
44+
$(L1_TOKEN_SEPOLIA) $(TOKEN_NAME) $(TOKEN_SYMBOL) \
45+
--private-key $(DEPLOYER_PRIVATE_KEY) \
46+
--rpc-url $(BASE_SEPOLIA_RPC_URL) \
47+
--json | jq -r '.logs[0].topics[2]' | cast parse-bytes32-address) && \
48+
echo "---------------------------------------------" && \
49+
echo "ALIGN L2 Token deployed at: $$L2_TOKEN" && \
50+
echo "---------------------------------------------"
51+
52+
deploy-base-mainnet: ## Deploy ALIGN token on BaseMainnet via OptimismMintableERC20Factory
53+
@echo "Deploying ALIGN on BaseMainnet..."
54+
@echo "L1 Remote Token: $(L1_TOKEN_MAINNET)"
55+
@echo "Factory: $(FACTORY)"
56+
@L2_TOKEN=$$(cast send $(FACTORY) \
57+
"createOptimismMintableERC20(address,string,string)" \
58+
$(L1_TOKEN_MAINNET) $(TOKEN_NAME) $(TOKEN_SYMBOL) \
59+
--private-key $(DEPLOYER_PRIVATE_KEY) \
60+
--rpc-url $(BASE_MAINNET_RPC_URL) \
61+
--json | jq -r '.logs[0].topics[2]' | cast parse-bytes32-address) && \
62+
echo "---------------------------------------------" && \
63+
echo "ALIGN L2 Token deployed at: $$L2_TOKEN" && \
64+
echo "---------------------------------------------"
65+
66+
# Verification
67+
68+
verify-base-sepolia: ## Verify the deployed L2 token on BaseSepolia (requires L2_TOKEN)
69+
@echo "Verifying L2 token deployment on BaseSepolia..."
70+
@echo "--- Token metadata ---"
71+
cast call $(L2_TOKEN) "name()(string)" --rpc-url $(BASE_SEPOLIA_RPC_URL)
72+
cast call $(L2_TOKEN) "symbol()(string)" --rpc-url $(BASE_SEPOLIA_RPC_URL)
73+
cast call $(L2_TOKEN) "decimals()(uint8)" --rpc-url $(BASE_SEPOLIA_RPC_URL)
74+
@echo "--- Bridge configuration ---"
75+
cast call $(L2_TOKEN) "REMOTE_TOKEN()(address)" --rpc-url $(BASE_SEPOLIA_RPC_URL)
76+
cast call $(L2_TOKEN) "BRIDGE()(address)" --rpc-url $(BASE_SEPOLIA_RPC_URL)
77+
@echo "--- Total supply (should be 0 before any bridging) ---"
78+
cast call $(L2_TOKEN) "totalSupply()(uint256)" --rpc-url $(BASE_SEPOLIA_RPC_URL)
79+
80+
verify-base-mainnet: ## Verify the deployed L2 token on BaseMainnet (requires L2_TOKEN)
81+
@echo "Verifying L2 token deployment on BaseMainnet..."
82+
@echo "--- Token metadata ---"
83+
cast call $(L2_TOKEN) "name()(string)" --rpc-url $(BASE_MAINNET_RPC_URL)
84+
cast call $(L2_TOKEN) "symbol()(string)" --rpc-url $(BASE_MAINNET_RPC_URL)
85+
cast call $(L2_TOKEN) "decimals()(uint8)" --rpc-url $(BASE_MAINNET_RPC_URL)
86+
@echo "--- Bridge configuration ---"
87+
cast call $(L2_TOKEN) "REMOTE_TOKEN()(address)" --rpc-url $(BASE_MAINNET_RPC_URL)
88+
cast call $(L2_TOKEN) "BRIDGE()(address)" --rpc-url $(BASE_MAINNET_RPC_URL)
89+
@echo "--- Total supply (should be 0 before any bridging) ---"
90+
cast call $(L2_TOKEN) "totalSupply()(uint256)" --rpc-url $(BASE_MAINNET_RPC_URL)
91+
92+
# Bridging L1 -> L2
93+
# Step 1: Approve the L1StandardBridge to spend ALIGN tokens
94+
# Step 2: Deposit tokens via the bridge (tokens appear on Base after ~20 min)
95+
# Requires: AMOUNT (in wei), USER_PRIVATE_KEY
96+
AMOUNT?=1000000000000000000 # Default to 1 ALIGN (18 decimals)
97+
USER_PRIVATE_KEY?=
98+
99+
bridge-l1-to-base-sepolia: ## Bridge ALIGN tokens from Sepolia L1 to BaseSepolia (requires AMOUNT)
100+
@echo "Step 1: Approving L1StandardBridge to spend $(AMOUNT) ALIGN on Sepolia..."
101+
cast send $(L1_TOKEN_SEPOLIA) \
102+
"approve(address,uint256)" \
103+
$(L1_BRIDGE_SEPOLIA) $(AMOUNT) \
104+
--private-key $(USER_PRIVATE_KEY) \
105+
--rpc-url $(L1_SEPOLIA_RPC_URL)
106+
@echo "Step 2: Depositing $(AMOUNT) ALIGN to BaseSepolia..."
107+
cast send $(L1_BRIDGE_SEPOLIA) \
108+
"depositERC20(address,address,uint256,uint32,bytes)" \
109+
$(L1_TOKEN_SEPOLIA) $(L2_TOKEN_SEPOLIA) $(AMOUNT) 200000 0x \
110+
--private-key $(USER_PRIVATE_KEY) \
111+
--rpc-url $(L1_SEPOLIA_RPC_URL)
112+
@echo "---------------------------------------------"
113+
@echo "Bridge initiated. Tokens will appear on BaseSepolia in ~20 minutes."
114+
@echo "---------------------------------------------"
115+
116+
bridge-l1-to-base-mainnet: ## Bridge ALIGN tokens from Ethereum L1 to Base (requires AMOUNT)
117+
@echo "Step 1: Approving L1StandardBridge to spend $(AMOUNT) ALIGN on Mainnet..."
118+
cast send $(L1_TOKEN_MAINNET) \
119+
"approve(address,uint256)" \
120+
$(L1_BRIDGE_MAINNET) $(AMOUNT) \
121+
--private-key $(USER_PRIVATE_KEY) \
122+
--rpc-url $(L1_MAINNET_RPC_URL)
123+
@echo "Step 2: Depositing $(AMOUNT) ALIGN to Base..."
124+
cast send $(L1_BRIDGE_MAINNET) \
125+
"depositERC20(address,address,uint256,uint32,bytes)" \
126+
$(L1_TOKEN_MAINNET) $(L2_TOKEN_MAINNET) $(AMOUNT) 200000 0x \
127+
--private-key $(USER_PRIVATE_KEY) \
128+
--rpc-url $(L1_MAINNET_RPC_URL)
129+
@echo "---------------------------------------------"
130+
@echo "Bridge initiated. Tokens will appear on Base in ~20 minutes."
131+
@echo "---------------------------------------------"

claim_contracts/base/README.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# ALIGN Token on Base L2
2+
3+
Deployment of the Aligned Token (ALIGN) on Base L2 using the OptimismMintableERC20Factory.
4+
5+
Based on the [OP Standard Bridge Standard Token tutorial](https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token).
6+
7+
## Overview
8+
9+
Base is an OP Stack chain. The ALIGN token on Base is created via the
10+
`OptimismMintableERC20Factory` predeploy at `0x4200000000000000000000000000000000000012`.
11+
This produces a standard `OptimismMintableERC20` token that is automatically compatible
12+
with the OP Standard Bridge for L1 <-> L2 token transfers.
13+
14+
No custom Solidity contract is deployed — the factory handles everything.
15+
16+
## Prerequisites
17+
18+
- [Foundry](https://book.getfoundry.sh/getting-started/installation) (`cast` CLI)
19+
- `jq` for parsing transaction receipts
20+
- An account with ETH on the target Base network (for gas)
21+
- The L1 ALIGN token proxy address (deployed on Ethereum Sepolia or Mainnet)
22+
23+
## Setup
24+
25+
1. Copy `.env.example` to `.env` and fill in the values:
26+
27+
```bash
28+
cp .env.example .env
29+
```
30+
31+
2. Set the L1 token addresses. The Makefile defaults are:
32+
- `L1_TOKEN_SEPOLIA=0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A`
33+
- `L1_TOKEN_MAINNET=` (set when ready)
34+
35+
You can override them via `.env` or by passing them to `make`.
36+
37+
## Deployment
38+
39+
The deploy targets call `createOptimismMintableERC20` on the factory and extract the
40+
deployed L2 token address from the `OptimismMintableERC20Created` event logs, following
41+
the [OP tutorial](https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token#create-an-l2-erc-20-token).
42+
43+
### BaseSepolia (Testnet)
44+
45+
```bash
46+
source .env
47+
make deploy-base-sepolia
48+
```
49+
50+
### BaseMainnet (Production)
51+
52+
```bash
53+
source .env
54+
make deploy-base-mainnet
55+
```
56+
57+
The output will print the deployed L2 token address.
58+
59+
## Verification
60+
61+
After deployment, verify the token was created correctly:
62+
63+
```bash
64+
make verify-base-sepolia L2_TOKEN=<deployed_l2_token_address>
65+
```
66+
67+
Expected output:
68+
69+
| Check | Expected |
70+
|-------|----------|
71+
| `name()` | `"Aligned Token"` |
72+
| `symbol()` | `"ALIGN"` |
73+
| `decimals()` | `18` |
74+
| `REMOTE_TOKEN()` | L1 token proxy address |
75+
| `BRIDGE()` | `0x4200000000000000000000000000000000000010` |
76+
| `totalSupply()` | `0` (before any bridging) |
77+
78+
## Bridging Tokens (L1 -> Base)
79+
80+
After the L2 token is deployed, tokens can be bridged from L1 to Base using the
81+
OP Standard Bridge. The Makefile handles the two-step process (approve + deposit).
82+
83+
### Bridge Addresses
84+
85+
Source: [Base Contracts](https://docs.base.org/chain/base-contracts)
86+
87+
| Network | L1StandardBridge | L2StandardBridge |
88+
|----------------------|------------------|------------------|
89+
| Sepolia / BaseSepolia | [`0xfd0Bf71F60660E2f608ed56e1659C450eB113120`](https://sepolia.etherscan.io/address/0xfd0Bf71F60660E2f608ed56e1659C450eB113120) | [`0x4200000000000000000000000000000000000010`](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000010) |
90+
| Mainnet / Base | [`0x3154Cf16ccdb4C6d922629664174b904d80F2C35`](https://etherscan.io/address/0x3154Cf16ccdb4C6d922629664174b904d80F2C35) | [`0x4200000000000000000000000000000000000010`](https://basescan.org/address/0x4200000000000000000000000000000000000010) |
91+
92+
### BaseSepolia
93+
94+
```bash
95+
make bridge-l1-to-base-sepolia USER_PRIVATE_KEY=0x... AMOUNT=1000000000000000000
96+
```
97+
98+
### BaseMainnet
99+
100+
```bash
101+
make bridge-l1-to-base-mainnet USER_PRIVATE_KEY=0x... AMOUNT=1000000000000000000
102+
```
103+
104+
`USER_PRIVATE_KEY` is the private key of the account holding ALIGN tokens on L1.
105+
`AMOUNT` is in wei (the example above bridges 1 ALIGN token = 1e18 wei).
106+
Both can also be set in the `.env` file.
107+
108+
The command will:
109+
1. Approve the L1StandardBridge to spend `AMOUNT` ALIGN tokens on L1
110+
2. Call `depositERC20` on the L1StandardBridge to initiate the bridge
111+
112+
Tokens will appear on Base after the L1 transaction is included and the
113+
message is relayed (~20 minutes).
114+
115+
### L2 -> L1 (Withdrawal)
116+
117+
Users call `withdraw` on the L2StandardBridge. After the challenge period
118+
(7 days on mainnet), prove and finalize the withdrawal on L1 to unlock tokens.
119+
120+
## Deployed Addresses
121+
122+
| Network | L1 Token (Ethereum) | L2 Token (Base) |
123+
|----------------------|---------------------|-----------------|
124+
| Sepolia / BaseSepolia | `0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A` | `0x4AAcFbc2C31598a560b285dB20966E00B73F9F81` |
125+
| Mainnet / Base | TBD | TBD |
126+
127+
## References
128+
129+
- [OP Standard Bridge Standard Token Tutorial](https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token)
130+
- [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io)

0 commit comments

Comments
 (0)