Skip to content
Open
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
1 change: 0 additions & 1 deletion claim_contracts/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ deploy-token-testnet: ## 🚀 Deploy the token contract
--private-key $(DEPLOYER_PRIVATE_KEY) \
--rpc-url $(RPC_URL) \
--broadcast \
--verbosity 3 \
--verify \
--etherscan-api-key $(ETHERSCAN_API_KEY)

Expand Down
25 changes: 25 additions & 0 deletions claim_contracts/base/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Base L2 ALIGN Token Deployment
# Copy this file to .env and fill in the values

# Keys
DEPLOYER_PRIVATE_KEY=
USER_PRIVATE_KEY=

# RPC URLs
BASE_SEPOLIA_RPC_URL=https://sepolia.base.org
BASE_MAINNET_RPC_URL=https://mainnet.base.org
L1_SEPOLIA_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
L1_MAINNET_RPC_URL=https://ethereum-rpc.publicnode.com

# Token addresses
L1_TOKEN_SEPOLIA=0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A
L2_TOKEN_SEPOLIA=0x4AAcFbc2C31598a560b285dB20966E00B73F9F81
L1_TOKEN_MAINNET=0x50614cc8e44f7814549c223aa31db9296e58057c
L2_TOKEN_MAINNET=0x53f39e5C53EE40bbc3Da97C3B47BD2968d110a8D

# Bridge addresses (source: https://docs.base.org/chain/base-contracts)
L1_BRIDGE_SEPOLIA=0xfd0Bf71F60660E2f608ed56e1659C450eB113120
L1_BRIDGE_MAINNET=0x3154Cf16ccdb4C6d922629664174b904d80F2C35

# Bridge amount in wei (1e18 = 1 ALIGN)
AMOUNT=1000000000000000000
17 changes: 17 additions & 0 deletions claim_contracts/base/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env

# Node
node_modules/
119 changes: 119 additions & 0 deletions claim_contracts/base/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
-include .env
export

.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

help: ## Show help
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

# OptimismMintableERC20Factory
# Mainnet uses Base-specific factory to avoid address conflicts with Optimism.
# Source: https://docs.base.org/chain/base-contracts
FACTORY_SEPOLIA=0x4200000000000000000000000000000000000012
FACTORY_MAINNET=0xF10122D428B4bc8A9d050D06a2037259b4c4B83B

# --- Deployment ---

deploy-base-sepolia: ## Deploy ALIGN on BaseSepolia
@L2_TOKEN=$$(cast send $(FACTORY_SEPOLIA) \
"createOptimismMintableERC20(address,string,string)" \
$(L1_TOKEN_SEPOLIA) "Aligned Token" "ALIGN" \
--private-key $(DEPLOYER_PRIVATE_KEY) \
--rpc-url $(BASE_SEPOLIA_RPC_URL) \
--json | jq -r '.logs[0].topics[2]' | cast parse-bytes32-address) && \
echo "L2 Token deployed at: $$L2_TOKEN"

deploy-base-mainnet: ## Deploy ALIGN on BaseMainnet
@L2_TOKEN=$$(cast send $(FACTORY_MAINNET) \
"createOptimismMintableERC20(address,string,string)" \
$(L1_TOKEN_MAINNET) "Aligned Token" "ALIGN" \
--interactive \
--rpc-url $(BASE_MAINNET_RPC_URL) \
--json | jq -r '.logs[0].topics[2]' | cast parse-bytes32-address) && \
echo "L2 Token deployed at: $$L2_TOKEN"

# --- Verification ---

verify: ## Verify L2 token (requires L2_TOKEN, RPC_URL)
@cast call $(L2_TOKEN) "name()(string)" --rpc-url $(RPC_URL)
@cast call $(L2_TOKEN) "symbol()(string)" --rpc-url $(RPC_URL)
@cast call $(L2_TOKEN) "decimals()(uint8)" --rpc-url $(RPC_URL)
@cast call $(L2_TOKEN) "REMOTE_TOKEN()(address)" --rpc-url $(RPC_URL)
@cast call $(L2_TOKEN) "BRIDGE()(address)" --rpc-url $(RPC_URL)
@cast call $(L2_TOKEN) "totalSupply()(uint256)" --rpc-url $(RPC_URL)

# --- Bridging L1 -> Base ---

bridge-l1-to-base-sepolia: ## Bridge ALIGN from Sepolia to BaseSepolia (requires AMOUNT)
cast send $(L1_TOKEN_SEPOLIA) "approve(address,uint256)" $(L1_BRIDGE_SEPOLIA) $(AMOUNT) \
--private-key $(USER_PRIVATE_KEY) --rpc-url $(L1_SEPOLIA_RPC_URL)
cast send $(L1_BRIDGE_SEPOLIA) "depositERC20(address,address,uint256,uint32,bytes)" \
$(L1_TOKEN_SEPOLIA) $(L2_TOKEN_SEPOLIA) $(AMOUNT) 200000 0x \
--private-key $(USER_PRIVATE_KEY) --rpc-url $(L1_SEPOLIA_RPC_URL)

