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=
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/
110 changes: 110 additions & 0 deletions claim_contracts/base/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
-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" \
--private-key $(DEPLOYER_PRIVATE_KEY) \
--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) \
--private-key $(USER_PRIVATE_KEY) --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 \
--private-key $(USER_PRIVATE_KEY) --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) \
--private-key $(USER_PRIVATE_KEY) --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 \
--private-key $(USER_PRIVATE_KEY) --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 \
--private-key $(USER_PRIVATE_KEY) --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

withdraw-full-sepolia: ## Full withdrawal flow: prove + finalize on Sepolia (requires TX_HASH)
npx tsx scripts/withdraw.ts full --tx-hash $(TX_HASH) --network sepolia

withdraw-full-mainnet: ## Full withdrawal flow: prove + finalize on Mainnet (requires TX_HASH)
npx tsx scripts/withdraw.ts full --tx-hash $(TX_HASH) --network mainnet
87 changes: 87 additions & 0 deletions claim_contracts/base/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# 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
# Fill in DEPLOYER_PRIVATE_KEY and USER_PRIVATE_KEY
```

## 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 in wei (1e18 = 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.

## 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
```

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