From c5e5aad67bd8b7cd6c5afdd55ccc44c64e7f3c7d Mon Sep 17 00:00:00 2001 From: JuArce <52429267+JuArce@users.noreply.github.com> Date: Fri, 13 Mar 2026 17:54:01 -0300 Subject: [PATCH 1/4] feat: add Base Token deployment --- claim_contracts/Makefile | 1 - claim_contracts/base/.env.example | 24 ++++++ claim_contracts/base/.gitignore | 14 ++++ claim_contracts/base/Makefile | 131 ++++++++++++++++++++++++++++++ claim_contracts/base/README.md | 130 +++++++++++++++++++++++++++++ 5 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 claim_contracts/base/.env.example create mode 100644 claim_contracts/base/.gitignore create mode 100644 claim_contracts/base/Makefile create mode 100644 claim_contracts/base/README.md diff --git a/claim_contracts/Makefile b/claim_contracts/Makefile index d1f5ab3517..c1ae58d0bc 100644 --- a/claim_contracts/Makefile +++ b/claim_contracts/Makefile @@ -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) diff --git a/claim_contracts/base/.env.example b/claim_contracts/base/.env.example new file mode 100644 index 0000000000..257774c4e1 --- /dev/null +++ b/claim_contracts/base/.env.example @@ -0,0 +1,24 @@ +# Base L2 ALIGN Token Deployment +# Copy this file to .env and fill in the values + +# Deployer private key (must have ETH on the target Base network for gas) +# Used for deploying the L2 token via the factory +DEPLOYER_PRIVATE_KEY= + +# User private key (must hold ALIGN tokens on L1 and ETH for gas) +# Used for bridging tokens from L1 to Base +USER_PRIVATE_KEY= + +# L1 token addresses +L1_TOKEN_SEPOLIA=0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A +L1_TOKEN_MAINNET= + +# L2 token addresses +L2_TOKEN_SEPOLIA=0x4AAcFbc2C31598a560b285dB20966E00B73F9F81 +L2_TOKEN_MAINNET= + +# 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 diff --git a/claim_contracts/base/.gitignore b/claim_contracts/base/.gitignore new file mode 100644 index 0000000000..85198aaa55 --- /dev/null +++ b/claim_contracts/base/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/claim_contracts/base/Makefile b/claim_contracts/base/Makefile new file mode 100644 index 0000000000..3b650fa36f --- /dev/null +++ b/claim_contracts/base/Makefile @@ -0,0 +1,131 @@ +-include .env + +.PHONY: help deploy-base-sepolia deploy-base-mainnet verify-base-sepolia verify-base-mainnet bridge-l1-to-base-sepolia bridge-l1-to-base-mainnet + +help: ## Show help for each of the Makefile recipes + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +# OptimismMintableERC20Factory predeploy address (same on all OP Stack chains) +FACTORY=0x4200000000000000000000000000000000000012 +TOKEN_NAME="Aligned Token" +TOKEN_SYMBOL="ALIGN" + +# Default values +DEPLOYER_PRIVATE_KEY?= +BASE_SEPOLIA_RPC_URL?=https://sepolia.base.org +BASE_MAINNET_RPC_URL?=https://mainnet.base.org + +# L1 token addresses (set these or pass via env/CLI) +L1_TOKEN_SEPOLIA?=0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A +L1_TOKEN_MAINNET?=0x0000000000000000000000000000000000000000 + +# L2 token addresses +L2_TOKEN_SEPOLIA?=0x4AAcFbc2C31598a560b285dB20966E00B73F9F81 +L2_TOKEN_MAINNET?=0x0000000000000000000000000000000000000000 + +# L1 Standard Bridge addresses +L1_BRIDGE_SEPOLIA=0xfd0Bf71F60660E2f608ed56e1659C450eB113120 +L1_BRIDGE_MAINNET=0x3154Cf16ccdb4C6d922629664174b904d80F2C35 + +# L1 RPC URLs +L1_SEPOLIA_RPC_URL?=https://ethereum-sepolia-rpc.publicnode.com +L1_MAINNET_RPC_URL?=https://ethereum-rpc.publicnode.com + +# Deployments +# Following: https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token#create-an-l2-erc-20-token +# The deployed L2 token address is extracted from the OptimismMintableERC20Created event logs. + +deploy-base-sepolia: ## Deploy ALIGN token on BaseSepolia via OptimismMintableERC20Factory + @echo "Deploying ALIGN on BaseSepolia..." + @echo "L1 Remote Token: $(L1_TOKEN_SEPOLIA)" + @echo "Factory: $(FACTORY)" + @L2_TOKEN=$$(cast send $(FACTORY) \ + "createOptimismMintableERC20(address,string,string)" \ + $(L1_TOKEN_SEPOLIA) $(TOKEN_NAME) $(TOKEN_SYMBOL) \ + --private-key $(DEPLOYER_PRIVATE_KEY) \ + --rpc-url $(BASE_SEPOLIA_RPC_URL) \ + --json | jq -r '.logs[0].topics[2]' | cast parse-bytes32-address) && \ + echo "---------------------------------------------" && \ + echo "ALIGN L2 Token deployed at: $$L2_TOKEN" && \ + echo "---------------------------------------------" + +deploy-base-mainnet: ## Deploy ALIGN token on BaseMainnet via OptimismMintableERC20Factory + @echo "Deploying ALIGN on BaseMainnet..." + @echo "L1 Remote Token: $(L1_TOKEN_MAINNET)" + @echo "Factory: $(FACTORY)" + @L2_TOKEN=$$(cast send $(FACTORY) \ + "createOptimismMintableERC20(address,string,string)" \ + $(L1_TOKEN_MAINNET) $(TOKEN_NAME) $(TOKEN_SYMBOL) \ + --private-key $(DEPLOYER_PRIVATE_KEY) \ + --rpc-url $(BASE_MAINNET_RPC_URL) \ + --json | jq -r '.logs[0].topics[2]' | cast parse-bytes32-address) && \ + echo "---------------------------------------------" && \ + echo "ALIGN L2 Token deployed at: $$L2_TOKEN" && \ + echo "---------------------------------------------" + +# Verification + +verify-base-sepolia: ## Verify the deployed L2 token on BaseSepolia (requires L2_TOKEN) + @echo "Verifying L2 token deployment on BaseSepolia..." + @echo "--- Token metadata ---" + cast call $(L2_TOKEN) "name()(string)" --rpc-url $(BASE_SEPOLIA_RPC_URL) + cast call $(L2_TOKEN) "symbol()(string)" --rpc-url $(BASE_SEPOLIA_RPC_URL) + cast call $(L2_TOKEN) "decimals()(uint8)" --rpc-url $(BASE_SEPOLIA_RPC_URL) + @echo "--- Bridge configuration ---" + cast call $(L2_TOKEN) "REMOTE_TOKEN()(address)" --rpc-url $(BASE_SEPOLIA_RPC_URL) + cast call $(L2_TOKEN) "BRIDGE()(address)" --rpc-url $(BASE_SEPOLIA_RPC_URL) + @echo "--- Total supply (should be 0 before any bridging) ---" + cast call $(L2_TOKEN) "totalSupply()(uint256)" --rpc-url $(BASE_SEPOLIA_RPC_URL) + +verify-base-mainnet: ## Verify the deployed L2 token on BaseMainnet (requires L2_TOKEN) + @echo "Verifying L2 token deployment on BaseMainnet..." + @echo "--- Token metadata ---" + cast call $(L2_TOKEN) "name()(string)" --rpc-url $(BASE_MAINNET_RPC_URL) + cast call $(L2_TOKEN) "symbol()(string)" --rpc-url $(BASE_MAINNET_RPC_URL) + cast call $(L2_TOKEN) "decimals()(uint8)" --rpc-url $(BASE_MAINNET_RPC_URL) + @echo "--- Bridge configuration ---" + cast call $(L2_TOKEN) "REMOTE_TOKEN()(address)" --rpc-url $(BASE_MAINNET_RPC_URL) + cast call $(L2_TOKEN) "BRIDGE()(address)" --rpc-url $(BASE_MAINNET_RPC_URL) + @echo "--- Total supply (should be 0 before any bridging) ---" + cast call $(L2_TOKEN) "totalSupply()(uint256)" --rpc-url $(BASE_MAINNET_RPC_URL) + +# Bridging L1 -> L2 +# Step 1: Approve the L1StandardBridge to spend ALIGN tokens +# Step 2: Deposit tokens via the bridge (tokens appear on Base after ~20 min) +# Requires: AMOUNT (in wei), USER_PRIVATE_KEY +AMOUNT?=1000000000000000000 # Default to 1 ALIGN (18 decimals) +USER_PRIVATE_KEY?= + +bridge-l1-to-base-sepolia: ## Bridge ALIGN tokens from Sepolia L1 to BaseSepolia (requires AMOUNT) + @echo "Step 1: Approving L1StandardBridge to spend $(AMOUNT) ALIGN on Sepolia..." + cast send $(L1_TOKEN_SEPOLIA) \ + "approve(address,uint256)" \ + $(L1_BRIDGE_SEPOLIA) $(AMOUNT) \ + --private-key $(USER_PRIVATE_KEY) \ + --rpc-url $(L1_SEPOLIA_RPC_URL) + @echo "Step 2: Depositing $(AMOUNT) ALIGN to BaseSepolia..." + 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) + @echo "---------------------------------------------" + @echo "Bridge initiated. Tokens will appear on BaseSepolia in ~20 minutes." + @echo "---------------------------------------------" + +bridge-l1-to-base-mainnet: ## Bridge ALIGN tokens from Ethereum L1 to Base (requires AMOUNT) + @echo "Step 1: Approving L1StandardBridge to spend $(AMOUNT) ALIGN on Mainnet..." + cast send $(L1_TOKEN_MAINNET) \ + "approve(address,uint256)" \ + $(L1_BRIDGE_MAINNET) $(AMOUNT) \ + --private-key $(USER_PRIVATE_KEY) \ + --rpc-url $(L1_MAINNET_RPC_URL) + @echo "Step 2: Depositing $(AMOUNT) ALIGN to Base..." + 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) + @echo "---------------------------------------------" + @echo "Bridge initiated. Tokens will appear on Base in ~20 minutes." + @echo "---------------------------------------------" diff --git a/claim_contracts/base/README.md b/claim_contracts/base/README.md new file mode 100644 index 0000000000..0ca5c6ec4d --- /dev/null +++ b/claim_contracts/base/README.md @@ -0,0 +1,130 @@ +# ALIGN Token on Base L2 + +Deployment of the Aligned Token (ALIGN) on Base L2 using the OptimismMintableERC20Factory. + +Based on the [OP Standard Bridge Standard Token tutorial](https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token). + +## Overview + +Base is an OP Stack chain. The ALIGN token on Base is created via the +`OptimismMintableERC20Factory` predeploy at `0x4200000000000000000000000000000000000012`. +This produces a standard `OptimismMintableERC20` token that is automatically compatible +with the OP Standard Bridge for L1 <-> L2 token transfers. + +No custom Solidity contract is deployed — the factory handles everything. + +## Prerequisites + +- [Foundry](https://book.getfoundry.sh/getting-started/installation) (`cast` CLI) +- `jq` for parsing transaction receipts +- An account with ETH on the target Base network (for gas) +- The L1 ALIGN token proxy address (deployed on Ethereum Sepolia or Mainnet) + +## Setup + +1. Copy `.env.example` to `.env` and fill in the values: + + ```bash + cp .env.example .env + ``` + +2. Set the L1 token addresses. The Makefile defaults are: + - `L1_TOKEN_SEPOLIA=0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A` + - `L1_TOKEN_MAINNET=` (set when ready) + + You can override them via `.env` or by passing them to `make`. + +## Deployment + +The deploy targets call `createOptimismMintableERC20` on the factory and extract the +deployed L2 token address from the `OptimismMintableERC20Created` event logs, following +the [OP tutorial](https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token#create-an-l2-erc-20-token). + +### BaseSepolia (Testnet) + +```bash +source .env +make deploy-base-sepolia +``` + +### BaseMainnet (Production) + +```bash +source .env +make deploy-base-mainnet +``` + +The output will print the deployed L2 token address. + +## Verification + +After deployment, verify the token was created correctly: + +```bash +make verify-base-sepolia L2_TOKEN= +``` + +Expected output: + +| Check | Expected | +|-------|----------| +| `name()` | `"Aligned Token"` | +| `symbol()` | `"ALIGN"` | +| `decimals()` | `18` | +| `REMOTE_TOKEN()` | L1 token proxy address | +| `BRIDGE()` | `0x4200000000000000000000000000000000000010` | +| `totalSupply()` | `0` (before any bridging) | + +## Bridging Tokens (L1 -> Base) + +After the L2 token is deployed, tokens can be bridged from L1 to Base using the +OP Standard Bridge. The Makefile handles the two-step process (approve + deposit). + +### Bridge Addresses + +Source: [Base Contracts](https://docs.base.org/chain/base-contracts) + +| Network | L1StandardBridge | L2StandardBridge | +|----------------------|------------------|------------------| +| Sepolia / BaseSepolia | [`0xfd0Bf71F60660E2f608ed56e1659C450eB113120`](https://sepolia.etherscan.io/address/0xfd0Bf71F60660E2f608ed56e1659C450eB113120) | [`0x4200000000000000000000000000000000000010`](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000010) | +| Mainnet / Base | [`0x3154Cf16ccdb4C6d922629664174b904d80F2C35`](https://etherscan.io/address/0x3154Cf16ccdb4C6d922629664174b904d80F2C35) | [`0x4200000000000000000000000000000000000010`](https://basescan.org/address/0x4200000000000000000000000000000000000010) | + +### BaseSepolia + +```bash +make bridge-l1-to-base-sepolia USER_PRIVATE_KEY=0x... AMOUNT=1000000000000000000 +``` + +### BaseMainnet + +```bash +make bridge-l1-to-base-mainnet USER_PRIVATE_KEY=0x... AMOUNT=1000000000000000000 +``` + +`USER_PRIVATE_KEY` is the private key of the account holding ALIGN tokens on L1. +`AMOUNT` is in wei (the example above bridges 1 ALIGN token = 1e18 wei). +Both can also be set in the `.env` file. + +The command will: +1. Approve the L1StandardBridge to spend `AMOUNT` ALIGN tokens on L1 +2. Call `depositERC20` on the L1StandardBridge to initiate the bridge + +Tokens will appear on Base after the L1 transaction is included and the +message is relayed (~20 minutes). + +### L2 -> L1 (Withdrawal) + +Users call `withdraw` on the L2StandardBridge. After the challenge period +(7 days on mainnet), prove and finalize the withdrawal on L1 to unlock tokens. + +## Deployed Addresses + +| Network | L1 Token (Ethereum) | L2 Token (Base) | +|----------------------|---------------------|-----------------| +| Sepolia / BaseSepolia | `0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A` | `0x4AAcFbc2C31598a560b285dB20966E00B73F9F81` | +| Mainnet / Base | TBD | TBD | + +## References + +- [OP Standard Bridge Standard Token Tutorial](https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token) +- [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io) From dacd4af99c53d963c1bdbb70b7e4f2b84bc41cd4 Mon Sep 17 00:00:00 2001 From: JuArce <52429267+JuArce@users.noreply.github.com> Date: Fri, 13 Mar 2026 18:03:14 -0300 Subject: [PATCH 2/4] refactor: simplify README.md --- claim_contracts/base/.env.example | 27 +++--- claim_contracts/base/Makefile | 134 +++++++----------------------- claim_contracts/base/README.md | 125 ++++++---------------------- 3 files changed, 69 insertions(+), 217 deletions(-) diff --git a/claim_contracts/base/.env.example b/claim_contracts/base/.env.example index 257774c4e1..eb55b16a46 100644 --- a/claim_contracts/base/.env.example +++ b/claim_contracts/base/.env.example @@ -1,24 +1,25 @@ # Base L2 ALIGN Token Deployment # Copy this file to .env and fill in the values -# Deployer private key (must have ETH on the target Base network for gas) -# Used for deploying the L2 token via the factory +# Keys DEPLOYER_PRIVATE_KEY= - -# User private key (must hold ALIGN tokens on L1 and ETH for gas) -# Used for bridging tokens from L1 to Base USER_PRIVATE_KEY= -# L1 token addresses -L1_TOKEN_SEPOLIA=0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A -L1_TOKEN_MAINNET= - -# L2 token addresses -L2_TOKEN_SEPOLIA=0x4AAcFbc2C31598a560b285dB20966E00B73F9F81 -L2_TOKEN_MAINNET= - # 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 diff --git a/claim_contracts/base/Makefile b/claim_contracts/base/Makefile index 3b650fa36f..8fc6919fda 100644 --- a/claim_contracts/base/Makefile +++ b/claim_contracts/base/Makefile @@ -1,131 +1,55 @@ -include .env -.PHONY: help deploy-base-sepolia deploy-base-mainnet verify-base-sepolia verify-base-mainnet bridge-l1-to-base-sepolia bridge-l1-to-base-mainnet +.PHONY: help deploy-base-sepolia deploy-base-mainnet verify bridge-l1-to-base-sepolia bridge-l1-to-base-mainnet -help: ## Show help for each of the Makefile recipes +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 predeploy address (same on all OP Stack chains) +# Factory predeploy (same on all OP Stack chains) FACTORY=0x4200000000000000000000000000000000000012 -TOKEN_NAME="Aligned Token" -TOKEN_SYMBOL="ALIGN" -# Default values -DEPLOYER_PRIVATE_KEY?= -BASE_SEPOLIA_RPC_URL?=https://sepolia.base.org -BASE_MAINNET_RPC_URL?=https://mainnet.base.org +# --- Deployment --- -# L1 token addresses (set these or pass via env/CLI) -L1_TOKEN_SEPOLIA?=0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A -L1_TOKEN_MAINNET?=0x0000000000000000000000000000000000000000 - -# L2 token addresses -L2_TOKEN_SEPOLIA?=0x4AAcFbc2C31598a560b285dB20966E00B73F9F81 -L2_TOKEN_MAINNET?=0x0000000000000000000000000000000000000000 - -# L1 Standard Bridge addresses -L1_BRIDGE_SEPOLIA=0xfd0Bf71F60660E2f608ed56e1659C450eB113120 -L1_BRIDGE_MAINNET=0x3154Cf16ccdb4C6d922629664174b904d80F2C35 - -# L1 RPC URLs -L1_SEPOLIA_RPC_URL?=https://ethereum-sepolia-rpc.publicnode.com -L1_MAINNET_RPC_URL?=https://ethereum-rpc.publicnode.com - -# Deployments -# Following: https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token#create-an-l2-erc-20-token -# The deployed L2 token address is extracted from the OptimismMintableERC20Created event logs. - -deploy-base-sepolia: ## Deploy ALIGN token on BaseSepolia via OptimismMintableERC20Factory - @echo "Deploying ALIGN on BaseSepolia..." - @echo "L1 Remote Token: $(L1_TOKEN_SEPOLIA)" - @echo "Factory: $(FACTORY)" +deploy-base-sepolia: ## Deploy ALIGN on BaseSepolia @L2_TOKEN=$$(cast send $(FACTORY) \ "createOptimismMintableERC20(address,string,string)" \ - $(L1_TOKEN_SEPOLIA) $(TOKEN_NAME) $(TOKEN_SYMBOL) \ + $(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 "---------------------------------------------" && \ - echo "ALIGN L2 Token deployed at: $$L2_TOKEN" && \ - echo "---------------------------------------------" + echo "L2 Token deployed at: $$L2_TOKEN" -deploy-base-mainnet: ## Deploy ALIGN token on BaseMainnet via OptimismMintableERC20Factory - @echo "Deploying ALIGN on BaseMainnet..." - @echo "L1 Remote Token: $(L1_TOKEN_MAINNET)" - @echo "Factory: $(FACTORY)" +deploy-base-mainnet: ## Deploy ALIGN on BaseMainnet @L2_TOKEN=$$(cast send $(FACTORY) \ "createOptimismMintableERC20(address,string,string)" \ - $(L1_TOKEN_MAINNET) $(TOKEN_NAME) $(TOKEN_SYMBOL) \ + $(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 "---------------------------------------------" && \ - echo "ALIGN L2 Token deployed at: $$L2_TOKEN" && \ - echo "---------------------------------------------" - -# Verification + echo "L2 Token deployed at: $$L2_TOKEN" -verify-base-sepolia: ## Verify the deployed L2 token on BaseSepolia (requires L2_TOKEN) - @echo "Verifying L2 token deployment on BaseSepolia..." - @echo "--- Token metadata ---" - cast call $(L2_TOKEN) "name()(string)" --rpc-url $(BASE_SEPOLIA_RPC_URL) - cast call $(L2_TOKEN) "symbol()(string)" --rpc-url $(BASE_SEPOLIA_RPC_URL) - cast call $(L2_TOKEN) "decimals()(uint8)" --rpc-url $(BASE_SEPOLIA_RPC_URL) - @echo "--- Bridge configuration ---" - cast call $(L2_TOKEN) "REMOTE_TOKEN()(address)" --rpc-url $(BASE_SEPOLIA_RPC_URL) - cast call $(L2_TOKEN) "BRIDGE()(address)" --rpc-url $(BASE_SEPOLIA_RPC_URL) - @echo "--- Total supply (should be 0 before any bridging) ---" - cast call $(L2_TOKEN) "totalSupply()(uint256)" --rpc-url $(BASE_SEPOLIA_RPC_URL) +# --- Verification --- -verify-base-mainnet: ## Verify the deployed L2 token on BaseMainnet (requires L2_TOKEN) - @echo "Verifying L2 token deployment on BaseMainnet..." - @echo "--- Token metadata ---" - cast call $(L2_TOKEN) "name()(string)" --rpc-url $(BASE_MAINNET_RPC_URL) - cast call $(L2_TOKEN) "symbol()(string)" --rpc-url $(BASE_MAINNET_RPC_URL) - cast call $(L2_TOKEN) "decimals()(uint8)" --rpc-url $(BASE_MAINNET_RPC_URL) - @echo "--- Bridge configuration ---" - cast call $(L2_TOKEN) "REMOTE_TOKEN()(address)" --rpc-url $(BASE_MAINNET_RPC_URL) - cast call $(L2_TOKEN) "BRIDGE()(address)" --rpc-url $(BASE_MAINNET_RPC_URL) - @echo "--- Total supply (should be 0 before any bridging) ---" - cast call $(L2_TOKEN) "totalSupply()(uint256)" --rpc-url $(BASE_MAINNET_RPC_URL) +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 -> L2 -# Step 1: Approve the L1StandardBridge to spend ALIGN tokens -# Step 2: Deposit tokens via the bridge (tokens appear on Base after ~20 min) -# Requires: AMOUNT (in wei), USER_PRIVATE_KEY -AMOUNT?=1000000000000000000 # Default to 1 ALIGN (18 decimals) -USER_PRIVATE_KEY?= +# --- Bridging L1 -> Base --- -bridge-l1-to-base-sepolia: ## Bridge ALIGN tokens from Sepolia L1 to BaseSepolia (requires AMOUNT) - @echo "Step 1: Approving L1StandardBridge to spend $(AMOUNT) ALIGN on Sepolia..." - cast send $(L1_TOKEN_SEPOLIA) \ - "approve(address,uint256)" \ - $(L1_BRIDGE_SEPOLIA) $(AMOUNT) \ - --private-key $(USER_PRIVATE_KEY) \ - --rpc-url $(L1_SEPOLIA_RPC_URL) - @echo "Step 2: Depositing $(AMOUNT) ALIGN to BaseSepolia..." - cast send $(L1_BRIDGE_SEPOLIA) \ - "depositERC20(address,address,uint256,uint32,bytes)" \ +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) - @echo "---------------------------------------------" - @echo "Bridge initiated. Tokens will appear on BaseSepolia in ~20 minutes." - @echo "---------------------------------------------" + --private-key $(USER_PRIVATE_KEY) --rpc-url $(L1_SEPOLIA_RPC_URL) -bridge-l1-to-base-mainnet: ## Bridge ALIGN tokens from Ethereum L1 to Base (requires AMOUNT) - @echo "Step 1: Approving L1StandardBridge to spend $(AMOUNT) ALIGN on Mainnet..." - cast send $(L1_TOKEN_MAINNET) \ - "approve(address,uint256)" \ - $(L1_BRIDGE_MAINNET) $(AMOUNT) \ - --private-key $(USER_PRIVATE_KEY) \ - --rpc-url $(L1_MAINNET_RPC_URL) - @echo "Step 2: Depositing $(AMOUNT) ALIGN to Base..." - cast send $(L1_BRIDGE_MAINNET) \ - "depositERC20(address,address,uint256,uint32,bytes)" \ +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) - @echo "---------------------------------------------" - @echo "Bridge initiated. Tokens will appear on Base in ~20 minutes." - @echo "---------------------------------------------" + --private-key $(USER_PRIVATE_KEY) --rpc-url $(L1_MAINNET_RPC_URL) diff --git a/claim_contracts/base/README.md b/claim_contracts/base/README.md index 0ca5c6ec4d..98528d15d7 100644 --- a/claim_contracts/base/README.md +++ b/claim_contracts/base/README.md @@ -1,130 +1,57 @@ # ALIGN Token on Base L2 -Deployment of the Aligned Token (ALIGN) on Base L2 using the OptimismMintableERC20Factory. - -Based on the [OP Standard Bridge Standard Token tutorial](https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token). - -## Overview - -Base is an OP Stack chain. The ALIGN token on Base is created via the -`OptimismMintableERC20Factory` predeploy at `0x4200000000000000000000000000000000000012`. -This produces a standard `OptimismMintableERC20` token that is automatically compatible -with the OP Standard Bridge for L1 <-> L2 token transfers. - -No custom Solidity contract is deployed — the factory handles everything. - -## Prerequisites - -- [Foundry](https://book.getfoundry.sh/getting-started/installation) (`cast` CLI) -- `jq` for parsing transaction receipts -- An account with ETH on the target Base network (for gas) -- The L1 ALIGN token proxy address (deployed on Ethereum Sepolia or Mainnet) +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 -1. Copy `.env.example` to `.env` and fill in the values: - - ```bash - cp .env.example .env - ``` - -2. Set the L1 token addresses. The Makefile defaults are: - - `L1_TOKEN_SEPOLIA=0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A` - - `L1_TOKEN_MAINNET=` (set when ready) - - You can override them via `.env` or by passing them to `make`. - -## Deployment - -The deploy targets call `createOptimismMintableERC20` on the factory and extract the -deployed L2 token address from the `OptimismMintableERC20Created` event logs, following -the [OP tutorial](https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token#create-an-l2-erc-20-token). - -### BaseSepolia (Testnet) - ```bash -source .env -make deploy-base-sepolia +cp .env.example .env +# Fill in DEPLOYER_PRIVATE_KEY and USER_PRIVATE_KEY ``` -### BaseMainnet (Production) +## Deploy -```bash -source .env -make deploy-base-mainnet -``` - -The output will print the deployed L2 token address. - -## Verification - -After deployment, verify the token was created correctly: +The L2 token is created via the `OptimismMintableERC20Factory` predeploy at `0x4200000000000000000000000000000000000012`. No custom contract is needed. ```bash -make verify-base-sepolia L2_TOKEN= +make deploy-base-sepolia # BaseSepolia +make deploy-base-mainnet # BaseMainnet ``` -Expected output: - -| Check | Expected | -|-------|----------| -| `name()` | `"Aligned Token"` | -| `symbol()` | `"ALIGN"` | -| `decimals()` | `18` | -| `REMOTE_TOKEN()` | L1 token proxy address | -| `BRIDGE()` | `0x4200000000000000000000000000000000000010` | -| `totalSupply()` | `0` (before any bridging) | - -## Bridging Tokens (L1 -> Base) - -After the L2 token is deployed, tokens can be bridged from L1 to Base using the -OP Standard Bridge. The Makefile handles the two-step process (approve + deposit). - -### Bridge Addresses - -Source: [Base Contracts](https://docs.base.org/chain/base-contracts) - -| Network | L1StandardBridge | L2StandardBridge | -|----------------------|------------------|------------------| -| Sepolia / BaseSepolia | [`0xfd0Bf71F60660E2f608ed56e1659C450eB113120`](https://sepolia.etherscan.io/address/0xfd0Bf71F60660E2f608ed56e1659C450eB113120) | [`0x4200000000000000000000000000000000000010`](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000010) | -| Mainnet / Base | [`0x3154Cf16ccdb4C6d922629664174b904d80F2C35`](https://etherscan.io/address/0x3154Cf16ccdb4C6d922629664174b904d80F2C35) | [`0x4200000000000000000000000000000000000010`](https://basescan.org/address/0x4200000000000000000000000000000000000010) | - -### BaseSepolia +## Verify ```bash -make bridge-l1-to-base-sepolia USER_PRIVATE_KEY=0x... AMOUNT=1000000000000000000 +make verify L2_TOKEN=
RPC_URL=https://sepolia.base.org ``` -### BaseMainnet +## Bridge (L1 -> Base) + +Approve + deposit in one command. `AMOUNT` is in wei (1e18 = 1 ALIGN). ```bash -make bridge-l1-to-base-mainnet USER_PRIVATE_KEY=0x... AMOUNT=1000000000000000000 +make bridge-l1-to-base-sepolia AMOUNT=1000000000000000000 +make bridge-l1-to-base-mainnet AMOUNT=1000000000000000000 ``` -`USER_PRIVATE_KEY` is the private key of the account holding ALIGN tokens on L1. -`AMOUNT` is in wei (the example above bridges 1 ALIGN token = 1e18 wei). -Both can also be set in the `.env` file. - -The command will: -1. Approve the L1StandardBridge to spend `AMOUNT` ALIGN tokens on L1 -2. Call `depositERC20` on the L1StandardBridge to initiate the bridge +Tokens appear on Base after ~20 minutes. -Tokens will appear on Base after the L1 transaction is included and the -message is relayed (~20 minutes). +## Bridge Addresses -### L2 -> L1 (Withdrawal) +Source: [Base Contracts](https://docs.base.org/chain/base-contracts) -Users call `withdraw` on the L2StandardBridge. After the challenge period -(7 days on mainnet), prove and finalize the withdrawal on L1 to unlock tokens. +| 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 / BaseSepolia | `0xd2Fd114f098b355321cB3424400f3CC6a0d75C9A` | `0x4AAcFbc2C31598a560b285dB20966E00B73F9F81` | -| Mainnet / Base | TBD | TBD | +| 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) -- [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io) +- [Base Contracts](https://docs.base.org/chain/base-contracts) From 5c3ebfeedd8b36bf0ba6dd6138c85d9f65850bf8 Mon Sep 17 00:00:00 2001 From: JuArce <52429267+JuArce@users.noreply.github.com> Date: Fri, 13 Mar 2026 19:47:42 -0300 Subject: [PATCH 3/4] fix: ci --- docker/batcher.Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/batcher.Dockerfile b/docker/batcher.Dockerfile index 7feff7c010..66917350e7 100644 --- a/docker/batcher.Dockerfile +++ b/docker/batcher.Dockerfile @@ -11,6 +11,9 @@ FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef FROM chef AS planner +COPY crates/sdk/Cargo.toml /aligned_layer/crates/sdk/Cargo.toml +COPY crates/sdk/src/lib.rs /aligned_layer/crates/sdk/src/lib.rs + COPY crates/batcher/Cargo.toml /aligned_layer/crates/batcher/Cargo.toml COPY crates/batcher/src/main.rs /aligned_layer/crates/batcher/src/main.rs WORKDIR /aligned_layer/crates/batcher/ From 2b91bc9106bc5a28d88bd6cd632ab3148150e0f3 Mon Sep 17 00:00:00 2001 From: JuArce <52429267+JuArce@users.noreply.github.com> Date: Fri, 13 Mar 2026 20:08:03 -0300 Subject: [PATCH 4/4] feat: add bridge to L1 scripts --- claim_contracts/base/.gitignore | 3 + claim_contracts/base/Makefile | 57 +- claim_contracts/base/README.md | 30 + claim_contracts/base/package-lock.json | 777 +++++++++++++++++++++++ claim_contracts/base/package.json | 12 + claim_contracts/base/scripts/withdraw.ts | 240 +++++++ claim_contracts/base/tsconfig.json | 10 + 7 files changed, 1128 insertions(+), 1 deletion(-) create mode 100644 claim_contracts/base/package-lock.json create mode 100644 claim_contracts/base/package.json create mode 100644 claim_contracts/base/scripts/withdraw.ts create mode 100644 claim_contracts/base/tsconfig.json diff --git a/claim_contracts/base/.gitignore b/claim_contracts/base/.gitignore index 85198aaa55..35116f49a1 100644 --- a/claim_contracts/base/.gitignore +++ b/claim_contracts/base/.gitignore @@ -12,3 +12,6 @@ docs/ # Dotenv file .env + +# Node +node_modules/ diff --git a/claim_contracts/base/Makefile b/claim_contracts/base/Makefile index 8fc6919fda..c1732ef494 100644 --- a/claim_contracts/base/Makefile +++ b/claim_contracts/base/Makefile @@ -1,6 +1,7 @@ -include .env +export -.PHONY: help deploy-base-sepolia deploy-base-mainnet verify bridge-l1-to-base-sepolia bridge-l1-to-base-mainnet +.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}' @@ -53,3 +54,57 @@ bridge-l1-to-base-mainnet: ## Bridge ALIGN from Ethereum to Base (requires AMOUN 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 diff --git a/claim_contracts/base/README.md b/claim_contracts/base/README.md index 98528d15d7..748f6b7c69 100644 --- a/claim_contracts/base/README.md +++ b/claim_contracts/base/README.md @@ -35,6 +35,35 @@ 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= + make prove-withdrawal-mainnet TX_HASH= + ``` + +3. **Finalize** on L1 — wait 7 days challenge period (shorter on testnet), then finalize: + + ```bash + make finalize-withdrawal-sepolia TX_HASH= + make finalize-withdrawal-mainnet 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) @@ -54,4 +83,5 @@ Source: [Base Contracts](https://docs.base.org/chain/base-contracts) ## 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) diff --git a/claim_contracts/base/package-lock.json b/claim_contracts/base/package-lock.json new file mode 100644 index 0000000000..33731e661a --- /dev/null +++ b/claim_contracts/base/package-lock.json @@ -0,0 +1,777 @@ +{ + "name": "aligned-base-bridge", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aligned-base-bridge", + "dependencies": { + "viem": "^2" + }, + "devDependencies": { + "tsx": "^4", + "typescript": "^5" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/ox": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.0.tgz", + "integrity": "sha512-WLOB7IKnmI3Ol6RAqY7CJdZKl8QaI44LN91OGF1061YIeN6bL5IsFcdp7+oQShRyamE/8fW/CBRWhJAOzI35Dw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/viem": { + "version": "2.47.2", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.47.2.tgz", + "integrity": "sha512-etDIwDgmDiGaPg8rUbJtUFuC3/nAJCbhMYyfh5dOcqNNkzBWTNcS2VluPSM5JVo+9U3b2hle2RkBEq3+xyvlvg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.14.0", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/claim_contracts/base/package.json b/claim_contracts/base/package.json new file mode 100644 index 0000000000..cb2184d9d6 --- /dev/null +++ b/claim_contracts/base/package.json @@ -0,0 +1,12 @@ +{ + "name": "aligned-base-bridge", + "private": true, + "type": "module", + "dependencies": { + "viem": "^2" + }, + "devDependencies": { + "tsx": "^4", + "typescript": "^5" + } +} diff --git a/claim_contracts/base/scripts/withdraw.ts b/claim_contracts/base/scripts/withdraw.ts new file mode 100644 index 0000000000..87745ec0e1 --- /dev/null +++ b/claim_contracts/base/scripts/withdraw.ts @@ -0,0 +1,240 @@ +import { + createPublicClient, + createWalletClient, + http, + type Hash, + type Chain, +} from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { sepolia, baseSepolia, mainnet, base } from "viem/chains"; +import { + publicActionsL1, + walletActionsL1, + publicActionsL2, +} from "viem/op-stack"; + +// --- Config --- + +type Network = "sepolia" | "mainnet"; + +function getChains(network: Network): { l1: Chain; l2: Chain } { + return network === "sepolia" + ? { l1: sepolia, l2: baseSepolia } + : { l1: mainnet, l2: base }; +} + +function getRpcUrls(network: Network) { + if (network === "sepolia") { + return { + l1: process.env.L1_SEPOLIA_RPC_URL || "https://ethereum-sepolia-rpc.publicnode.com", + l2: process.env.BASE_SEPOLIA_RPC_URL || "https://sepolia.base.org", + }; + } + return { + l1: process.env.L1_MAINNET_RPC_URL || "https://ethereum-rpc.publicnode.com", + l2: process.env.BASE_MAINNET_RPC_URL || "https://mainnet.base.org", + }; +} + +function getPrivateKey(): Hash { + const key = process.env.USER_PRIVATE_KEY; + if (!key) { + console.error("Error: USER_PRIVATE_KEY not set"); + process.exit(1); + } + return key as Hash; +} + +function createPublicClients(network: Network) { + const chains = getChains(network); + const rpc = getRpcUrls(network); + + const publicClientL1 = createPublicClient({ + chain: chains.l1, + transport: http(rpc.l1), + }).extend(publicActionsL1()); + + const publicClientL2 = createPublicClient({ + chain: chains.l2, + transport: http(rpc.l2), + }).extend(publicActionsL2()); + + return { publicClientL1, publicClientL2, chains }; +} + +function createClients(network: Network) { + const { publicClientL1, publicClientL2, chains } = createPublicClients(network); + const rpc = getRpcUrls(network); + const account = privateKeyToAccount(getPrivateKey()); + + const walletClientL1 = createWalletClient({ + account, + chain: chains.l1, + transport: http(rpc.l1), + }).extend(walletActionsL1()); + + return { publicClientL1, walletClientL1, publicClientL2, chains }; +} + +// --- Commands --- + +async function prove(txHash: Hash, network: Network) { + const { publicClientL1, walletClientL1, publicClientL2, chains } = + createClients(network); + + console.log(`Getting withdrawal receipt for ${txHash}...`); + const receipt = await publicClientL2.getTransactionReceipt({ hash: txHash }); + + console.log("Waiting for withdrawal to be provable (~1 hour)..."); + const { output, withdrawal } = await publicClientL1.waitToProve({ + receipt, + targetChain: chains.l2, + }); + + console.log("Building prove withdrawal args..."); + const proveArgs = await publicClientL2.buildProveWithdrawal({ + output, + withdrawal, + }); + + console.log("Proving withdrawal on L1..."); + const proveHash = await walletClientL1.proveWithdrawal(proveArgs); + + console.log("Waiting for prove tx confirmation..."); + await publicClientL1.waitForTransactionReceipt({ hash: proveHash }); + + console.log(`Withdrawal proved: ${proveHash}`); + return proveHash; +} + +async function finalize(txHash: Hash, network: Network) { + const { publicClientL1, walletClientL1, publicClientL2, chains } = + createClients(network); + + console.log(`Getting withdrawal receipt for ${txHash}...`); + const receipt = await publicClientL2.getTransactionReceipt({ hash: txHash }); + + console.log("Waiting for finalization period (7 days on mainnet)..."); + // Use getWithdrawalStatus instead of waitToFinalize due to a viem bug + // with Portal v3+ (Fault Proof System) where waitToFinalize incorrectly + // throws "Withdrawal has not been proven on L1". + while (true) { + const withdrawalStatus = await publicClientL1.getWithdrawalStatus({ + receipt, + targetChain: chains.l2, + }); + console.log(` Status: ${withdrawalStatus}`); + if (withdrawalStatus === "ready-to-finalize") break; + if (withdrawalStatus === "finalized") { + console.log("Withdrawal already finalized."); + return; + } + await new Promise((resolve) => setTimeout(resolve, 10_000)); + } + + console.log("Finalizing withdrawal on L1..."); + const finalizeHash = await walletClientL1.finalizeWithdrawal({ + receipt, + targetChain: chains.l2, + }); + + console.log("Waiting for finalize tx confirmation..."); + await publicClientL1.waitForTransactionReceipt({ hash: finalizeHash }); + + console.log(`Withdrawal finalized: ${finalizeHash}`); + return finalizeHash; +} + +async function status(txHash: Hash, network: Network) { + const { publicClientL1, publicClientL2, chains } = createPublicClients(network); + + console.log(`Getting withdrawal receipt for ${txHash}...`); + const receipt = await publicClientL2.getTransactionReceipt({ hash: txHash }); + + const withdrawalStatus = await publicClientL1.getWithdrawalStatus({ + receipt, + targetChain: chains.l2, + }); + + console.log(`Withdrawal status: ${withdrawalStatus}`); +} + +async function full(txHash: Hash, network: Network) { + await prove(txHash, network); + await finalize(txHash, network); + console.log("Withdrawal complete."); +} + +// --- CLI --- + +function usage() { + console.log(`Usage: npx tsx scripts/withdraw.ts --tx-hash --network + +Commands: + prove Prove a withdrawal on L1 (wait ~1 hour after initiation) + finalize Finalize a withdrawal on L1 (wait 7 days after proving) + full Run prove + finalize sequentially + status Check withdrawal status`); + process.exit(1); +} + +function parseArgs() { + const args = process.argv.slice(2); + const command = args[0]; + + if (!command || !["prove", "finalize", "full", "status"].includes(command)) { + usage(); + } + + let txHash: string | undefined; + let network: Network = "sepolia"; + + for (let i = 1; i < args.length; i++) { + if (args[i] === "--tx-hash" && args[i + 1]) { + txHash = args[++i]; + } else if (args[i] === "--network" && args[i + 1]) { + network = args[++i] as Network; + } + } + + if (!txHash) { + console.error("Error: --tx-hash is required"); + usage(); + } + + if (!["sepolia", "mainnet"].includes(network)) { + console.error("Error: --network must be sepolia or mainnet"); + usage(); + } + + return { command: command!, txHash: txHash as Hash, network }; +} + +async function main() { + const { command, txHash, network } = parseArgs(); + + console.log(`Network: ${network}`); + console.log(`Command: ${command}`); + console.log(`TX Hash: ${txHash}`); + console.log("---"); + + switch (command) { + case "prove": + await prove(txHash, network); + break; + case "finalize": + await finalize(txHash, network); + break; + case "full": + await full(txHash, network); + break; + case "status": + await status(txHash, network); + break; + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/claim_contracts/base/tsconfig.json b/claim_contracts/base/tsconfig.json new file mode 100644 index 0000000000..3b933a4fc0 --- /dev/null +++ b/claim_contracts/base/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + } +}