Skip to content

Commit 1a19f5c

Browse files
authored
feat: Base token deployment (#2279)
1 parent fb06269 commit 1a19f5c

File tree

9 files changed

+1302
-1
lines changed

9 files changed

+1302
-1
lines changed

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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Base L2 ALIGN Token Deployment
2+
# Copy this file to .env and fill in the values
3+
4+
# Keys
5+
DEPLOYER_PRIVATE_KEY=
6+
USER_PRIVATE_KEY=
7+
8+
# RPC URLs
9+
BASE_SEPOLIA_RPC_URL=https://sepolia.base.org
10+
BASE_MAINNET_RPC_URL=https://mainnet.base.org
11+
L1_SEPOLIA_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
12+
L1_MAINNET_RPC_URL=https://ethereum-rpc.publicnode.com
13+
14+
# Token addresses
15+
L1_TOKEN_SEPOLIA=0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A
16+
L2_TOKEN_SEPOLIA=0x4AAcFbc2C31598a560b285dB20966E00B73F9F81
17+
L1_TOKEN_MAINNET=0x50614cc8e44f7814549c223aa31db9296e58057c
18+
L2_TOKEN_MAINNET=0x53f39e5C53EE40bbc3Da97C3B47BD2968d110a8D
19+
20+
# Bridge addresses (source: https://docs.base.org/chain/base-contracts)
21+
L1_BRIDGE_SEPOLIA=0xfd0Bf71F60660E2f608ed56e1659C450eB113120
22+
L1_BRIDGE_MAINNET=0x3154Cf16ccdb4C6d922629664174b904d80F2C35
23+
24+
# Bridge amount in wei (1e18 = 1 ALIGN)
25+
AMOUNT=1000000000000000000

claim_contracts/base/.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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
15+
16+
# Node
17+
node_modules/

claim_contracts/base/Makefile

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
-include .env
2+
export
3+
4+
.PHONY: help deploy-base-sepolia deploy-base-mainnet verify bridge-l1-to-base-sepolia bridge-l1-to-base-mainnet withdraw-base-to-l1-sepolia withdraw-base-to-l1-mainnet prove-withdrawal-sepolia prove-withdrawal-mainnet finalize-withdrawal-sepolia finalize-withdrawal-mainnet
5+
6+
help: ## Show help
7+
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
8+
9+
# OptimismMintableERC20Factory
10+
# Mainnet uses Base-specific factory to avoid address conflicts with Optimism.
11+
# Source: https://docs.base.org/chain/base-contracts
12+
FACTORY_SEPOLIA=0x4200000000000000000000000000000000000012
13+
FACTORY_MAINNET=0xF10122D428B4bc8A9d050D06a2037259b4c4B83B
14+
15+
# --- Deployment ---
16+
17+
deploy-base-sepolia: ## Deploy ALIGN on BaseSepolia
18+
@L2_TOKEN=$$(cast send $(FACTORY_SEPOLIA) \
19+
"createOptimismMintableERC20(address,string,string)" \
20+
$(L1_TOKEN_SEPOLIA) "Aligned Token" "ALIGN" \
21+
--private-key $(DEPLOYER_PRIVATE_KEY) \
22+
--rpc-url $(BASE_SEPOLIA_RPC_URL) \
23+
--json | jq -r '.logs[0].topics[2]' | cast parse-bytes32-address) && \
24+
echo "L2 Token deployed at: $$L2_TOKEN"
25+
26+
deploy-base-mainnet: ## Deploy ALIGN on BaseMainnet
27+
@L2_TOKEN=$$(cast send $(FACTORY_MAINNET) \
28+
"createOptimismMintableERC20(address,string,string)" \
29+
$(L1_TOKEN_MAINNET) "Aligned Token" "ALIGN" \
30+
--interactive \
31+
--rpc-url $(BASE_MAINNET_RPC_URL) \
32+
--json | jq -r '.logs[0].topics[2]' | cast parse-bytes32-address) && \
33+
echo "L2 Token deployed at: $$L2_TOKEN"
34+
35+
# --- Verification ---
36+
37+
verify: ## Verify L2 token (requires L2_TOKEN, RPC_URL)
38+
@cast call $(L2_TOKEN) "name()(string)" --rpc-url $(RPC_URL)
39+
@cast call $(L2_TOKEN) "symbol()(string)" --rpc-url $(RPC_URL)
40+
@cast call $(L2_TOKEN) "decimals()(uint8)" --rpc-url $(RPC_URL)
41+
@cast call $(L2_TOKEN) "REMOTE_TOKEN()(address)" --rpc-url $(RPC_URL)
42+
@cast call $(L2_TOKEN) "BRIDGE()(address)" --rpc-url $(RPC_URL)
43+
@cast call $(L2_TOKEN) "totalSupply()(uint256)" --rpc-url $(RPC_URL)
44+
45+
# --- Bridging L1 -> Base ---
46+
47+
bridge-l1-to-base-sepolia: ## Bridge ALIGN from Sepolia to BaseSepolia (requires AMOUNT)
48+
cast send $(L1_TOKEN_SEPOLIA) "approve(address,uint256)" $(L1_BRIDGE_SEPOLIA) $(AMOUNT) \
49+
--private-key $(USER_PRIVATE_KEY) --rpc-url $(L1_SEPOLIA_RPC_URL)
50+
cast send $(L1_BRIDGE_SEPOLIA) "depositERC20(address,address,uint256,uint32,bytes)" \
51+
$(L1_TOKEN_SEPOLIA) $(L2_TOKEN_SEPOLIA) $(AMOUNT) 200000 0x \
52+
--private-key $(USER_PRIVATE_KEY) --rpc-url $(L1_SEPOLIA_RPC_URL)
53+
54+
bridge-l1-to-base-mainnet: ## Bridge ALIGN from Ethereum to Base (requires AMOUNT)
55+
cast send $(L1_TOKEN_MAINNET) "approve(address,uint256)" $(L1_BRIDGE_MAINNET) $(AMOUNT) \
56+
--interactive --rpc-url $(L1_MAINNET_RPC_URL)
57+
cast send $(L1_BRIDGE_MAINNET) "depositERC20(address,address,uint256,uint32,bytes)" \
58+
$(L1_TOKEN_MAINNET) $(L2_TOKEN_MAINNET) $(AMOUNT) 200000 0x \
59+
--interactive --rpc-url $(L1_MAINNET_RPC_URL)
60+
61+
bridge-l1-to-base-sepolia-to: ## Bridge ALIGN from Sepolia to BaseSepolia to a different address (requires AMOUNT, TO)
62+
cast send $(L1_TOKEN_SEPOLIA) "approve(address,uint256)" $(L1_BRIDGE_SEPOLIA) $(AMOUNT) \
63+
--private-key $(USER_PRIVATE_KEY) --rpc-url $(L1_SEPOLIA_RPC_URL)
64+
cast send $(L1_BRIDGE_SEPOLIA) "depositERC20To(address,address,address,uint256,uint32,bytes)" \
65+
$(L1_TOKEN_SEPOLIA) $(L2_TOKEN_SEPOLIA) $(TO) $(AMOUNT) 200000 0x \
66+
--private-key $(USER_PRIVATE_KEY) --rpc-url $(L1_SEPOLIA_RPC_URL)
67+
68+
bridge-l1-to-base-mainnet-to: ## Bridge ALIGN from Ethereum to Base to a different address (requires AMOUNT, TO)
69+
cast send $(L1_TOKEN_MAINNET) "approve(address,uint256)" $(L1_BRIDGE_MAINNET) $(AMOUNT) \
70+
--interactive --rpc-url $(L1_MAINNET_RPC_URL)
71+
cast send $(L1_BRIDGE_MAINNET) "depositERC20To(address,address,address,uint256,uint32,bytes)" \
72+
$(L1_TOKEN_MAINNET) $(L2_TOKEN_MAINNET) $(TO) $(AMOUNT) 200000 0x \
73+
--interactive --rpc-url $(L1_MAINNET_RPC_URL)
74+
75+
# --- Bridging Base -> L1 (withdrawal) ---
76+
# This initiates the withdrawal on L2. After this, you must:
77+
# 1. Wait ~1 hour for the L2 output to be proposed
78+
# 2. Prove the withdrawal on L1 (requires Optimism SDK or Base Bridge UI)
79+
# 3. Wait 7 days (challenge period)
80+
# 4. Finalize the withdrawal on L1
81+
# L2StandardBridge predeploy: 0x4200000000000000000000000000000000000010
82+
83+
withdraw-base-to-l1-sepolia: ## Initiate ALIGN withdrawal from BaseSepolia to Sepolia (requires AMOUNT)
84+
cast send 0x4200000000000000000000000000000000000010 \
85+
"withdraw(address,uint256,uint32,bytes)" \
86+
$(L2_TOKEN_SEPOLIA) $(AMOUNT) 200000 0x \
87+
--private-key $(USER_PRIVATE_KEY) --rpc-url $(BASE_SEPOLIA_RPC_URL)
88+
89+
withdraw-base-to-l1-mainnet: ## Initiate ALIGN withdrawal from Base to Ethereum (requires AMOUNT)
90+
cast send 0x4200000000000000000000000000000000000010 \
91+
"withdraw(address,uint256,uint32,bytes)" \
92+
$(L2_TOKEN_MAINNET) $(AMOUNT) 200000 0x \
93+
--interactive --rpc-url $(BASE_MAINNET_RPC_URL)
94+
95+
withdraw-base-to-l1-sepolia-to: ## Initiate ALIGN withdrawal from BaseSepolia to a different address on Sepolia (requires AMOUNT, TO)
96+
cast send 0x4200000000000000000000000000000000000010 \
97+
"withdrawTo(address,address,uint256,uint32,bytes)" \
98+
$(L2_TOKEN_SEPOLIA) $(TO) $(AMOUNT) 200000 0x \
99+
--private-key $(USER_PRIVATE_KEY) --rpc-url $(BASE_SEPOLIA_RPC_URL)
100+
101+
withdraw-base-to-l1-mainnet-to: ## Initiate ALIGN withdrawal from Base to a different address on Ethereum (requires AMOUNT, TO)
102+
cast send 0x4200000000000000000000000000000000000010 \
103+
"withdrawTo(address,address,uint256,uint32,bytes)" \
104+
$(L2_TOKEN_MAINNET) $(TO) $(AMOUNT) 200000 0x \
105+
--interactive --rpc-url $(BASE_MAINNET_RPC_URL)
106+
107+
# --- Prove & Finalize (requires npm install) ---
108+
109+
prove-withdrawal-sepolia: ## Prove withdrawal on L1 Sepolia (requires TX_HASH)
110+
npx tsx scripts/withdraw.ts prove --tx-hash $(TX_HASH) --network sepolia
111+
112+
prove-withdrawal-mainnet: ## Prove withdrawal on L1 Mainnet (requires TX_HASH)
113+
npx tsx scripts/withdraw.ts prove --tx-hash $(TX_HASH) --network mainnet
114+
115+
finalize-withdrawal-sepolia: ## Finalize withdrawal on L1 Sepolia (requires TX_HASH)
116+
npx tsx scripts/withdraw.ts finalize --tx-hash $(TX_HASH) --network sepolia
117+
118+
finalize-withdrawal-mainnet: ## Finalize withdrawal on L1 Mainnet (requires TX_HASH)
119+
npx tsx scripts/withdraw.ts finalize --tx-hash $(TX_HASH) --network mainnet

claim_contracts/base/README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# ALIGN Token on Base L2
2+
3+
Deployment and bridging of the Aligned Token (ALIGN) on Base using the [OP Standard Bridge](https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token).
4+
5+
## Setup
6+
7+
```bash
8+
cp .env.example .env
9+
```
10+
11+
## Deploy
12+
13+
The L2 token is created via the `OptimismMintableERC20Factory` predeploy at `0x4200000000000000000000000000000000000012`. No custom contract is needed.
14+
15+
```bash
16+
make deploy-base-sepolia # BaseSepolia
17+
make deploy-base-mainnet # BaseMainnet
18+
```
19+
20+
## Verify
21+
22+
```bash
23+
make verify L2_TOKEN=<address> RPC_URL=https://sepolia.base.org
24+
```
25+
26+
## Bridge (L1 -> Base)
27+
28+
Approve + deposit in one command. `AMOUNT` is the token amount with 18 decimals (1000000000000000000 = 1 ALIGN).
29+
30+
```bash
31+
make bridge-l1-to-base-sepolia AMOUNT=1000000000000000000
32+
make bridge-l1-to-base-mainnet AMOUNT=1000000000000000000
33+
```
34+
35+
Tokens appear on Base after ~20 minutes.
36+
37+
To bridge to a different L2 address, use the `TO` parameter:
38+
39+
```bash
40+
make bridge-l1-to-base-sepolia-to AMOUNT=1000000000000000000 TO=0x...
41+
make bridge-l1-to-base-mainnet-to AMOUNT=1000000000000000000 TO=0x...
42+
```
43+
44+
## Withdraw (Base -> L1)
45+
46+
Withdrawals are a [multi-step process](https://docs.optimism.io/app-developers/tutorials/bridging/cross-dom-bridge-erc20#withdraw-tokens). No approval is needed. All three steps use the same `TX_HASH` — the **L2 initiation tx hash** from step 1.
47+
48+
1. **Initiate** on L2 (burns tokens on Base):
49+
50+
```bash
51+
make withdraw-base-to-l1-sepolia AMOUNT=1000000000000000000
52+
make withdraw-base-to-l1-mainnet AMOUNT=1000000000000000000
53+
```
54+
55+
To withdraw to a different L1 address, use the `TO` parameter:
56+
57+
```bash
58+
make withdraw-base-to-l1-sepolia-to AMOUNT=1000000000000000000 TO=0x...
59+
make withdraw-base-to-l1-mainnet-to AMOUNT=1000000000000000000 TO=0x...
60+
```
61+
62+
Save the tx hash from this step — it's needed for prove and finalize.
63+
64+
2. **Prove** on L1 — wait ~1 hour for the L2 output to be proposed, then prove:
65+
66+
```bash
67+
make prove-withdrawal-sepolia TX_HASH=<L2 initiation tx hash>
68+
make prove-withdrawal-mainnet TX_HASH=<L2 initiation tx hash>
69+
```
70+
71+
3. **Finalize** on L1 — wait 7 days challenge period (shorter on testnet), then finalize:
72+
73+
```bash
74+
make finalize-withdrawal-sepolia TX_HASH=<L2 initiation tx hash>
75+
make finalize-withdrawal-mainnet TX_HASH=<L2 initiation tx hash>
76+
```
77+
78+
> **Note:** Prove and finalize use `viem` + `viem/op-stack`. Run `npm install` first.
79+
80+
## Bridge Addresses
81+
82+
Source: [Base Contracts](https://docs.base.org/chain/base-contracts)
83+
84+
| Network | L1StandardBridge | L2StandardBridge |
85+
|---------|------------------|------------------|
86+
| Sepolia | [`0xfd0Bf71F60660E2f608ed56e1659C450eB113120`](https://sepolia.etherscan.io/address/0xfd0Bf71F60660E2f608ed56e1659C450eB113120) | [`0x4200000000000000000000000000000000000010`](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000010) |
87+
| Mainnet | [`0x3154Cf16ccdb4C6d922629664174b904d80F2C35`](https://etherscan.io/address/0x3154Cf16ccdb4C6d922629664174b904d80F2C35) | [`0x4200000000000000000000000000000000000010`](https://basescan.org/address/0x4200000000000000000000000000000000000010) |
88+
89+
## Deployed Addresses
90+
91+
| Network | L1 Token (Ethereum) | L2 Token (Base) |
92+
|---------|---------------------|-----------------|
93+
| Sepolia | `0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A` | `0x4AAcFbc2C31598a560b285dB20966E00B73F9F81` |
94+
| Mainnet | `0x50614cc8e44f7814549c223aa31db9296e58057c` | `0x53f39e5C53EE40bbc3Da97C3B47BD2968d110a8D` |
95+
96+
## References
97+
98+
- [OP Standard Bridge Standard Token Tutorial](https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token)
99+
- [OP Bridge ERC-20 Tutorial (withdraw flow)](https://docs.optimism.io/app-developers/tutorials/bridging/cross-dom-bridge-erc20)
100+
- [Base Contracts](https://docs.base.org/chain/base-contracts)

0 commit comments

Comments
 (0)