Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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=

# 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/
116 changes: 116 additions & 0 deletions claim_contracts/base/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
-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 withdraw-full-sepolia withdraw-full-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}'

# Factory predeploy (same on all OP Stack chains)
FACTORY=0x4200000000000000000000000000000000000012

# --- Deployment ---

deploy-base-sepolia: ## Deploy ALIGN on BaseSepolia
@L2_TOKEN=$$(cast send $(FACTORY) \
"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) \
"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