From 6cc8c6ffb6af02806ea920cba13f29d1614e2913 Mon Sep 17 00:00:00 2001 From: Gowtham Chittemsetty <65408812+Gowtham118@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:25:30 +0530 Subject: [PATCH 01/11] feat: automate editing env files - adds a script which edits and writes env values into files - added basic input validation for env values --- package.json | 3 +- scripts/setup-envs.js | 155 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 scripts/setup-envs.js diff --git a/package.json b/package.json index 81cb9ab..6b3ab87 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "lint": "eslint .", "prettier:format": "prettier './**/*.{js,json,md,sol,ts,yml}' --write && yarn run lint --fix", "prettier:check": "prettier './**/*.{js,json,md,sol,ts,yml}' --check && yarn run lint", - "testAll": "tests/runAll.sh" + "testAll": "tests/runAll.sh", + "setup-envs": "node scripts/setup-envs.js" }, "devDependencies": { "@offchainlabs/eslint-config-typescript": "^0.2.1", diff --git a/scripts/setup-envs.js b/scripts/setup-envs.js new file mode 100644 index 0000000..09a41d4 --- /dev/null +++ b/scripts/setup-envs.js @@ -0,0 +1,155 @@ +#!/usr/bin/env node + +/* + * Environment setup script for Arbitrum Tutorials. + * Usage: + * yarn setup-envs + */ + +/* eslint-disable no-await-in-loop */ + +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); + +const VARS = ['PRIVATE_KEY', 'CHAIN_RPC', 'PARENT_CHAIN_RPC', 'L1_RPC']; + +function log(msg) { + console.log(msg); +} +function warn(msg) { + console.warn(msg); +} +function error(msg) { + console.error(msg); +} + +async function promptForValues() { + const values = {}; + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + const ask = (q) => new Promise((res) => rl.question(q, (ans) => res(ans.trim()))); + for (const v of VARS) { + const optional = v === 'L1_RPC'; + const existing = process.env[v] ? ` [default: ${process.env[v]}]` : ''; + const prompt = optional ? `${v} (optional)${existing}: ` : `${v}${existing}: `; + const ans = await ask(prompt); + if (ans) { + values[v] = ans; + } else if (process.env[v]) { + values[v] = process.env[v]; + } else if (!optional) { + error(`Required value missing: ${v}`); + process.exit(1); + } + } + rl.close(); + return values; +} + +function replaceOrAppend(contentLines, key, value) { + const prefix = key + '='; + let replaced = false; + for (let i = 0; i < contentLines.length; i++) { + const line = contentLines[i]; + if (line.startsWith(prefix)) { + contentLines[i] = `${key}="${value}"`; + replaced = true; + break; + } + } + if (!replaced) contentLines.push(`${key}="${value}"`); +} + +function processSampleFile(samplePath, envPath, values) { + const lines = fs.readFileSync(samplePath, 'utf8').split(/\r?\n/); + while (lines.length && lines[lines.length - 1].trim() === '') lines.pop(); + for (const v of VARS) { + if (values[v]) replaceOrAppend(lines, v, values[v]); + } + + const newContent = lines.join('\n') + '\n'; + fs.writeFileSync(envPath, newContent, 'utf8'); + if (samplePath !== envPath) { + fs.unlinkSync(samplePath); // remove sample after successful creation + } +} + +function processDirectory(dir, values, summary) { + const samplePath = path.join(dir, '.env-sample'); + const envPath = path.join(dir, '.env'); + const hasSample = fs.existsSync(samplePath); + const hasEnv = fs.existsSync(envPath); + + if (!hasSample && !hasEnv) return; + + try { + if (hasSample) { + processSampleFile(samplePath, envPath, values); + summary.updated.push(dir); + } else if (hasEnv) { + processSampleFile(envPath, envPath, values); + summary.updated.push(dir); + } + } catch (e) { + summary.errors.push({ dir, error: e.message }); + } +} + +function validate(values) { + const pk = values.PRIVATE_KEY; + if (!/^0x[a-fA-F0-9]{64}$/.test(pk)) { + throw new Error('PRIVATE_KEY must be 0x + 64 hex characters.'); + } + ['CHAIN_RPC', 'PARENT_CHAIN_RPC'].forEach((k) => { + if (!/^https?:\/\/\\S+$/i.test(values[k])) { + throw new Error(`${k} must be an http(s) URL.`); + } + }); + if (values.L1_RPC && !/^https?:\/\/\\S+$/i.test(values.L1_RPC)) { + throw new Error('L1_RPC must be an http(s) URL if provided.'); + } +} + +async function main() { + log('Arbitrum Tutorials environment setup starting...'); + const values = await promptForValues(); + + if (!values.PRIVATE_KEY || !values.CHAIN_RPC || !values.PARENT_CHAIN_RPC) { + error('PRIVATE_KEY, CHAIN_RPC, and PARENT_CHAIN_RPC are required.'); + process.exit(1); + } + + validate(values); + + const rootDir = path.resolve(__dirname, '..'); + const packagesDir = path.join(rootDir, 'packages'); + + const summary = { updated: [], errors: [] }; + + processDirectory(rootDir, values, summary); + + if (fs.existsSync(packagesDir)) { + const entries = fs.readdirSync(packagesDir); + for (const entry of entries) { + const fullPath = path.join(packagesDir, entry); + if (fs.statSync(fullPath).isDirectory()) { + processDirectory(fullPath, values, summary); + } + } + } + + log('Environment setup complete.'); + log(`Updated: ${summary.updated.length}`); + if (summary.errors.length) { + warn('Errors encountered:'); + for (const e of summary.errors) warn(` - ${e.dir}: ${e.error}`); + } + + log('\nExample: run a tutorial script after env creation:'); + log(' cd packages/greeter && npx hardhat run scripts/sendParentMessage.ts'); +} + +main().catch((e) => { + error(e.stack || e.message); + process.exit(1); +}); From f4f1adc02e5a668b38e98399140a0277c0c36938 Mon Sep 17 00:00:00 2001 From: Gowtham Chittemsetty <65408812+Gowtham118@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:26:54 +0530 Subject: [PATCH 02/11] chore: update about env script - add a note to prefer local node for testing --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index fb7fc53..991a4a7 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,14 @@ From root directory: yarn install ``` +## Environment Setup + +To simplify providing environment variables across all tutorials, a helper script automates creating `.env` files from each existing `.env-sample`. + +```bash +yarn setup-envs +``` + ## Testing 1. Start the nitro-testnode (you can find instructions [here](https://docs.arbitrum.io/run-arbitrum-node/run-local-full-chain-simulation)) with the following parameters: @@ -26,6 +34,10 @@ yarn install yarn run testAll ``` +### Notes on RPCs and Finality + +Using public testnet RPCs can be slow because many tutorials wait for transaction finality or multiple confirmations. Some tests may take 10–15 minutes to complete on testnets. For faster and more reliable execution, prefer running a local node and pointing your environment variables (`CHAIN_RPC`, `PARENT_CHAIN_RPC`, and `L1_RPC` when applicable) to local RPC endpoints. + ## What's included? #### :white_check_mark: Basics From bb0fa6f32e92170bfe16361db8d96284fd6382cf Mon Sep 17 00:00:00 2001 From: Gowtham Chittemsetty <65408812+Gowtham118@users.noreply.github.com> Date: Tue, 2 Dec 2025 16:15:18 +0530 Subject: [PATCH 03/11] fix regex and add --update flag --- README.md | 3 +++ scripts/setup-envs.js | 21 +++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 991a4a7..6c09098 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ To simplify providing environment variables across all tutorials, a helper scrip ```bash yarn setup-envs + +# To update existing .env files as well +yarn setup-envs --update ``` ## Testing diff --git a/scripts/setup-envs.js b/scripts/setup-envs.js index 09a41d4..d5cfc67 100644 --- a/scripts/setup-envs.js +++ b/scripts/setup-envs.js @@ -3,7 +3,8 @@ /* * Environment setup script for Arbitrum Tutorials. * Usage: - * yarn setup-envs + * yarn setup-envs # prompts for values (L1_RPC optional), does NOT overwrite existing .env + * yarn setup-envs --update # prompts and updates existing .env files as well */ /* eslint-disable no-await-in-loop */ @@ -13,6 +14,8 @@ const path = require('path'); const readline = require('readline'); const VARS = ['PRIVATE_KEY', 'CHAIN_RPC', 'PARENT_CHAIN_RPC', 'L1_RPC']; +const ARGS = new Set(process.argv.slice(2)); +const UPDATE_EXISTING = ARGS.has('--update'); function log(msg) { console.log(msg); @@ -87,8 +90,12 @@ function processDirectory(dir, values, summary) { processSampleFile(samplePath, envPath, values); summary.updated.push(dir); } else if (hasEnv) { - processSampleFile(envPath, envPath, values); - summary.updated.push(dir); + if (UPDATE_EXISTING) { + processSampleFile(envPath, envPath, values); + summary.updated.push(dir); + } else { + summary.skipped.push(dir); + } } } catch (e) { summary.errors.push({ dir, error: e.message }); @@ -101,11 +108,11 @@ function validate(values) { throw new Error('PRIVATE_KEY must be 0x + 64 hex characters.'); } ['CHAIN_RPC', 'PARENT_CHAIN_RPC'].forEach((k) => { - if (!/^https?:\/\/\\S+$/i.test(values[k])) { + if (!/^https?:\/\/\S+$/i.test(values[k])) { throw new Error(`${k} must be an http(s) URL.`); } }); - if (values.L1_RPC && !/^https?:\/\/\\S+$/i.test(values.L1_RPC)) { + if (values.L1_RPC && !/^https?:\/\/\S+$/i.test(values.L1_RPC)) { throw new Error('L1_RPC must be an http(s) URL if provided.'); } } @@ -124,7 +131,7 @@ async function main() { const rootDir = path.resolve(__dirname, '..'); const packagesDir = path.join(rootDir, 'packages'); - const summary = { updated: [], errors: [] }; + const summary = { updated: [], skipped: [], errors: [] }; processDirectory(rootDir, values, summary); @@ -140,6 +147,8 @@ async function main() { log('Environment setup complete.'); log(`Updated: ${summary.updated.length}`); + if (summary.skipped.length) + log(`Skipped (existing .env, no --update): ${summary.skipped.length}`); if (summary.errors.length) { warn('Errors encountered:'); for (const e of summary.errors) warn(` - ${e.dir}: ${e.error}`); From f9e7ca361193853a0ec7496e0502fa364b4ebbbf Mon Sep 17 00:00:00 2001 From: Gowtham Chittemsetty <65408812+Gowtham118@users.noreply.github.com> Date: Tue, 2 Dec 2025 20:14:45 +0530 Subject: [PATCH 04/11] fix: don't delete .env-sample --- scripts/setup-envs.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/setup-envs.js b/scripts/setup-envs.js index d5cfc67..cf8b7d1 100644 --- a/scripts/setup-envs.js +++ b/scripts/setup-envs.js @@ -72,9 +72,6 @@ function processSampleFile(samplePath, envPath, values) { const newContent = lines.join('\n') + '\n'; fs.writeFileSync(envPath, newContent, 'utf8'); - if (samplePath !== envPath) { - fs.unlinkSync(samplePath); // remove sample after successful creation - } } function processDirectory(dir, values, summary) { From 19cab9b2c0803414cc2dc4652131ba0607f570a4 Mon Sep 17 00:00:00 2001 From: Gowtham Chittemsetty <65408812+Gowtham118@users.noreply.github.com> Date: Fri, 5 Dec 2025 22:24:29 +0530 Subject: [PATCH 05/11] fix: make regex inclusive for local testing --- scripts/setup-envs.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/setup-envs.js b/scripts/setup-envs.js index cf8b7d1..44e0c31 100644 --- a/scripts/setup-envs.js +++ b/scripts/setup-envs.js @@ -100,16 +100,22 @@ function processDirectory(dir, values, summary) { } function validate(values) { - const pk = values.PRIVATE_KEY; - if (!/^0x[a-fA-F0-9]{64}$/.test(pk)) { - throw new Error('PRIVATE_KEY must be 0x + 64 hex characters.'); + const pk = values.PRIVATE_KEY && String(values.PRIVATE_KEY).trim(); + if (!/^(?:0x)?[a-fA-F0-9]{64}$/.test(pk)) { + throw new Error('PRIVATE_KEY must be 64 hex characters, with or without a leading 0x.'); + } + // Normalize: ensure stored value has 0x prefix + if (!pk.startsWith('0x')) { + values.PRIVATE_KEY = `0x${pk}`; + } else { + values.PRIVATE_KEY = pk; } ['CHAIN_RPC', 'PARENT_CHAIN_RPC'].forEach((k) => { - if (!/^https?:\/\/\S+$/i.test(values[k])) { + if (!/^https?:\/\/\S+$/i.test(String(values[k] || ''))) { throw new Error(`${k} must be an http(s) URL.`); } }); - if (values.L1_RPC && !/^https?:\/\/\S+$/i.test(values.L1_RPC)) { + if (values.L1_RPC && !/^https?:\/\/\S+$/i.test(String(values.L1_RPC))) { throw new Error('L1_RPC must be an http(s) URL if provided.'); } } From dfd2b87c3b271627e11fd15849aee523f492beaa Mon Sep 17 00:00:00 2001 From: Jason-Wanxt Date: Wed, 17 Dec 2025 17:47:13 +0800 Subject: [PATCH 06/11] reorg if case --- scripts/setup-envs.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/setup-envs.js b/scripts/setup-envs.js index 44e0c31..f56b8b1 100644 --- a/scripts/setup-envs.js +++ b/scripts/setup-envs.js @@ -81,18 +81,17 @@ function processDirectory(dir, values, summary) { const hasEnv = fs.existsSync(envPath); if (!hasSample && !hasEnv) return; - try { - if (hasSample) { - processSampleFile(samplePath, envPath, values); - summary.updated.push(dir); - } else if (hasEnv) { + if (hasEnv) { if (UPDATE_EXISTING) { processSampleFile(envPath, envPath, values); summary.updated.push(dir); } else { summary.skipped.push(dir); } + } else if (hasSample) { + processSampleFile(samplePath, envPath, values); + summary.updated.push(dir); } } catch (e) { summary.errors.push({ dir, error: e.message }); From 2d542ad474ebe72aca2fd428007116164905f6fb Mon Sep 17 00:00:00 2001 From: Jason-Wanxt Date: Wed, 17 Dec 2025 18:17:01 +0800 Subject: [PATCH 07/11] update custom network address --- customNetwork.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/customNetwork.json b/customNetwork.json index ae17823..4c5e6c5 100644 --- a/customNetwork.json +++ b/customNetwork.json @@ -3,11 +3,11 @@ "chainID": 412346, "confirmPeriodBlocks": 20, "ethBridge": { - "bridge": "0x5eCF728ffC5C5E802091875f96281B5aeECf6C49", - "inbox": "0x9f8c1c641336A371031499e3c362e40d58d0f254", - "outbox": "0x50143333b44Ea46255BEb67255C9Afd35551072F", - "rollup": "0xe5Ab92C74CD297F0a1F2914cE37204FC5Bc4e82D", - "sequencerInbox": "0x18d19C5d3E685f5be5b9C86E097f0E439285D216" + "bridge": "0xd21d0cA137A64165E3C3702a37C5D42E6a5CdACC", + "inbox": "0xF52c6E85A9E6287942fD913DfCc269EB571816a6", + "outbox": "0xbf2265Cd6Fd551e5EAA862Fd81BA317D585eE693", + "rollup": "0x1b172F52EC0EFFeeC7946903F20410B5d6571AA0", + "sequencerInbox": "0x7F6958d308593dC0BdEa73c4bcD742965E1C79af" }, "explorerUrl": "", "isArbitrum": true, @@ -19,17 +19,17 @@ "nitroGenesisL1Block": 0, "depositTimeout": 900000, "tokenBridge": { - "parentGatewayRouter": "0x093AAa96CD4387A68FC0e24C60140938Dc812549", + "parentGatewayRouter": "0x145899e2EF2A3f460Cfb2F7897615F5758000F66", "childGatewayRouter": "0x32656396981868E925280FB772b3f806892cf4bF", - "parentErc20Gateway": "0x00D9fE1a2B67B8151aEdE8855c95E58D73FB4245", + "parentErc20Gateway": "0x4D9Aa26aCee73293ba84fB1FcA8100006239860E", "childErc20Gateway": "0x7424e3DAAAAcd867c85ceB75c1E00119F2ee5eb7", - "parentCustomGateway": "0x8407E6180dC009D20D26D4BABB4790C1d4E6D2aA", + "parentCustomGateway": "0xC9ba2864E0E5dC3B78a539fA5e50b4508595A236", "childCustomGateway": "0x0B35cfE62314C3852A0942b5830c728353BD654F", - "parentWethGateway": "0xB8F48Ba39fCfB44d70F6008fe1bf4F3E744044AF", + "parentWethGateway": "0xCb24A87c3Dfc5b6DC8f1Da8D62D731fA1109A32C", "childWethGateway": "0x67aE8014BD1A0c1Ed747715d22b3b3a188aC324B", "parentWeth": "0x7E32b54800705876d3b5cFbc7d9c226a211F7C1a", "childWeth": "0xA1abD387192e3bb4e84D3109181F9f005aBaF5CA", - "parentProxyAdmin": "0x2A1f38c9097e7883570e0b02BFBE6869Cc25d8a3", + "parentProxyAdmin": "0x275FC51309e5928Cb085b463ADEF5cbD45c76b62", "childProxyAdmin": "0x9F95547ABB0FfC92b4E37b3124d1e8613d5aB74A", "parentMultiCall": "0x49117fC32930E324F2E9A7BeA588FFb26008b8eC", "childMultiCall": "0x6B1E93aE298B64e8f5b9f43B65Dd8F1eaA6DD4c3" @@ -41,11 +41,11 @@ "chainID": 333333, "confirmPeriodBlocks": 20, "ethBridge": { - "bridge": "0xA584795e24628D9c067A6480b033C9E96281fcA3", - "inbox": "0xDcA690902d3154886Ec259308258D10EA5450996", - "outbox": "0xda243bD61B011024FC923164db75Dde198AC6175", - "rollup": "0x47b238E195b638b8972Cb3649e5d6775c279245d", - "sequencerInbox": "0x16c54EE2015CD824415c2077F4103f444E00A8cb" + "bridge": "0xFbd865f29BadF3fdcf14b7e742F78c8DdF7fa4C9", + "inbox": "0x7CB5a1892d31936a13CeE3a4A8183Ed40300e87c", + "outbox": "0x3dAEcE87269310A2f055920ef5C84679ca50F218", + "rollup": "0x9129f7A7f4D5A87F971Fe15C554AD1CF22ceb4EF", + "sequencerInbox": "0x99c807B58c00DE2b068e521AAC227A1e8ADe1fa9" }, "explorerUrl": "", "isArbitrum": true, @@ -57,17 +57,17 @@ "nitroGenesisL1Block": 0, "depositTimeout": 900000, "tokenBridge": { - "parentGatewayRouter": "0xfE03DBdf7A126994dBd749631D7fbaB58C618c58", + "parentGatewayRouter": "0x9a6E8E638451781B47b8632a11E56020270A335B", "childGatewayRouter": "0x8B6BC759226f8Fe687c8aD8Cc0DbF85E095e9297", - "parentErc20Gateway": "0x6B0805Fc6e275ef66a0901D0CE68805631E271e5", + "parentErc20Gateway": "0x7A6b9Ca6e4f87E6a641c1AbD7671aD42bF538977", "childErc20Gateway": "0xaa7d51aFFEeB32d99b1CB2fd6d81D7adA4a896e8", - "parentCustomGateway": "0xA191D519260A06b32f8D04c84b9F457B8Caa0514", + "parentCustomGateway": "0xDCa39C8Fbd686cb8838106Ed94C0B9e9802697d6", "childCustomGateway": "0xD4816AeF8f85A3C1E01Cd071a81daD4fa941625f", - "parentWethGateway": "0x77603b0ea6a797C74Fa9ef11b5BdE04A4E03D550", + "parentWethGateway": "0x3bF927fc03d13bD2199b02a029ABb490D13706b3", "childWethGateway": "0xA6AB233B3c7bfd0399834897b5073974A3D467e2", "parentWeth": "0xA1abD387192e3bb4e84D3109181F9f005aBaF5CA", "childWeth": "0x582a8dBc77f665dF2c49Ce0a138978e9267dd968", - "parentProxyAdmin": "0x1A61102c26ad3f64bA715B444C93388491fd8E68", + "parentProxyAdmin": "0x74513d47D265527f0647eD5B90072578c609B378", "childProxyAdmin": "0x36C56eC2CF3a3f53db9F01d0A5Ae84b36fb0A1e2", "parentMultiCall": "0x20a3627Dcc53756E38aE3F92717DE9B23617b422", "childMultiCall": "0x052B15c8Ff0544287AE689C4F2FC53A3905d7Db3" From 599a3d1dad839c8d023164dabf96d25e04455195 Mon Sep 17 00:00:00 2001 From: Gowtham Chittemsetty <65408812+Gowtham118@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:20:43 +0530 Subject: [PATCH 08/11] fix: TransferTo optional env --- scripts/setup-envs.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/setup-envs.js b/scripts/setup-envs.js index 44e0c31..68fe11b 100644 --- a/scripts/setup-envs.js +++ b/scripts/setup-envs.js @@ -13,7 +13,7 @@ const fs = require('fs'); const path = require('path'); const readline = require('readline'); -const VARS = ['PRIVATE_KEY', 'CHAIN_RPC', 'PARENT_CHAIN_RPC', 'L1_RPC']; +const VARS = ['PRIVATE_KEY', 'CHAIN_RPC', 'PARENT_CHAIN_RPC', 'L1_RPC', 'TransferTo']; const ARGS = new Set(process.argv.slice(2)); const UPDATE_EXISTING = ARGS.has('--update'); @@ -32,7 +32,7 @@ async function promptForValues() { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const ask = (q) => new Promise((res) => rl.question(q, (ans) => res(ans.trim()))); for (const v of VARS) { - const optional = v === 'L1_RPC'; + const optional = v === 'L1_RPC' || v === 'TransferTo'; const existing = process.env[v] ? ` [default: ${process.env[v]}]` : ''; const prompt = optional ? `${v} (optional)${existing}: ` : `${v}${existing}: `; const ans = await ask(prompt); @@ -118,6 +118,13 @@ function validate(values) { if (values.L1_RPC && !/^https?:\/\/\S+$/i.test(String(values.L1_RPC))) { throw new Error('L1_RPC must be an http(s) URL if provided.'); } + if (values.TransferTo) { + const addr = String(values.TransferTo).trim(); + if (!/^0x[a-fA-F0-9]{40}$/.test(addr)) { + throw new Error('TransferTo must be a valid 0x-prefixed Ethereum address.'); + } + values.TransferTo = addr; + } } async function main() { From f127cc1ccf3d6ae49c9b9421b2cdbd7aa65073ff Mon Sep 17 00:00:00 2001 From: Gowtham Chittemsetty <65408812+Gowtham118@users.noreply.github.com> Date: Thu, 18 Dec 2025 19:41:48 +0530 Subject: [PATCH 09/11] update: block verification in parent chain assertion - Replaces `NodeCreated` with `AssertionCreated` - Updates version to use the latest ABI --- .../README.md | 6 +- .../package.json | 2 +- .../scripts/exec.ts | 79 ++++++------------- yarn.lock | 11 +++ 4 files changed, 39 insertions(+), 59 deletions(-) diff --git a/packages/block-verification-in-parent-chain-assertion/README.md b/packages/block-verification-in-parent-chain-assertion/README.md index b8d6831..482ce0a 100644 --- a/packages/block-verification-in-parent-chain-assertion/README.md +++ b/packages/block-verification-in-parent-chain-assertion/README.md @@ -1,12 +1,12 @@ # Block verification in an assertion posted on the parent chain -This tutorial shows how to verify whether a block of a chain has been processed as part of an RBlock assertion on its parent chain. +This tutorial shows how to verify whether a block of a chain has been processed as part of an assertion on its parent chain. -It uses the `Rollup` contract to find the latest confirmed (or created if configured in the script) RBlock/node, find the event that created it, and get the latest processed block hash of the child chain that's part of the assertion of that RBlock/node. +It uses the `Rollup` contract to find the latest confirmed assertion hash, find the `AssertionCreated` event that created it, and get the latest processed block hash of the child chain that's part of that assertion's `afterState`. Then it checks whether the block number passed as argument was created before the latest block hash of the child chain processed. -See [./exec.js](./scripts/exec.js) for inline explanations. +See [./exec.ts](./scripts/exec.ts) for inline explanations. ## Set environment variables diff --git a/packages/block-verification-in-parent-chain-assertion/package.json b/packages/block-verification-in-parent-chain-assertion/package.json index b18f56b..95fed55 100644 --- a/packages/block-verification-in-parent-chain-assertion/package.json +++ b/packages/block-verification-in-parent-chain-assertion/package.json @@ -6,7 +6,7 @@ "exec": "ts-node scripts/exec.ts" }, "dependencies": { - "@arbitrum/sdk": "^v4.0.1", + "@arbitrum/sdk": "^v4.0.4", "ts-node": "^10.9.2", "typescript": "^4.9.5" } diff --git a/packages/block-verification-in-parent-chain-assertion/scripts/exec.ts b/packages/block-verification-in-parent-chain-assertion/scripts/exec.ts index 8ba9734..eb00100 100644 --- a/packages/block-verification-in-parent-chain-assertion/scripts/exec.ts +++ b/packages/block-verification-in-parent-chain-assertion/scripts/exec.ts @@ -1,6 +1,6 @@ import { providers, Contract } from 'ethers'; import { getArbitrumNetwork } from '@arbitrum/sdk'; -import { RollupCore__factory } from '@arbitrum/sdk/dist/lib/abi/factories/RollupCore__factory'; +import { BoldRollupUserLogic__factory } from '@arbitrum/sdk/dist/lib/abi-bold/factories/BoldRollupUserLogic__factory'; import { arbLog, requireEnvVariables, addCustomNetworkFromFile } from 'arb-shared-dependencies'; require('dotenv').config(); requireEnvVariables(['CHAIN_RPC', 'PARENT_CHAIN_RPC']); @@ -11,93 +11,64 @@ requireEnvVariables(['CHAIN_RPC', 'PARENT_CHAIN_RPC']); const parentChainProvider = new providers.JsonRpcProvider(process.env.PARENT_CHAIN_RPC); const childChainProvider = new providers.JsonRpcProvider(process.env.CHAIN_RPC); -/** - * Use the latest node created instead of the last confirmed one - */ -const useCreatedNodeInsteadOfConfirmed = false; - const main = async (childChainBlockNumberToVerify: number) => { await arbLog( 'Find whether a block of the child chain has been processed as part of an RBlock on the parent chain', ); - /** - * Add the custom network configuration to the SDK if present - */ addCustomNetworkFromFile(); - /** - * Use childChainNetwork to find the Rollup contract's address and instantiate a contract handler - */ const childChainNetwork = await getArbitrumNetwork(childChainProvider); const rollupAddress = childChainNetwork.ethBridge.rollup; - const rollup = new Contract(rollupAddress, RollupCore__factory.abi, parentChainProvider); + const rollup = new Contract(rollupAddress, BoldRollupUserLogic__factory.abi, parentChainProvider); console.log(`Rollup contract found at address ${rollup.address}`); - /** - * Get the latest node created or confirmed - */ - const nodeId = useCreatedNodeInsteadOfConfirmed - ? await rollup.latestNodeCreated() - : await rollup.latestConfirmed(); - console.log( - `Latest ${useCreatedNodeInsteadOfConfirmed ? 'created' : 'confirmed'} Rblock/node: ${nodeId}`, - ); + const assertionHash: string = await rollup.latestConfirmed(); + console.log(`Latest confirmed assertion hash: ${assertionHash}`); - /** - * Find the NodeCreated event - */ - const nodeCreatedEventFilter = rollup.filters.NodeCreated(nodeId); - const nodeCreatedEvents = await rollup.queryFilter(nodeCreatedEventFilter); - if (!nodeCreatedEvents) { - throw new Error(`INTERNAL ERROR: NodeCreated events not found for Rblock/node: ${nodeId}`); + const assertionCreatedEventFilter = (rollup as any).filters.AssertionCreated(assertionHash); + const assertionCreatedEvents = await rollup.queryFilter(assertionCreatedEventFilter); + if (!assertionCreatedEvents || assertionCreatedEvents.length === 0) { + throw new Error( + `INTERNAL ERROR: AssertionCreated events not found for assertion: ${assertionHash}`, + ); } - const nodeCreatedEvent = nodeCreatedEvents[0]; - console.log(`NodeCreated event found in transaction ${nodeCreatedEvent.transactionHash}`); + const assertionCreatedEvent = assertionCreatedEvents[0]; + console.log( + `AssertionCreated event found in transaction ${assertionCreatedEvent.transactionHash}`, + ); - /** - * Finding the assertion within the NodeCreated event, and getting the afterState - */ - if (!nodeCreatedEvent.args) { + if (!assertionCreatedEvent.args) { throw new Error( - `INTERNAL ERROR: NodeCreated event does not have an assertion for Rblock/node: ${nodeId}`, + `INTERNAL ERROR: AssertionCreated event does not have an assertion for hash: ${assertionHash}`, ); } - const assertion = nodeCreatedEvent.args.assertion; + const assertion = (assertionCreatedEvent as any).args.assertion; const afterState = assertion.afterState; - /** - * Latest child chain's block hash processed is in the first element of the bytes32Vals property in the globalState - */ + // Latest child chain's block hash processed is in the first element of the bytes32Vals property in the globalState const lastChildChainBlockHash = afterState.globalState.bytes32Vals[0]; console.log( `Last block hash of the child chain processed in this Rblock/node: ${lastChildChainBlockHash}`, ); - /** - * Getting the block number from that block hash - */ + // Getting the block number from that block hash + const lastChildChainBlock = await childChainProvider.getBlock(lastChildChainBlockHash); const lastChildChainBlockNumber = lastChildChainBlock.number; console.log( `Last block number of the child chain processed in this Rblock/node: ${lastChildChainBlockNumber}`, ); - /** - * Final verification - */ + // Final verification console.log(`************`); if (lastChildChainBlockNumber > childChainBlockNumberToVerify) { console.log( - `${childChainBlockNumberToVerify} has been processed as part of the latest ${ - useCreatedNodeInsteadOfConfirmed ? 'created' : 'confirmed' - } RBlock/node`, + `${childChainBlockNumberToVerify} has been processed as part of the latest confirmed assertion`, ); } else { console.log( - `${childChainBlockNumberToVerify} has NOT been processed as part of the latest ${ - useCreatedNodeInsteadOfConfirmed ? 'created' : 'confirmed' - } RBlock/node`, + `${childChainBlockNumberToVerify} has NOT been processed as part of the latest confirmed assertion`, ); } console.log(`************`); @@ -106,9 +77,7 @@ const main = async (childChainBlockNumberToVerify: number) => { // Getting the transaction hash from the command arguments if (process.argv.length < 3) { console.log( - `Missing block number of the child chain to verify whether it has been processed in the latest ${ - useCreatedNodeInsteadOfConfirmed ? 'created' : 'confirmed' - } RBlock/node`, + 'Missing block number of the child chain to verify whether it has been processed in the latest confirmed assertion', ); console.log(`Usage: yarn run exec `); process.exit(1); diff --git a/yarn.lock b/yarn.lock index f861af2..2039192 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,6 +34,17 @@ async-mutex "^0.4.0" ethers "^5.1.0" +"@arbitrum/sdk@^v4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@arbitrum/sdk/-/sdk-4.0.4.tgz#32f3cfa75d2b3f9ab0be01eb807112e0001f428d" + integrity sha512-GscwlkHYmPzRKs9huDHntbqx1xMRhTraTUvTC9exu+prjndKxHe9ZORuIcqmtEqwLwma/l8nqxI+k+pEEdIO6Q== + dependencies: + "@ethersproject/address" "^5.0.8" + "@ethersproject/bignumber" "^5.1.1" + "@ethersproject/bytes" "^5.0.8" + async-mutex "^0.4.0" + ethers "^5.1.0" + "@arbitrum/token-bridge-contracts@^1.2.3": version "1.2.3" resolved "https://registry.yarnpkg.com/@arbitrum/token-bridge-contracts/-/token-bridge-contracts-1.2.3.tgz#b08b22b5dcac255149a10bd8b66b0b55be7f3329" From 471b382e4af0798d06399c11d7834d546e6b585b Mon Sep 17 00:00:00 2001 From: Jason-Wanxt Date: Fri, 19 Dec 2025 01:55:38 +0800 Subject: [PATCH 10/11] remove sdk version specific --- .../block-verification-in-parent-chain-assertion/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-verification-in-parent-chain-assertion/package.json b/packages/block-verification-in-parent-chain-assertion/package.json index 95fed55..3e25f22 100644 --- a/packages/block-verification-in-parent-chain-assertion/package.json +++ b/packages/block-verification-in-parent-chain-assertion/package.json @@ -6,7 +6,6 @@ "exec": "ts-node scripts/exec.ts" }, "dependencies": { - "@arbitrum/sdk": "^v4.0.4", "ts-node": "^10.9.2", "typescript": "^4.9.5" } From 5ba44cb0d80000f32055fb9bf49d82494386132e Mon Sep 17 00:00:00 2001 From: Jason-Wanxt Date: Fri, 19 Dec 2025 02:33:56 +0800 Subject: [PATCH 11/11] use custom func insert --- packages/arb-shared-dependencies/index.js | 9 ++++++--- .../package.json | 1 + .../scripts/exec.ts | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/arb-shared-dependencies/index.js b/packages/arb-shared-dependencies/index.js index 9e33ae3..ee9dba9 100644 --- a/packages/arb-shared-dependencies/index.js +++ b/packages/arb-shared-dependencies/index.js @@ -53,18 +53,21 @@ const requireEnvVariables = (envVars) => { console.log('Environmental variables properly set 👍'); }; -const addCustomNetworkFromFile = () => { +const addCustomNetworkFromFile = (registerFn) => { const pathToCustomNetworkFile = path.join(__dirname, '..', '..', 'customNetwork.json'); if (!fs.existsSync(pathToCustomNetworkFile)) { return; } + // Use the provided register function, or fall back to the local one + const register = registerFn || registerCustomArbitrumNetwork; + const customNetworkFileContents = fs.readFileSync(pathToCustomNetworkFile, 'utf8'); const customNetworkInformation = JSON.parse(customNetworkFileContents); if (customNetworkInformation instanceof Array) { - customNetworkInformation.map((customNetwork) => registerCustomArbitrumNetwork(customNetwork)); + customNetworkInformation.map((customNetwork) => register(customNetwork)); } else { - registerCustomArbitrumNetwork(customNetworkInformation); + register(customNetworkInformation); } }; diff --git a/packages/block-verification-in-parent-chain-assertion/package.json b/packages/block-verification-in-parent-chain-assertion/package.json index 3e25f22..b18f56b 100644 --- a/packages/block-verification-in-parent-chain-assertion/package.json +++ b/packages/block-verification-in-parent-chain-assertion/package.json @@ -6,6 +6,7 @@ "exec": "ts-node scripts/exec.ts" }, "dependencies": { + "@arbitrum/sdk": "^v4.0.1", "ts-node": "^10.9.2", "typescript": "^4.9.5" } diff --git a/packages/block-verification-in-parent-chain-assertion/scripts/exec.ts b/packages/block-verification-in-parent-chain-assertion/scripts/exec.ts index eb00100..110ca6b 100644 --- a/packages/block-verification-in-parent-chain-assertion/scripts/exec.ts +++ b/packages/block-verification-in-parent-chain-assertion/scripts/exec.ts @@ -1,5 +1,5 @@ import { providers, Contract } from 'ethers'; -import { getArbitrumNetwork } from '@arbitrum/sdk'; +import { getArbitrumNetwork, registerCustomArbitrumNetwork } from '@arbitrum/sdk'; import { BoldRollupUserLogic__factory } from '@arbitrum/sdk/dist/lib/abi-bold/factories/BoldRollupUserLogic__factory'; import { arbLog, requireEnvVariables, addCustomNetworkFromFile } from 'arb-shared-dependencies'; require('dotenv').config(); @@ -16,7 +16,7 @@ const main = async (childChainBlockNumberToVerify: number) => { 'Find whether a block of the child chain has been processed as part of an RBlock on the parent chain', ); - addCustomNetworkFromFile(); + addCustomNetworkFromFile(registerCustomArbitrumNetwork); const childChainNetwork = await getArbitrumNetwork(childChainProvider); const rollupAddress = childChainNetwork.ethBridge.rollup;