bridge-l1-to-base-mainnet: ## Bridge ALIGN from Ethereum to Base (requires AMOUNT)
cast send $(L1_TOKEN_MAINNET) "approve(address,uint256)" $(L1_BRIDGE_MAINNET) $(AMOUNT) \
--interactive --rpc-url $(L1_MAINNET_RPC_URL)
cast send $(L1_BRIDGE_MAINNET) "depositERC20(address,address,uint256,uint32,bytes)" \
$(L1_TOKEN_MAINNET) $(L2_TOKEN_MAINNET) $(AMOUNT) 200000 0x \
--interactive --rpc-url $(L1_MAINNET_RPC_URL)

bridge-l1-to-base-sepolia-to: ## Bridge ALIGN from Sepolia to BaseSepolia to a different address (requires AMOUNT, TO)
cast send $(L1_TOKEN_SEPOLIA) "approve(address,uint256)" $(L1_BRIDGE_SEPOLIA) $(AMOUNT) \
--private-key $(USER_PRIVATE_KEY) --rpc-url $(L1_SEPOLIA_RPC_URL)
cast send $(L1_BRIDGE_SEPOLIA) "depositERC20To(address,address,address,uint256,uint32,bytes)" \
$(L1_TOKEN_SEPOLIA) $(L2_TOKEN_SEPOLIA) $(TO) $(AMOUNT) 200000 0x \
--private-key $(USER_PRIVATE_KEY) --rpc-url $(L1_SEPOLIA_RPC_URL)

bridge-l1-to-base-mainnet-to: ## Bridge ALIGN from Ethereum to Base to a different address (requires AMOUNT, TO)
cast send $(L1_TOKEN_MAINNET) "approve(address,uint256)" $(L1_BRIDGE_MAINNET) $(AMOUNT) \
--interactive --rpc-url $(L1_MAINNET_RPC_URL)
cast send $(L1_BRIDGE_MAINNET) "depositERC20To(address,address,address,uint256,uint32,bytes)" \
$(L1_TOKEN_MAINNET) $(L2_TOKEN_MAINNET) $(TO) $(AMOUNT) 200000 0x \
--interactive --rpc-url $(L1_MAINNET_RPC_URL)

# --- Bridging Base -> L1 (withdrawal) ---
# This initiates the withdrawal on L2. After this, you must:
# 1. Wait ~1 hour for the L2 output to be proposed
# 2. Prove the withdrawal on L1 (requires Optimism SDK or Base Bridge UI)
# 3. Wait 7 days (challenge period)
# 4. Finalize the withdrawal on L1
# L2StandardBridge predeploy: 0x4200000000000000000000000000000000000010

withdraw-base-to-l1-sepolia: ## Initiate ALIGN withdrawal from BaseSepolia to Sepolia (requires AMOUNT)
cast send 0x4200000000000000000000000000000000000010 \
"withdraw(address,uint256,uint32,bytes)" \
$(L2_TOKEN_SEPOLIA) $(AMOUNT) 200000 0x \
--private-key $(USER_PRIVATE_KEY) --rpc-url $(BASE_SEPOLIA_RPC_URL)

withdraw-base-to-l1-mainnet: ## Initiate ALIGN withdrawal from Base to Ethereum (requires AMOUNT)
cast send 0x4200000000000000000000000000000000000010 \
"withdraw(address,uint256,uint32,bytes)" \
$(L2_TOKEN_MAINNET) $(AMOUNT) 200000 0x \
--interactive --rpc-url $(BASE_MAINNET_RPC_URL)

withdraw-base-to-l1-sepolia-to: ## Initiate ALIGN withdrawal from BaseSepolia to a different address on Sepolia (requires AMOUNT, TO)
cast send 0x4200000000000000000000000000000000000010 \
"withdrawTo(address,address,uint256,uint32,bytes)" \
$(L2_TOKEN_SEPOLIA) $(TO) $(AMOUNT) 200000 0x \
--private-key $(USER_PRIVATE_KEY) --rpc-url $(BASE_SEPOLIA_RPC_URL)

withdraw-base-to-l1-mainnet-to: ## Initiate ALIGN withdrawal from Base to a different address on Ethereum (requires AMOUNT, TO)
cast send 0x4200000000000000000000000000000000000010 \
"withdrawTo(address,address,uint256,uint32,bytes)" \
$(L2_TOKEN_MAINNET) $(TO) $(AMOUNT) 200000 0x \
--interactive --rpc-url $(BASE_MAINNET_RPC_URL)

# --- Prove & Finalize (requires npm install) ---

prove-withdrawal-sepolia: ## Prove withdrawal on L1 Sepolia (requires TX_HASH)
npx tsx scripts/withdraw.ts prove --tx-hash $(TX_HASH) --network sepolia

prove-withdrawal-mainnet: ## Prove withdrawal on L1 Mainnet (requires TX_HASH)
npx tsx scripts/withdraw.ts prove --tx-hash $(TX_HASH) --network mainnet

finalize-withdrawal-sepolia: ## Finalize withdrawal on L1 Sepolia (requires TX_HASH)
npx tsx scripts/withdraw.ts finalize --tx-hash $(TX_HASH) --network sepolia

finalize-withdrawal-mainnet: ## Finalize withdrawal on L1 Mainnet (requires TX_HASH)
npx tsx scripts/withdraw.ts finalize --tx-hash $(TX_HASH) --network mainnet
100 changes: 100 additions & 0 deletions claim_contracts/base/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# ALIGN Token on Base L2

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

## Setup

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

## Deploy

The L2 token is created via the `OptimismMintableERC20Factory` predeploy at `0x4200000000000000000000000000000000000012`. No custom contract is needed.

```bash
make deploy-base-sepolia # BaseSepolia
make deploy-base-mainnet # BaseMainnet
```

## Verify

```bash
make verify L2_TOKEN=<address> RPC_URL=https://sepolia.base.org
```

## Bridge (L1 -> Base)

Approve + deposit in one command. `AMOUNT` is the token amount with 18 decimals (1000000000000000000 = 1 ALIGN).

```bash
make bridge-l1-to-base-sepolia AMOUNT=1000000000000000000
make bridge-l1-to-base-mainnet AMOUNT=1000000000000000000
```

Tokens appear on Base after ~20 minutes.

To bridge to a different L2 address, use the `TO` parameter:

```bash
make bridge-l1-to-base-sepolia-to AMOUNT=1000000000000000000 TO=0x...
make bridge-l1-to-base-mainnet-to AMOUNT=1000000000000000000 TO=0x...
```

## Withdraw (Base -> L1)

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.

1. **Initiate** on L2 (burns tokens on Base):

```bash
make withdraw-base-to-l1-sepolia AMOUNT=1000000000000000000
make withdraw-base-to-l1-mainnet AMOUNT=1000000000000000000
```

To withdraw to a different L1 address, use the `TO` parameter:

```bash
make withdraw-base-to-l1-sepolia-to AMOUNT=1000000000000000000 TO=0x...
make withdraw-base-to-l1-mainnet-to AMOUNT=1000000000000000000 TO=0x...
```

Save the tx hash from this step — it's needed for prove and finalize.

2. **Prove** on L1 — wait ~1 hour for the L2 output to be proposed, then prove:

```bash
make prove-withdrawal-sepolia TX_HASH=<L2 initiation tx hash>
make prove-withdrawal-mainnet TX_HASH=<L2 initiation tx hash>
```

3. **Finalize** on L1 — wait 7 days challenge period (shorter on testnet), then finalize:

```bash
make finalize-withdrawal-sepolia TX_HASH=<L2 initiation tx hash>
make finalize-withdrawal-mainnet TX_HASH=<L2 initiation tx hash>
```

> **Note:** Prove and finalize use `viem` + `viem/op-stack`. Run `npm install` first.

## Bridge Addresses

Source: [Base Contracts](https://docs.base.org/chain/base-contracts)

| Network | L1StandardBridge | L2StandardBridge |
|---------|------------------|------------------|
| Sepolia | [`0xfd0Bf71F60660E2f608ed56e1659C450eB113120`](https://sepolia.etherscan.io/address/0xfd0Bf71F60660E2f608ed56e1659C450eB113120) | [`0x4200000000000000000000000000000000000010`](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000010) |
| Mainnet | [`0x3154Cf16ccdb4C6d922629664174b904d80F2C35`](https://etherscan.io/address/0x3154Cf16ccdb4C6d922629664174b904d80F2C35) | [`0x4200000000000000000000000000000000000010`](https://basescan.org/address/0x4200000000000000000000000000000000000010) |

## Deployed Addresses

| Network | L1 Token (Ethereum) | L2 Token (Base) |
|---------|---------------------|-----------------|
| Sepolia | `0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A` | `0x4AAcFbc2C31598a560b285dB20966E00B73F9F81` |
| Mainnet | TBD | TBD |

## References

- [OP Standard Bridge Standard Token Tutorial](https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token)
- [OP Bridge ERC-20 Tutorial (withdraw flow)](https://docs.optimism.io/app-developers/tutorials/bridging/cross-dom-bridge-erc20)
- [Base Contracts](https://docs.base.org/chain/base-contracts)
Loading
Loading