| name | etherclaw-crosschain |
| description | EtherClaw InstanceFactory/TheWorld cross-chain architecture - Optimistic Upgrader system spanning EVM (Base Mainnet) and ICP, ERC-7546, auditor approval flow |
| triggers | ["InstanceFactory","TheWorld","Optimistic Upgrader","cross-chain","etherclaw","ERC-7546","Base Mainnet","ICP canister","auditor approval","proposeUpgrade","fetchEvmLogs"] |
EtherClaw Cross-Chain Architecture
Overview
EtherClaw is a cross-chain system where:
- InstanceFactory (EVM factory contract) runs on EVM (Base Mainnet)
- TheWorld (Optimistic Upgrader Canister) runs on ICP (Internet Computer)
Both are implemented in Idris2 using different backends:
- InstanceFactory: idris2-evm (Yul codegen)
- TheWorld: idris2-icwasm (RefC -> WASM)
Architecture Diagram
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ EtherClaw Architecture โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Base Mainnet (EVM) ICP Mainnet โ
โ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ Instance โ โ TheWorld โ โ
โ โ Factory โ โ (Canister) โ โ
โ โ idris2-ouf โ โ idris2-ouc โ โ
โ โโโโโโโโฌโโโโโโโโ โโโโโโโโฌโโโโโโโโ โ
โ โ โ โ
โ โ createOU() โ fetchEvmLogs() โ
โ โผ โ โ
โ โโโโโโโโโโโโโโโโ โ โ
โ โ OU โ โโโ Event (UpgradeProposed) โโโโถ Detect โ
โ โ (Upgrader) โ โ โ
โ โ โ โโโ Approval (via signing) โโโ submitReview() โ
โ โโโโโโโโโโโโโโโโ โ โ
โ โ โ
โ โโโโโโโโโโโโโโโโ โ โ
โ โ Target โ โ Owner is OU (not EOA) โ โ
โ โ (TextDAO etc)โ Upgrades only via OU โ โ
โ โโโโโโโโโโโโโโโโ โ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Key Concept: Ownership Transfer
Target contracts (e.g., TextDAO) transfer ownership to OU, NOT to an EOA.
This means:
- Original deployer deploys TextDAO
- Deployer calls
TextDAO.transferOwnership(OU_ADDRESS)
- Now only OU can upgrade TextDAO
- OU requires Auditor approval before executing upgrades
- โ Multi-sig + Optimistic approval for safe upgrades
E2E Flow
1. Deploy InstanceFactory (Base Mainnet)
cd /Users/bob/code/etherclaw/pkgs/Idris2Ouf
solc --strict-assembly --optimize build/exec/idris2-ouf.yul --bin > /tmp/ouf.bin
cast send --rpc-url https://mainnet.base.org \
--private-key $ETH_EOA_PRIVATE_KEY \
--create "0x$(cat /tmp/ouf.bin)"
2. Create OU Instance & Transfer Ownership
cast send $INSTANCE_FACTORY_ADDRESS "createOU()" --rpc-url https://mainnet.base.org --private-key $KEY
cast send $TARGET_CONTRACT "transferOwnership(address)" $OU_ADDRESS --rpc-url https://mainnet.base.org --private-key $KEY
3. Propose Upgrade (EVM)
cast send $OU_ADDRESS "proposeUpgrade(address,address,bytes)" \
$TARGET $NEW_IMPL 0x \
--rpc-url https://mainnet.base.org --private-key $KEY
4. Detect Event (ICP)
dfx canister call --network ic nrkou-hqaaa-aaaah-qq6qa-cai fetchEvmLogs \
'(41265820 : nat64, 41265840 : nat64, opt "0x<INSTANCE_FACTORY_ADDRESS>")'
5. Auditor Review (ICP)
dfx canister call --network ic nrkou-hqaaa-aaaah-qq6qa-cai submitReview \
'(proposalId, "approve")'
6. Execute Upgrade (EVM)
cast send $OU_ADDRESS "executeUpgrade(uint256)" $PROPOSAL_ID \
--rpc-url https://mainnet.base.org --private-key $KEY
Deployed Addresses
TheWorld (ICP Mainnet)
- Canister ID:
nrkou-hqaaa-aaaah-qq6qa-cai
InstanceFactory (Base Mainnet, Chain ID: 8453)
ใใผใธใงใณ็ฎก็: ๅฎ้็จใงใฏๅ InstanceFactory ใใผใธใงใณใซ็ดใฅใ OU ใคใณในใฟใณในใใใใใใTheWorld ๅดใงใใผใธใงใณ็ฎก็ใๅฟ
่ฆใ
| Version | Contract Address | Block | Status | Features |
|---|
| v8 | 0xb094b55924a790c4c9f86e16beb93d1261ed9891 | 41270838 | Active | idris2-yul Codegenไฟฎๆญฃๆธใฟ |
| v7 | 0x58abBd4b6dF53f3DDb335Ac45437a467c854Ad1d | 41270046 | Deprecated | ๆๅใใฐๅ้ฟ็ |
| v3 | 0xC7Efeca27d2e4D16e6354f2a23cd692210BbB19b | 41268931 | Deprecated | keccak256 event sigs |
| v2 | 0x03A460DC91A4606317C90679D4058c8250568eCd | 41267491 | Deprecated | Full dispatcher |
| v1 | 0xFB636FF84752A918F6962Fa40a56697Ed61b7459 | 41265830 | Deprecated | Initial test |
ๆฐ่ฆ OU ไฝๆๆ: ๅธธใซๆๆฐใฎ Active ใใผใธใงใณใไฝฟ็จใ
ๆขๅญ OU ็ฎก็: OU ใ็ดใฅใ InstanceFactory ใใผใธใงใณใ็ขบ่ชใใฆ้ฉๅใช ABI ใงๆไฝใ
RPC & Keys
- RPC:
https://mainnet.base.org
- Keys file:
/Users/bob/code/idris2-ouc/.env
ETH_EOA_ADDRESS
ETH_EOA_PRIVATE_KEY
TheWorld Methods
dfx canister call --network ic $CANISTER_ID getVersion '()'
dfx canister call --network ic $CANISTER_ID getProposalCount '()'
dfx canister call --network ic $CANISTER_ID getProposal '(0)'
dfx canister call --network ic $CANISTER_ID testEvmRpc
dfx canister call --network ic $CANISTER_ID submitProposal '("{...}")'
dfx canister call --network ic $CANISTER_ID submitReview '(proposalId, "approve")'
dfx canister call --network ic $CANISTER_ID fetchEvmLogs '(from, to, opt address)'
ERC-7546 (NOT ERC-2535)
InstanceFactory uses ERC-7546 Upgradeable Clone, NOT ERC-2535 Diamond Standard.
| Standard | Function | Selector |
|---|
| ERC-7546 | getImplementation(bytes4) | 0xdc9cc645 |
| ERC-2535 | facetAddress(bytes4) | 0xcdffacc6 |
When monitoring OU from TheWorld, use getImplementation (0xdc9cc645).
Project Locations
/Users/bob/code/etherclaw/pkgs/
โโโ Idris2TheWorld/ # ICP Canister (idris2-icwasm)
โ โโโ CLAUDE.md # Detailed E2E docs
โ โโโ can.did # Candid interface
โ โโโ lib/ic0/canister_entry.c
โโโ Idris2Ouf/ # EVM Contract (idris2-evm)
โ โโโ CLAUDE.md
โ โโโ build/exec/idris2-ouf.yul
โโโ Idris2TextDao/ # Example target contract
โโโ Idris2IcpIndexer/ # SQLite WASI for TheWorld
Build Commands
TheWorld (ICP)
cd /Users/bob/code/etherclaw/pkgs/Idris2TheWorld
./scripts/build-canister.sh
dfx canister --network ic install theworld --mode upgrade --wasm build/theworld_stubbed.wasm --yes
InstanceFactory (EVM) - idris2-yul ใซใใใใซใ
้่ฆ: InstanceFactory ใฏ pack build ใงใฏใชใ idris2-yul (ใซในใฟใ Idris2) ใงใใซใใใใ
ใใใฏใจใณใๆคๅบใซใผใซ
| import ๆ | ใใใฏใจใณใ | ใใผใซ |
|---|
EVM.Primitives | EVM/Yul | idris2-yul --codegen yul |
IC0.* / Candid.* | ICP WASM | ./scripts/build-canister.sh (RefCโemcc) |
| ใชใ (ๆจๆบใฉใคใใฉใชใฎใฟ) | Chez Scheme | pack build |
ใใซใๆ้
cd /Users/bob/code/etherclaw/pkgs/Idris2Ouf
IDRIS2_PACKAGE_PATH=$(pack package-path) idris2-yul \
--codegen yul \
-p base -p contrib -p idris2-evm -p idris2-subcontract \
--source-dir src \
-o idris2-ouf \
src/Main.idr
solc --strict-assembly --optimize build/exec/idris2-ouf.yul --bin > /tmp/ouf.bin
cast send --rpc-url https://mainnet.base.org \
--private-key $ETH_EOA_PRIVATE_KEY \
--create "0x$(cat /tmp/ouf.bin)"
idris2-yul ใฎใคใณในใใผใซ
cd /Users/bob/code/idris2-magical-utils/pkgs/Idris2Evm
pack install idris2-evm.ipkg
EVM Precompiled Contracts & keccak256
Precompiled Contracts (0x01-0x0a)
EVMใซใฏไบๅใณใณใใคใซๆธใฟใณใณใใฉใฏใใใใใไฝใณในใใงๆๅทๆไฝใๅฏ่ฝ:
| Address | Name | Gas | Idris2 ๅผใณๅบใ |
|---|
| 0x01 | ecrecover | 3000 | staticcall gas 0x01 inOff inSize outOff outSize |
| 0x02 | SHA256 | 60+12/word | staticcall gas 0x02 ... |
| 0x03 | RIPEMD160 | 600+120/word | staticcall gas 0x03 ... |
| 0x04 | identity | 15+3/word | staticcall gas 0x04 ... (memcpy) |
| 0x05 | modexp | ่ค้ | staticcall gas 0x05 ... (RSA็ญ) |
| 0x06 | ecAdd | 150 | staticcall gas 0x06 ... (BN256) |
| 0x07 | ecMul | 6000 | staticcall gas 0x07 ... (BN256) |
| 0x08 | ecPairing | 45000+34000*k | staticcall gas 0x08 ... (zkSNARKๆค่จผ) |
| 0x09 | blake2f | 1/round | staticcall gas 0x09 ... |
| 0x0a | point evaluation | 50000 | staticcall gas 0x0a ... (EIP-4844 KZG) |
ๆณจๆ: keccak256 ใฏ Precompiled ใงใฏใชใ opcode (0x20) ใจใใฆๅฎ่ฃ
ใ
Precompiled ๅผใณๅบใๅฎ่ฃ
ไพ (ecrecover)
-- EVM.Primitives
import EVM.Primitives
||| ECDSA็ฝฒๅใใใขใใฌในใๅพฉๅ
||| ecrecover(hash, v, r, s) -> address
ecrecover : Integer -> Integer -> Integer -> Integer -> IO Integer
ecrecover hash v r s = do
-- ๅ
ฅๅใใกใขใชใซ้
็ฝฎ
mstore 0 hash -- 0-31: message hash
mstore 32 v -- 32-63: v
mstore 64 r -- 64-95: r
mstore 96 s -- 96-127: s
-- Precompiled contract 0x01 ใๅผใณๅบใ
success <- staticcall 3000 0x01 0 128 128 32
if success == 1
then mload 128 -- ๅบๅใใ่ชญใฟๅใ
else pure 0 -- ๅคฑๆ
||| SHA256ใใใทใฅ (Precompiled 0x02)
sha256 : Integer -> Integer -> IO Integer
sha256 offset size = do
success <- staticcall 100 0x02 offset size 0 32
if success == 1 then mload 0 else pure 0
keccak256 ใฎ2ใคใฎๅฉ็จๆนๆณ
1. ใฉใณใฟใคใ (EVM opcode 0x20)
import EVM.Primitives
-- EVM.Primitives ใใใจใฏในใใผใๆธใฟ
keccak256 : Integer -> Integer -> IO Integer
-- ไฝฟ็จไพ: ในใใฌใผใธในใญใใ่จ็ฎ (mapping)
computeMappingSlot : Integer -> Integer -> IO Integer
computeMappingSlot key base = do
mstore 0 key
mstore 32 base
keccak256 0 64 -- keccak256(key . base)
2. ใณใณใใคใซๆ (%runElab + cast)
import Language.Reflection
||| ใณใณใใคใซๆใซใปใฌใฏใฟใ่จ็ฎ
%macro
computeSelector : String -> Elab Integer
computeSelector sig = do
-- cast keccak ใๅผใณๅบใใฆ็ตๆใใใผใน
-- cast keccak "transfer(address,uint256)" => 0xa9059cbb...
result <- runIO $ popen "cast keccak" sig
let selector = parseHex (take 10 result) -- ๅ
้ ญ4ใใคใ
pure selector
-- ไฝฟ็จไพ
SEL_TRANSFER : Integer
SEL_TRANSFER = %runElab computeSelector "transfer(address,uint256)"
-- ใณใณใใคใซๆใซ 0xa9059cbb ใๅใ่พผใพใใ
3. ็ด็ฒ Idris2 ๅฎ่ฃ
(ใใผใฟใใซ)
-- Keccak-256 ใขใซใดใชใบใ ใIdris2ใงๅฎ่ฃ
-- (SHA-3 ใฎๅ่บซใsponge construction)
keccak256Pure : List Bits8 -> Bits256
keccak256Pure input =
let padded = pad input 136 -- rate = 1088 bits = 136 bytes
absorbed = foldl absorb initialState (chunk 136 padded)
in squeeze absorbed
-- ใปใฌใฏใฟ่จ็ฎ
selectorOf : String -> Integer
selectorOf sig =
let hash = keccak256Pure (utf8Encode sig)
in extractFirst4Bytes hash
ๅๅฎๅ
จใปใฌใฏใฟ (Dependent Types)
||| ้ขๆฐใทใฐใใใฃใจ่จ็ฎใใใใปใฌใฏใฟๅคใฎใใข
||| ๅใฌใใซใงใทใฐใใใฃใจใปใฌใฏใฟใฎๅฏพๅฟใไฟ่จผ
record Selector where
constructor MkSelector
signature : String
value : Integer
0 proof : value = selectorOf signature -- ่จผๆ (erased)
||| ใณใณใใคใซๆใซใปใฌใฏใฟใ็ๆ
mkSelector : (sig : String) -> Selector
mkSelector sig = MkSelector sig (selectorOf sig) Refl
-- ไฝฟ็จไพ
SEL_TRANSFER : Selector
SEL_TRANSFER = mkSelector "transfer(address,uint256)"
-- ใใฃในใใใใฃใงไฝฟ็จ
dispatch : IO ()
dispatch = do
sel <- readSelector
if sel == SEL_TRANSFER.value
then handleTransfer
else ...
้ข้ฃใใกใคใซ
/Users/bob/code/idris2-magical-utils/pkgs/Idris2Evm/src/EVM/Primitives.idr - keccak256 opcode, staticcall
/Users/bob/code/idris2-magical-utils/pkgs/Idris2Evm/src/YulMain.idr - idris2-yul ใจใณใใชใใคใณใ
/Users/bob/code/idris2-magical-utils/pkgs/Idris2Evm/src/Compiler/EVM/ABI.idr - ใปใฌใฏใฟ่จ็ฎ (ในใฟใ)
/Users/bob/code/idris2-magical-utils/pkgs/Idris2Subcontract/ - ๅๅฎๅ
จใปใฌใฏใฟ่ฟฝๅ ๅ
idris2-yul ใณใผใ็ๆใใฐ๏ผไฟฎๆญฃๆธใฟ๏ผ
ไปฅไธใฎใใฐใฏ Codegen.idr ใงไฟฎๆญฃๆธใฟใpack install idris2-evm ใงๆๆฐ็ใไฝฟ็จใใใใจใ
ไฟฎๆญฃ1: switch default ๆฌ ่ฝ (2025-01-25 ไฟฎๆญฃ)
ๅ้ก: mdef = Nothing ใฎ case ๅผใง default ใใฉใณใใ็ๆใใใใใใใใใชใๅคใงๆชๅฎ็พฉๅไฝใ
ไฟฎๆญฃ: Compiler/EVM/Codegen.idr ใฎ AConCase/AConstCase ใงๅธธใซ default ใใฉใณใใ็ๆใ
-- ไฟฎๆญฃๅ: switch v; case 1: ... (case 0 ใชใ)
-- ไฟฎๆญฃๅพ: switch v; case 1: ... default {} (็ฉบใงใๅฟ
ใ็ๆ)
ไฟฎๆญฃ2: ใณใณในใใฉใฏใฟๅผๆฐ็ก่ฆ (2025-01-25 ไฟฎๆญฃ)
ๅ้ก: ACon ใงใณใณในใใฉใฏใฟๅผๆฐใใใผใใซๆธใ่พผใพใใใใฟใฐใฎใฟ่ฟใใใใ
ไฟฎๆญฃ: Compiler/EVM/Codegen.idr ใง allocWords + writeTaggedValue ใไฝฟ็จใ
-- ไฟฎๆญฃๅ: [x, y] โ ๅฎๆฐ 1 (ใฟใฐใฎใฟ)
-- ไฟฎๆญฃๅพ: [x, y] โ ใใผใใใคใณใฟ (tag + fields)
็พๅจใฎๅไฝ็ขบ่ชๆธใฟใใฟใผใณ
-- /= ใฏๆญฃๅธธๅไฝ๏ผcase 0/1 ไธกๆน็ๆ๏ผ
if x /= 0 then doA else doB -- OK
-- if-then-else ใฏๆญฃๅธธๅไฝ
if isValid then proceed else revert -- OK
-- ใชในใใณใณในใใฉใฏใฟใฏใใผใๅฒใๅฝใฆ
[x, y, z] -- OK (ใใ ใEVMใงใฏใฌในๅน็ๆชใใๅฏ่ฝใชใ้ฟใใ)
ใฌใฌใทใผๅ้ฟ็ญ๏ผไธ่ฆใ ใไบๆๆงใฎใใๆฎๅญ๏ผ
InstanceFactory ใณใผใใซใฏๅคใๅ้ฟ็ญใณใกใณใใๆฎใฃใฆใใใใไฟฎๆญฃๆธใฟใฎใใไธ่ฆ:
-- NOTE: Using == 0 instead of /= 0 due to idris2-yul codegen bug with /=
-- โ ใใฎใณใกใณใใฏ obsoleteใ/= ใฏๆญฃๅธธๅไฝใใ
initSingle : Integer -> IO ()
### ็ขบ่ชๆนๆณ
Yul ๅบๅใ็ขบ่ชใใฆ switch ๆใซ case 0 ใใใใ็ขบ่ช:
```bash
grep -A5 "switch" build/exec/idris2-ouf.yul
InstanceFactory/TheWorld ใใผใธใงใณ็ฎก็
InstanceFactory (Base Mainnet, Chain ID: 8453)
| Version | Contract Address | Block | Status | Notes |
|---|
| v8 | 0xb094b55924a790c4c9f86e16beb93d1261ed9891 | 41270838 | Active | idris2-yul Codegenไฟฎๆญฃๆธใฟ |
| v7 | 0x58abBd4b6dF53f3DDb335Ac45437a467c854Ad1d | 41270046 | Deprecated | ๆๅใใฐๅ้ฟ็ |
| v3 | 0xC7Efeca27d2e4D16e6354f2a23cd692210BbB19b | 41268931 | Deprecated | keccak256 event sigs |
| v2 | 0x03A460DC91A4606317C90679D4058c8250568eCd | 41267491 | Deprecated | Full dispatcher |
| v1 | 0xFB636FF84752A918F6962Fa40a56697Ed61b7459 | 41265830 | Deprecated | Initial test |
ใคใใณใใใใใฏ
EVENT_UPGRADE_PROPOSED = 0xf34129fccd3678779a7cae31d1189c93038ab2834690d15f21bc6020833084b6
TheWorld ใฎ OucIndexerAdapter.idr ใฎ upgradeProposedTopic ใจไธ่ดใใใใใจใ
E2E Testing
OpenClaw Lifecycle E2E (Integrated)
etherclaw e2e standalone
etherclaw e2e governed
etherclaw e2e evm
ๅฎ่ฃ
: pkgs/EtherClaw/src/EtherClaw/E2E/Runner.idr (standalone), GovernedRunner.idr (governed)
่ฉณ็ดฐ: docs/production/openclaw-e2e-lifecycle.md
Cross-Chain Test Package
/Users/bob/code/etherclaw/pkgs/E2eTests/
โโโ e2e-tests.ipkg
โโโ src/
โโโ E2eTests.idr # Main entry
โโโ E2eTests/Config.idr # Configuration
โโโ E2eTests/InstanceFactoryTheWorld.idr # InstanceFactoryโTheWorld test case
Shared Harness
E2E tests use idris2-e2e-harness from idris2-magical-utils:
/Users/bob/code/idris2-magical-utils/pkgs/Idris2E2eHarness/
Running Cross-Chain E2E Tests
cd /Users/bob/code/etherclaw/pkgs/E2eTests
idris2 --build e2e-tests.ipkg && ./build/exec/e2e-tests --generate > run-e2e.sh
chmod +x run-e2e.sh && ./run-e2e.sh
E2E Test: InstanceFactory โ TheWorld Event Detection
- Start Anvil (forking Base Mainnet at InstanceFactory deploy block)
- Start DFX local replica
- Deploy TheWorld canister locally
- Call
proposeUpgrade on InstanceFactory (via Anvil fork)
- Call
fetchEvmLogs on TheWorld
- Assert
eventsStored > 0
CI Integration
E2Eใในใใฏ lazy core ask ใฎ่ฒฌๅใงใฏใชใใAgent Skills ใจ Init Template ใฎ CI ๆ็ต้ข้ใจใใฆๅฎ่กใใใ
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Idris2
run: ...
- name: Setup Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Setup DFX
run: ...
- name: Run E2E Tests
run: |
cd pkgs/E2eTests
idris2 --build e2e-tests.ipkg
./build/exec/e2e-tests --generate | bash