├── .editorconfig ├── .env.example ├── .github ├── dependabot.yml └── workflows │ ├── build-test.yml │ └── check-pr-title.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── LICENSE ├── README.md ├── audit-ci.jsonc ├── examples ├── .gitignore ├── create-rollup-custom-fee-token │ ├── .env.example │ ├── README.md │ ├── index.ts │ ├── low_level.ts │ ├── package.json │ └── tsconfig.json ├── create-rollup-eth │ ├── .env.example │ ├── README.md │ ├── index.ts │ ├── low_level.ts │ ├── package.json │ └── tsconfig.json ├── create-token-bridge-custom-fee-token │ ├── .env.example │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── create-token-bridge-eth │ ├── .env.example │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── prepare-node-config │ ├── .env.example │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── set-new-validators │ ├── .env.example │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── set-valid-keyset │ ├── .env.example │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── setup-aep-fee-router │ ├── .env.example │ ├── README.md │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── setup-fast-withdrawal │ ├── .env.example │ ├── README.md │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── setup-fee-distributor-contract │ ├── .env.example │ ├── README.md │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── tsconfig.base.json └── upgrade-executor-add-account │ ├── .env.example │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── package.json ├── patches └── @wagmi+cli+1.5.2.patch ├── prettier.config.cjs ├── src ├── __snapshots__ │ ├── createRollupPrepareConfig.unit.test.ts.snap │ ├── createRollupPrepareDeploymentParamsConfig.unit.test.ts.snap │ ├── createRollupPrepareTransaction.unit.test.ts.snap │ ├── createRollupPrepareTransactionReceipt.unit.test.ts.snap │ ├── getDefaultChallengeGracePeriodBlocks.unit.test.ts.snap │ ├── getDefaultConfirmPeriodBlocks.unit.test.ts.snap │ ├── getDefaultMinimumAssertionPeriod.unit.test.ts.snap │ ├── getDefaultSequencerInboxMaxTimeVariation.unit.test.ts.snap │ ├── getDefaultValidatorAfkBlocks.unit.test.ts.snap │ └── prepareChainConfig.unit.test.ts.snap ├── actions │ ├── buildInvalidateKeysetHash.ts │ ├── buildSetIsBatchPoster.ts │ ├── buildSetMaxTimeVariation.ts │ ├── buildSetValidKeyset.ts │ ├── getMaxTimeVariation.ts │ ├── index.ts │ ├── isBatchPoster.ts │ ├── isValidKeysetHash.ts │ └── sequencerInbox.integration.test.ts ├── arbAggregatorPrepareTransactionRequest.ts ├── arbAggregatorReadContract.ts ├── arbGasInfoReadContract.ts ├── arbOwnerPrepareTransactionRequest.ts ├── arbOwnerReadContract.ts ├── chains.ts ├── chains.unit.test.ts ├── constants.ts ├── contracts │ ├── ArbAggregator.ts │ ├── ArbGasInfo.ts │ ├── ArbOwner.ts │ ├── ArbOwnerPublic.ts │ ├── ERC20.ts │ ├── GnosisSafeL2.ts │ ├── Rollup │ │ ├── index.ts │ │ ├── v1.1.ts │ │ ├── v2.1.ts │ │ └── v3.1.ts │ ├── RollupCreator │ │ ├── index.ts │ │ ├── v1.1.ts │ │ ├── v2.1.ts │ │ └── v3.1.ts │ ├── SequencerInbox │ │ ├── index.ts │ │ ├── v1.1.ts │ │ ├── v2.1.ts │ │ └── v3.1.ts │ ├── TokenBridgeCreator │ │ ├── index.ts │ │ └── v1.2.ts │ ├── UpgradeExecutor.ts │ └── WETH.ts ├── createRollup.integration.test.ts ├── createRollup.ts ├── createRollupDefaults.ts ├── createRollupEnoughCustomFeeTokenAllowance.ts ├── createRollupFetchCoreContracts.ts ├── createRollupFetchTransactionHash.ts ├── createRollupGetCallValue.ts ├── createRollupGetMaxDataSize.ts ├── createRollupGetRetryablesFees.ts ├── createRollupGetRetryablesFees.unit.test.ts ├── createRollupPrepareCustomFeeTokenApprovalTransactionRequest.ts ├── createRollupPrepareDeploymentParamsConfig.ts ├── createRollupPrepareDeploymentParamsConfig.unit.test.ts ├── createRollupPrepareDeploymentParamsConfigDefaults.ts ├── createRollupPrepareTransaction.ts ├── createRollupPrepareTransaction.unit.test.ts ├── createRollupPrepareTransactionReceipt.ts ├── createRollupPrepareTransactionReceipt.unit.test.ts ├── createRollupPrepareTransactionRequest.ts ├── createRollupPrepareTransactionRequest.unit.test.ts ├── createSafePrepareTransactionReceipt.ts ├── createSafePrepareTransactionRequest.ts ├── createTokenBridge-ethers.ts ├── createTokenBridge-testHelpers.ts ├── createTokenBridge.integration.test.ts ├── createTokenBridge.ts ├── createTokenBridgeEnoughCustomFeeTokenAllowance.ts ├── createTokenBridgeFetchTokenBridgeContracts.ts ├── createTokenBridgePrepareCustomFeeTokenApprovalTransactionRequest.ts ├── createTokenBridgePrepareSetWethGatewayTransactionReceipt.ts ├── createTokenBridgePrepareSetWethGatewayTransactionRequest.ts ├── createTokenBridgePrepareTransactionReceipt.ts ├── createTokenBridgePrepareTransactionRequest.ts ├── decorators │ ├── arbAggregatorActions.integration.test.ts │ ├── arbAggregatorActions.ts │ ├── arbGasInfoPublicActions.ts │ ├── arbOwnerPrepareTransactionRequest.unit.test.ts │ ├── arbOwnerPublicActions.integration.test.ts │ ├── arbOwnerPublicActions.ts │ ├── arbOwnerPublicActionsUpgradeExecutor.integration.test.ts │ ├── rollupAdminLogicActions.unit.test.ts │ ├── rollupAdminLogicPublicActions.integration.test.ts │ ├── rollupAdminLogicPublicActions.ts │ ├── sequencerInboxActions.integration.test.ts │ ├── sequencerInboxActions.ts │ └── sequencerInboxActions.unit.test.ts ├── ethers-compat │ ├── ethersTransactionReceiptToViemTransactionReceipt.ts │ ├── publicClientToProvider.ts │ ├── publicClientToProvider.unit.test.ts │ └── viemTransactionReceiptToEthersTransactionReceipt.ts ├── feeRouter.integration.test.ts ├── feeRouterDeployChildToParentRewardRouter.ts ├── feeRouterDeployRewardDistributor.ts ├── getBatchPosters.integration.test.ts ├── getBatchPosters.ts ├── getBatchPosters.unit.test.ts ├── getDefaultChallengeGracePeriodBlocks.ts ├── getDefaultChallengeGracePeriodBlocks.unit.test.ts ├── getDefaultConfirmPeriodBlocks.ts ├── getDefaultConfirmPeriodBlocks.unit.test.ts ├── getDefaultMinimumAssertionPeriod.ts ├── getDefaultMinimumAssertionPeriod.unit.test.ts ├── getDefaultSequencerInboxMaxTimeVariation.ts ├── getDefaultSequencerInboxMaxTimeVariation.unit.test.ts ├── getDefaultValidatorAfkBlocks.ts ├── getDefaultValidatorAfkBlocks.unit.test.ts ├── getKeysets.integration.test.ts ├── getKeysets.ts ├── getKeysets.unit.test.ts ├── getParentChainBlockTime.ts ├── getValidators.integration.test.ts ├── getValidators.ts ├── getValidators.unit.test.ts ├── index.ts ├── isAnyTrust.ts ├── isAnyTrust.unit.test.ts ├── isTokenBridgeDeployed.ts ├── package.json ├── parentChainIsArbitrum.ts ├── parentChainIsMainnet.ts ├── prepareChainConfig.ts ├── prepareChainConfig.unit.test.ts ├── prepareKeyset.ts ├── prepareKeyset.unit.test.ts ├── prepareKeysetHash.ts ├── prepareKeysetHash.unit.test.ts ├── prepareKeysetHash.unit.testInputs-arbitrumNova.ts ├── prepareKeysetHash.unit.testInputs-xai.ts ├── prepareNodeConfig.ts ├── prepareUpgradeExecutorCallParameters.ts ├── rollupAdminLogicPrepareTransactionRequest.ts ├── rollupAdminLogicReadContract.ts ├── scripts │ └── generateNodeConfigType.ts ├── sequencerInboxPrepareTransactionRequest.ts ├── sequencerInboxReadContract.ts ├── setAnyTrustFastConfirmerPrepareTransactionRequest.ts ├── setValidKeyset.ts ├── setValidKeysetEncodeFunctionData.ts ├── setValidKeysetPrepareTransactionRequest.ts ├── testHelpers.ts ├── types │ ├── Actions.ts │ ├── ChainConfig.ts │ ├── CoreContracts.ts │ ├── NodeConfig.generated.ts │ ├── NodeConfig.ts │ ├── ParentChain.ts │ ├── TokenBridgeContracts.ts │ ├── createRollupTypes.ts │ ├── createTokenBridgeTypes.ts │ └── utils.ts ├── upgradeExecutor.integration.test.ts ├── upgradeExecutor.unit.test.ts ├── upgradeExecutorEncodeFunctionData.ts ├── upgradeExecutorFetchPrivilegedAccounts.ts ├── upgradeExecutorPrepareAddExecutorTransactionRequest.ts ├── upgradeExecutorPrepareRemoveExecutorTransactionRequest.ts ├── utils │ ├── __snapshots__ │ │ └── registerNewNetwork.unit.test.ts.snap │ ├── assertChainId.ts │ ├── decimals.ts │ ├── erc20.ts │ ├── gasOverrides.ts │ ├── generateChainId.ts │ ├── getArbOSVersion.ts │ ├── getArbOSVersion.unit.test.ts │ ├── getBlockExplorerUrl.ts │ ├── getClientVersion.ts │ ├── getClientVersion.unit.test.ts │ ├── getEarliestRollupCreatorDeploymentBlockNumber.ts │ ├── getImplementation.ts │ ├── getImplementation.unit.test.ts │ ├── getLogsWithBatching.ts │ ├── getLogsWithBatching.unit.test.ts │ ├── getParentChainFromId.ts │ ├── getParentChainLayer.ts │ ├── getRollupCreatorAddress.ts │ ├── getRollupCreatorAddress.unit.test.ts │ ├── getTokenBridgeCreatorAddress.ts │ ├── getTokenBridgeCreatorAddress.unit.test.ts │ ├── getWethAddress.ts │ ├── getWethAddress.unit.test.ts │ ├── index.ts │ ├── isCustomFeeTokenChain.ts │ ├── isNonZeroAddress.ts │ ├── registerNewNetwork.ts │ ├── registerNewNetwork.unit.test.ts │ ├── sanitizePrivateKey.ts │ └── validateChain.ts ├── validateParentChain.unit.test.ts ├── wasmModuleRoot.ts └── wasmModuleRoot.unit.test.ts ├── token-bridge-contracts └── Dockerfile ├── tsconfig.json ├── vitest.common.ts ├── vitest.integration.config.ts ├── vitest.unit.config.ts ├── wagmi.config.ts └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | ETHERSCAN_API_KEY= 2 | 3 | NITRO_TESTNODE_DEPLOYER_PRIVATE_KEY=0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659 4 | 5 | NITRO_TESTNODE_L2_ROLLUP_OWNER_PRIVATE_KEY=0xdc04c5399f82306ec4b4d654a342f40e2e0620fe39950d967e1e574b32d4dd36 6 | NITRO_TESTNODE_L2_TOKEN_BRIDGE_DEPLOYER_PRIVATE_KEY=0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659 7 | 8 | NITRO_TESTNODE_L3_ROLLUP_OWNER_PRIVATE_KEY=0xecdf21cb41c65afb51f91df408b7656e2c8739a5877f2814add0afd780cc210e 9 | NITRO_TESTNODE_L3_TOKEN_BRIDGE_DEPLOYER_PRIVATE_KEY=0xadd3d9301e184194943ce6244aa25c90e73c5843db16a994d202091f97f5bb27 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directories: 5 | - '**/*' 6 | schedule: 7 | interval: daily 8 | versioning-strategy: increase 9 | ignore: 10 | - dependency-name: '*' 11 | update-types: 12 | - 'version-update:semver-major' 13 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build, Test 2 | 3 | on: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 10 | cancel-in-progress: true 11 | 12 | env: 13 | ARBISCAN_API_KEY: ${{ secrets.ARBISCAN_API_KEY }} 14 | 15 | jobs: 16 | audit: 17 | name: 'Audit' 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Install node_modules 24 | uses: OffchainLabs/actions/node-modules/install@main 25 | 26 | - name: Run audit 27 | run: yarn audit:ci 28 | 29 | check-formatting: 30 | name: 'Check Formatting' 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v4 35 | 36 | - name: Install node_modules 37 | uses: OffchainLabs/actions/node-modules/install@main 38 | 39 | - name: Check formatting with Prettier 40 | run: yarn prettier:check 41 | 42 | test-unit: 43 | name: Test (Unit) 44 | runs-on: ubuntu-latest 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v4 48 | 49 | - name: Install node_modules 50 | uses: OffchainLabs/actions/node-modules/install@main 51 | 52 | - name: Copy .env 53 | run: cp ./.env.example ./.env 54 | 55 | - name: Build 56 | run: yarn build 57 | 58 | - name: Test 59 | run: yarn test:unit 60 | 61 | test-integration: 62 | name: Test (Integration) - ${{ matrix.config.name }} 63 | runs-on: ubuntu-latest 64 | strategy: 65 | matrix: 66 | config: 67 | - name: Custom gas token with 18 decimals 68 | args: --tokenbridge --l3node --l3-token-bridge --l3-fee-token 69 | decimals: 18 70 | - name: Custom gas token with 6 decimals 71 | args: --tokenbridge --l3node --l3-token-bridge --l3-fee-token --l3-fee-token-decimals 6 72 | decimals: 6 73 | steps: 74 | - name: Checkout 75 | uses: actions/checkout@v4 76 | 77 | - name: Install node_modules 78 | uses: OffchainLabs/actions/node-modules/install@main 79 | 80 | - name: Set up the local node 81 | uses: OffchainLabs/actions/run-nitro-test-node@feat-simplify 82 | with: 83 | nitro-testnode-ref: v3-support 84 | args: ${{ matrix.config.args }} 85 | 86 | - name: Copy .env 87 | run: cp ./.env.example ./.env 88 | 89 | - name: Build 90 | run: yarn build 91 | 92 | - name: Test 93 | run: DECIMALS=${{matrix.config.decimals}} yarn test:integration 94 | -------------------------------------------------------------------------------- /.github/workflows/check-pr-title.yml: -------------------------------------------------------------------------------- 1 | name: 'Check PR Title' 2 | # PR title is checked according to https://www.conventionalcommits.org/en/v1.0.0/ 3 | 4 | on: 5 | pull_request_target: 6 | types: 7 | - opened 8 | - edited 9 | - synchronize 10 | merge_group: 11 | 12 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | main: 19 | name: Check 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: amannn/action-semantic-pull-request@v5 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | subjectPattern: '^.{0,50}$' 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | src/dist 2 | src/generated.ts 3 | src/examples/prepare-node-config/node-config.json 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arbitrum Orbit SDK 2 | 3 | TypeScript SDK for building [Arbitrum Orbit](https://arbitrum.io/orbit) chains. 4 | 5 | ## Installation 6 | 7 | Make sure you are using Node.js v18 or greater. 8 | 9 | ```bash 10 | yarn add @arbitrum/orbit-sdk viem@^1.20.0 11 | ``` 12 | 13 | ## Run integration tests 14 | 15 | Clone the branch `main` of [nitro-testnode](https://github.com/OffchainLabs/nitro-testnode), and run the testnode using the following arguments: 16 | 17 | ```bash 18 | ./test-node.bash --init --tokenbridge --l3node --l3-fee-token --l3-token-bridge 19 | ``` 20 | 21 | Then, run the integration tests: 22 | 23 | ```bash 24 | yarn test:integration 25 | ``` 26 | 27 | ## Examples 28 | 29 | See [examples](./examples). 30 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | node-config.json 2 | -------------------------------------------------------------------------------- /examples/create-rollup-custom-fee-token/.env.example: -------------------------------------------------------------------------------- 1 | # Deployer private key 2 | DEPLOYER_PRIVATE_KEY= 3 | 4 | # Custom fee token address on the parent chain 5 | CUSTOM_FEE_TOKEN_ADDRESS= 6 | 7 | # Optional (these will be randomly generated if not provided) 8 | BATCH_POSTER_PRIVATE_KEY= 9 | VALIDATOR_PRIVATE_KEY= 10 | 11 | # (Optional) Infura RPC endpoint for the parent chain (Arbitrum Sepolia), if not provided, the script might fail with timeout 12 | PARENT_CHAIN_RPC=https://arbitrum-sepolia.infura.io/v3/YOUR_API_KEY -------------------------------------------------------------------------------- /examples/create-rollup-custom-fee-token/README.md: -------------------------------------------------------------------------------- 1 | # Arbitrum Orbit SDK 2 | 3 | ## Deploying a rollup using a custom token as gas token 4 | 5 | This is an example for deploying the rollup contracts for your Orbit chain to its parent chain. 6 | 7 | ```typescript 8 | // set the custom fee token 9 | const nativeToken: Address = process.env.CUSTOM_FEE_TOKEN_ADDRESS as `0x${string}`; 10 | 11 | const createRollupConfig = createRollupPrepareDeploymentParamsConfig(parentChainPublicClient, { 12 | chainId: BigInt(chainId), 13 | owner: deployer.address, 14 | chainConfig: prepareChainConfig({ 15 | chainId, 16 | arbitrum: { 17 | InitialChainOwner: deployer.address, 18 | DataAvailabilityCommittee: true, 19 | }, 20 | }), 21 | }); 22 | 23 | await createRollup({ 24 | params: { 25 | config: createRollupConfig, 26 | batchPosters: [batchPoster], 27 | validators: [validator], 28 | nativeToken, 29 | }, 30 | account: deployer, 31 | parentChainPublicClient, 32 | }); 33 | ``` 34 | 35 | ## Setup 36 | 37 | 1. Install dependencies 38 | 39 | ```bash 40 | yarn install 41 | ``` 42 | 43 | 2. Create .env file and add the env vars 44 | 45 | ```bash 46 | cp .env.example .env 47 | ``` 48 | 49 | 3. Run the example 50 | ```bash 51 | yarn dev 52 | ``` 53 | 54 | There is an option to deploy the rollup contracts using more low-level methods as demonstrated in `low_level.ts` or `yarn dev:low-level`. 55 | 56 | [Read full documentation](https://docs.arbitrum.io/launch-orbit-chain/how-tos/orbit-sdk-deploying-custom-gas-token-chain) 57 | -------------------------------------------------------------------------------- /examples/create-rollup-custom-fee-token/index.ts: -------------------------------------------------------------------------------- 1 | import { createPublicClient, http, Address } from 'viem'; 2 | import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; 3 | import { arbitrumSepolia } from 'viem/chains'; 4 | import { 5 | prepareChainConfig, 6 | createRollupPrepareDeploymentParamsConfig, 7 | createRollup, 8 | } from '@arbitrum/orbit-sdk'; 9 | import { sanitizePrivateKey, generateChainId } from '@arbitrum/orbit-sdk/utils'; 10 | import { config } from 'dotenv'; 11 | config(); 12 | 13 | function withFallbackPrivateKey(privateKey: string | undefined): `0x${string}` { 14 | if (typeof privateKey === 'undefined' || privateKey === '') { 15 | return generatePrivateKey(); 16 | } 17 | 18 | return sanitizePrivateKey(privateKey); 19 | } 20 | 21 | if (typeof process.env.DEPLOYER_PRIVATE_KEY === 'undefined') { 22 | throw new Error(`Please provide the "DEPLOYER_PRIVATE_KEY" environment variable`); 23 | } 24 | 25 | if (typeof process.env.CUSTOM_FEE_TOKEN_ADDRESS === 'undefined') { 26 | throw new Error(`Please provide the "CUSTOM_FEE_TOKEN_ADDRESS" environment variable`); 27 | } 28 | 29 | if (typeof process.env.PARENT_CHAIN_RPC === 'undefined' || process.env.PARENT_CHAIN_RPC === '') { 30 | console.warn( 31 | `Warning: you may encounter timeout errors while running the script with the default rpc endpoint. Please provide the "PARENT_CHAIN_RPC" environment variable instead.`, 32 | ); 33 | } 34 | 35 | // load or generate a random batch poster account 36 | const batchPosterPrivateKey = withFallbackPrivateKey(process.env.BATCH_POSTER_PRIVATE_KEY); 37 | const batchPoster = privateKeyToAccount(batchPosterPrivateKey).address; 38 | 39 | // load or generate a random validator account 40 | const validatorPrivateKey = withFallbackPrivateKey(process.env.VALIDATOR_PRIVATE_KEY); 41 | const validator = privateKeyToAccount(validatorPrivateKey).address; 42 | 43 | // set the parent chain and create a public client for it 44 | const parentChain = arbitrumSepolia; 45 | const parentChainPublicClient = createPublicClient({ 46 | chain: parentChain, 47 | transport: http(process.env.PARENT_CHAIN_RPC), 48 | }); 49 | 50 | // load the deployer account 51 | const deployer = privateKeyToAccount(sanitizePrivateKey(process.env.DEPLOYER_PRIVATE_KEY)); 52 | 53 | async function main() { 54 | // generate a random chain id 55 | const chainId = generateChainId(); 56 | // set the custom fee token 57 | const nativeToken: Address = process.env.CUSTOM_FEE_TOKEN_ADDRESS as `0x${string}`; 58 | 59 | const createRollupConfig = createRollupPrepareDeploymentParamsConfig(parentChainPublicClient, { 60 | chainId: BigInt(chainId), 61 | owner: deployer.address, 62 | chainConfig: prepareChainConfig({ 63 | chainId, 64 | arbitrum: { 65 | InitialChainOwner: deployer.address, 66 | DataAvailabilityCommittee: true, 67 | }, 68 | }), 69 | }); 70 | 71 | try { 72 | await createRollup({ 73 | params: { 74 | config: createRollupConfig, 75 | batchPosters: [batchPoster], 76 | validators: [validator], 77 | nativeToken, 78 | }, 79 | account: deployer, 80 | parentChainPublicClient, 81 | }); 82 | } catch (error) { 83 | console.error(`Rollup creation failed with error: ${error}`); 84 | } 85 | } 86 | 87 | main(); 88 | -------------------------------------------------------------------------------- /examples/create-rollup-custom-fee-token/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-rollup-custom-fee-token", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "tsc --outDir dist && node ./dist/index.js", 8 | "dev:low-level": "tsc --outDir dist && node ./dist/low_level.js" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "^20.9.0", 12 | "typescript": "^5.2.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/create-rollup-custom-fee-token/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "include": ["./**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/create-rollup-eth/.env.example: -------------------------------------------------------------------------------- 1 | # Required 2 | DEPLOYER_PRIVATE_KEY= 3 | 4 | # Optional (these will be randomly generated if not provided) 5 | BATCH_POSTER_PRIVATE_KEY= 6 | VALIDATOR_PRIVATE_KEY= 7 | 8 | # (Optional) Infura RPC endpoint for the parent chain (Arbitrum Sepolia), if not provided, the script might fail with timeout 9 | PARENT_CHAIN_RPC=https://arbitrum-sepolia.infura.io/v3/YOUR_API_KEY -------------------------------------------------------------------------------- /examples/create-rollup-eth/README.md: -------------------------------------------------------------------------------- 1 | # Arbitrum Orbit SDK 2 | 3 | ## Deploying a rollup using ETH as gas token 4 | 5 | This is an example for deploying the rollup contracts for your Orbit chain to its parent chain. 6 | 7 | ```typescript 8 | const createRollupConfig = createRollupPrepareDeploymentParamsConfig(parentChainPublicClient, { 9 | chainId: BigInt(chainId), 10 | owner: deployer.address, 11 | chainConfig: prepareChainConfig({ 12 | chainId, 13 | arbitrum: { 14 | InitialChainOwner: deployer.address, 15 | DataAvailabilityCommittee: true, 16 | }, 17 | }), 18 | }); 19 | 20 | await createRollup({ 21 | params: { 22 | config: createRollupConfig, 23 | batchPosters: [batchPoster], 24 | validators: [validator], 25 | }, 26 | account: deployer, 27 | parentChainPublicClient, 28 | }); 29 | ``` 30 | 31 | ## Setup 32 | 33 | 1. Install dependencies 34 | 35 | ```bash 36 | yarn install 37 | ``` 38 | 39 | 2. Create .env file and add the env vars 40 | 41 | ```bash 42 | cp .env.example .env 43 | ``` 44 | 45 | 3. Run the example 46 | ```bash 47 | yarn dev 48 | ``` 49 | 50 | There is an option to deploy the rollup contracts using more low-level methods as demonstrated in `low_level.ts` or `yarn dev:low-level`. 51 | 52 | [Read full documentation](https://docs.arbitrum.io/launch-orbit-chain/how-tos/orbit-sdk-deploying-rollup-chain) 53 | -------------------------------------------------------------------------------- /examples/create-rollup-eth/index.ts: -------------------------------------------------------------------------------- 1 | import { createPublicClient, http } from 'viem'; 2 | import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; 3 | import { arbitrumSepolia } from 'viem/chains'; 4 | import { 5 | prepareChainConfig, 6 | createRollupPrepareDeploymentParamsConfig, 7 | createRollup, 8 | } from '@arbitrum/orbit-sdk'; 9 | import { sanitizePrivateKey, generateChainId } from '@arbitrum/orbit-sdk/utils'; 10 | import { config } from 'dotenv'; 11 | config(); 12 | 13 | function withFallbackPrivateKey(privateKey: string | undefined): `0x${string}` { 14 | if (typeof privateKey === 'undefined' || privateKey === '') { 15 | return generatePrivateKey(); 16 | } 17 | 18 | return sanitizePrivateKey(privateKey); 19 | } 20 | 21 | if (typeof process.env.DEPLOYER_PRIVATE_KEY === 'undefined') { 22 | throw new Error(`Please provide the "DEPLOYER_PRIVATE_KEY" environment variable`); 23 | } 24 | 25 | if (typeof process.env.PARENT_CHAIN_RPC === 'undefined' || process.env.PARENT_CHAIN_RPC === '') { 26 | console.warn( 27 | `Warning: you may encounter timeout errors while running the script with the default rpc endpoint. Please provide the "PARENT_CHAIN_RPC" environment variable instead.`, 28 | ); 29 | } 30 | 31 | // load or generate a random batch poster account 32 | const batchPosterPrivateKey = withFallbackPrivateKey(process.env.BATCH_POSTER_PRIVATE_KEY); 33 | const batchPoster = privateKeyToAccount(batchPosterPrivateKey).address; 34 | 35 | // load or generate a random validator account 36 | const validatorPrivateKey = withFallbackPrivateKey(process.env.VALIDATOR_PRIVATE_KEY); 37 | const validator = privateKeyToAccount(validatorPrivateKey).address; 38 | 39 | // set the parent chain and create a public client for it 40 | const parentChain = arbitrumSepolia; 41 | const parentChainPublicClient = createPublicClient({ 42 | chain: parentChain, 43 | transport: http(process.env.PARENT_CHAIN_RPC), 44 | }); 45 | 46 | // load the deployer account 47 | const deployer = privateKeyToAccount(sanitizePrivateKey(process.env.DEPLOYER_PRIVATE_KEY)); 48 | 49 | async function main() { 50 | // generate a random chain id 51 | const chainId = generateChainId(); 52 | 53 | const createRollupConfig = createRollupPrepareDeploymentParamsConfig(parentChainPublicClient, { 54 | chainId: BigInt(chainId), 55 | owner: deployer.address, 56 | chainConfig: prepareChainConfig({ 57 | chainId, 58 | arbitrum: { 59 | InitialChainOwner: deployer.address, 60 | DataAvailabilityCommittee: true, 61 | }, 62 | }), 63 | }); 64 | 65 | try { 66 | await createRollup({ 67 | params: { 68 | config: createRollupConfig, 69 | batchPosters: [batchPoster], 70 | validators: [validator], 71 | }, 72 | account: deployer, 73 | parentChainPublicClient, 74 | }); 75 | } catch (error) { 76 | console.error(`Rollup creation failed with error: ${error}`); 77 | } 78 | } 79 | 80 | main(); 81 | -------------------------------------------------------------------------------- /examples/create-rollup-eth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-rollup-eth", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "tsc --outDir dist && node ./dist/index.js", 8 | "dev:low-level": "tsc --outDir dist && node ./dist/low_level.js" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "^20.9.0", 12 | "typescript": "^5.2.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/create-rollup-eth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "include": ["./**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/create-token-bridge-custom-fee-token/.env.example: -------------------------------------------------------------------------------- 1 | # Address of the Rollup contract 2 | ROLLUP_ADDRESS= 3 | 4 | # Private key of the account with executor privileges in the UpgradeExecutor admin contract for the chain 5 | # (usually, the deployer of the chain) 6 | ROLLUP_OWNER_PRIVATE_KEY= 7 | 8 | # Custom fee token address on the parent chain 9 | CUSTOM_FEE_TOKEN_ADDRESS= 10 | 11 | # Orbit chain configuration 12 | ORBIT_CHAIN_ID= 13 | ORBIT_CHAIN_RPC= 14 | 15 | # (Optional) Infura RPC endpoint for the parent chain (Arbitrum Sepolia), if not provided, the script might fail with timeout 16 | PARENT_CHAIN_RPC=https://arbitrum-sepolia.infura.io/v3/YOUR_API_KEY -------------------------------------------------------------------------------- /examples/create-token-bridge-custom-fee-token/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-token-bridge-custom-fee-token", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "tsc --outDir dist && node ./dist/index.js" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^20.9.0", 11 | "typescript": "^5.2.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/create-token-bridge-custom-fee-token/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "include": ["./**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/create-token-bridge-eth/.env.example: -------------------------------------------------------------------------------- 1 | # Address of the Rollup contract 2 | ROLLUP_ADDRESS= 3 | 4 | # Private key of the account with executor privileges in the UpgradeExecutor admin contract for the chain 5 | # (usually, the deployer of the chain) 6 | ROLLUP_OWNER_PRIVATE_KEY= 7 | 8 | # Orbit chain configuration 9 | ORBIT_CHAIN_ID= 10 | ORBIT_CHAIN_RPC= 11 | 12 | # (Optional) Infura RPC endpoint for the parent chain (Arbitrum Sepolia), if not provided, the script might fail with timeout 13 | PARENT_CHAIN_RPC=https://arbitrum-sepolia.infura.io/v3/YOUR_API_KEY -------------------------------------------------------------------------------- /examples/create-token-bridge-eth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-token-bridge-eth", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "tsc --outDir dist && node ./dist/index.js" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^20.9.0", 11 | "typescript": "^5.2.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/create-token-bridge-eth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "include": ["./**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/prepare-node-config/.env.example: -------------------------------------------------------------------------------- 1 | # Required 2 | ORBIT_DEPLOYMENT_TRANSACTION_HASH= 3 | BATCH_POSTER_PRIVATE_KEY= 4 | VALIDATOR_PRIVATE_KEY= 5 | 6 | # For L2 Orbit chains settling to Ethereum mainnet or testnet 7 | ETHEREUM_BEACON_RPC_URL= 8 | 9 | # (Optional) Infura RPC endpoint for the parent chain (Arbitrum Sepolia), if not provided, the script might fail with timeout 10 | PARENT_CHAIN_RPC=https://arbitrum-sepolia.infura.io/v3/YOUR_API_KEY -------------------------------------------------------------------------------- /examples/prepare-node-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prepare-node-config", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "tsc --outDir dist && node ./dist/index.js" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^20.9.0", 11 | "typescript": "^5.2.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/prepare-node-config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "include": ["./**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/set-new-validators/.env.example: -------------------------------------------------------------------------------- 1 | # Address of the Rollup contract 2 | ROLLUP_ADDRESS= 3 | 4 | # Private key of the account with executor privileges in the UpgradeExecutor admin contract for the chain 5 | # (usually, the deployer of the chain) 6 | ROLLUP_OWNER_PRIVATE_KEY= 7 | 8 | # Address of the account to give the validtaor role 9 | NEW_VALIDATOR_ADDRESS= 10 | 11 | # (Optional) Infura RPC endpoint for the parent chain (Arbitrum Sepolia), if not provided, the script might fail with timeout 12 | PARENT_CHAIN_RPC=https://arbitrum-sepolia.infura.io/v3/YOUR_API_KEY -------------------------------------------------------------------------------- /examples/set-new-validators/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "set-new-validators", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "tsc --outDir dist && node ./dist/index.js" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^20.9.0", 11 | "typescript": "^5.2.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/set-new-validators/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "include": ["./**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/set-valid-keyset/.env.example: -------------------------------------------------------------------------------- 1 | # Deployer private key 2 | DEPLOYER_PRIVATE_KEY= 3 | 4 | # (Optional) Infura RPC endpoint for the parent chain (Arbitrum Sepolia), if not provided, the script might fail with timeout 5 | PARENT_CHAIN_RPC=https://arbitrum-sepolia.infura.io/v3/YOUR_API_KEY -------------------------------------------------------------------------------- /examples/set-valid-keyset/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain, createPublicClient, http } from 'viem'; 2 | import { privateKeyToAccount } from 'viem/accounts'; 3 | import { arbitrumSepolia } from 'viem/chains'; 4 | import { setValidKeysetPrepareTransactionRequest } from '@arbitrum/orbit-sdk'; 5 | import { sanitizePrivateKey } from '@arbitrum/orbit-sdk/utils'; 6 | 7 | function getBlockExplorerUrl(chain: Chain) { 8 | return chain.blockExplorers?.default.url; 9 | } 10 | 11 | if (typeof process.env.DEPLOYER_PRIVATE_KEY === 'undefined') { 12 | throw new Error(`Please provide the "DEPLOYER_PRIVATE_KEY" environment variable`); 13 | } 14 | 15 | if (typeof process.env.PARENT_CHAIN_RPC === 'undefined' || process.env.PARENT_CHAIN_RPC === '') { 16 | console.warn( 17 | `Warning: you may encounter timeout errors while running the script with the default rpc endpoint. Please provide the "PARENT_CHAIN_RPC" environment variable instead.`, 18 | ); 19 | } 20 | 21 | const keyset = 22 | '0x00000000000000010000000000000001012160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; 23 | 24 | // set the parent chain and create a public client for it 25 | const parentChain = arbitrumSepolia; 26 | const parentChainPublicClient = createPublicClient({ 27 | chain: parentChain, 28 | transport: http(process.env.PARENT_CHAIN_RPC), 29 | }); 30 | 31 | // load the deployer account 32 | const deployer = privateKeyToAccount(sanitizePrivateKey(process.env.DEPLOYER_PRIVATE_KEY)); 33 | 34 | async function main() { 35 | // prepare the transaction setting the keyset 36 | const txRequest = await setValidKeysetPrepareTransactionRequest({ 37 | coreContracts: { 38 | upgradeExecutor: '0x82c42d2cdcbe6b4482900e299b3532082e217132', 39 | sequencerInbox: '0x42b5da0625cf278067955f07045f63cafd79274f', 40 | }, 41 | keyset, 42 | account: deployer.address, 43 | publicClient: parentChainPublicClient, 44 | }); 45 | 46 | // sign and send the transaction 47 | const txHash = await parentChainPublicClient.sendRawTransaction({ 48 | serializedTransaction: await deployer.signTransaction(txRequest), 49 | }); 50 | 51 | // wait for the transaction receipt 52 | const txReceipt = await parentChainPublicClient.waitForTransactionReceipt({ hash: txHash }); 53 | 54 | console.log( 55 | `Keyset updated in ${getBlockExplorerUrl(parentChain)}/tx/${txReceipt.transactionHash}`, 56 | ); 57 | } 58 | 59 | main(); 60 | -------------------------------------------------------------------------------- /examples/set-valid-keyset/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "set-valid-keyset", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "tsc --outDir dist && node ./dist/index.js" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^20.9.0", 11 | "typescript": "^5.2.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/set-valid-keyset/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "include": ["./**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/setup-aep-fee-router/.env.example: -------------------------------------------------------------------------------- 1 | # Address of the Rollup contract 2 | ROLLUP_ADDRESS= 3 | 4 | # Private key of the account with executor privileges in the UpgradeExecutor admin contract for the chain 5 | # (usually, the deployer of the chain) 6 | CHAIN_OWNER_PRIVATE_KEY= 7 | 8 | # Orbit chain configuration 9 | ORBIT_CHAIN_ID= 10 | ORBIT_CHAIN_RPC= 11 | 12 | # Parent chain id 13 | PARENT_CHAIN_ID=1 14 | 15 | # Parent chain target address. It will receive 10% of the chain revenue on the parent chain 16 | # Check the README of this directory to find out more about what address to use 17 | PARENT_CHAIN_TARGET_ADDRESS= -------------------------------------------------------------------------------- /examples/setup-aep-fee-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "setup-aep-fee-router", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "tsc --outDir dist && node ./dist/index.js" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^20.9.0", 11 | "typescript": "^5.2.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/setup-aep-fee-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "include": ["./**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/setup-fast-withdrawal/.env.example: -------------------------------------------------------------------------------- 1 | # Private key of an account with executor privileges in the UpgradeExecutor admin contract for the chain. 2 | CHAIN_OWNER_PRIVATE_KEY= 3 | 4 | # Parent chain id 5 | PARENT_CHAIN_ID=421614 6 | 7 | # Address of the Rollup contract 8 | ROLLUP_ADDRESS= 9 | 10 | # Fast-confirmation validators (comma-separated array) 11 | FC_VALIDATORS=["0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567891"] 12 | 13 | # Minimum number of blocks that have to pass in between assertions (measured in L1 blocks) 14 | MINIMUM_ASSERTION_PERIOD=75 15 | -------------------------------------------------------------------------------- /examples/setup-fast-withdrawal/README.md: -------------------------------------------------------------------------------- 1 | # Setup a fast-withdrawal committee for your AnyTrust Orbit chain 2 | 3 | This example script shows how to setup a fast-withdrawal committee for your AnyTrust Orbit chain. 4 | 5 | ## Rationale 6 | 7 | Optimistic rollups must sustain a multi-day challenge period to allow time for fraud proofs. This delays finality for users and apps, resulting in multi-day withdrawal times and cross-chain communication delays. 8 | 9 | Fast Withdrawals is a new configuration allowing Orbit chains to achieve fast finality. Orbit chains with Fast Withdrawals will have their transactions processed by a committee of validators. Transactions with a unanimous vote across the committee will have their state transition immediately confirmed. 10 | 11 | This will allow: 12 | 13 | - Orbit chains can configure a fast confirmation frequency (any time up to 15 minutes) 14 | - User withdrawals to are confirmed on the parent chain at frequencies up to ~15 minutes 15 | - Enhanced cross-chain communication by allowing cross-chain apps to read finalized state up to the fast confirmation frequency 16 | 17 | ## How it works 18 | 19 | This script performs the following operations: 20 | 21 | 1. Create a new n/n Safe wallet with the specified validators as signers 22 | 2. Add the specified validators to the Rollup validators whitelist 23 | 3. Set the new Safe wallet as the anytrustFastConfirmer in the Rollup contract 24 | 4. Set the new minimumAssertionPeriod if needed 25 | 5. Show how to configure the batch poster and validator nodes 26 | 27 | ## Variables needed 28 | 29 | You need to set the following environment variables in an .env file: 30 | 31 | - CHAIN_OWNER_PRIVATE_KEY: private key of the account with executor privileges in the UpgradeExecutor admin contract for the chain. It will be the deployer of the multisig Safe wallet. 32 | - PARENT_CHAIN_ID: chainId of the parent chain. 33 | - ROLLUP_ADDRESS: address of the Rollup contract. 34 | - FC_VALIDATORS: array of fast-withdrawal validators. They will be added as signers to the multisig Safe wallet, and will be added to the Rollup's validator whitelist. 35 | - MINIMUM_ASSERTION_PERIOD: optional parameter. Minimum number of blocks that have to pass in between assertions (measured in L1 blocks). 36 | 37 | ## Setup 38 | 39 | 1. Install dependencies 40 | 41 | ```bash 42 | yarn install 43 | ``` 44 | 45 | 2. Create .env file and add the env vars 46 | 47 | ```bash 48 | cp .env.example .env 49 | ``` 50 | 51 | 3. Run the example 52 | ```bash 53 | yarn dev 54 | ``` 55 | -------------------------------------------------------------------------------- /examples/setup-fast-withdrawal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "setup-fast-withdrawal", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "tsc --outDir dist && node ./dist/index.js" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^20.9.0", 11 | "typescript": "^5.2.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/setup-fast-withdrawal/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "include": ["./**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/setup-fee-distributor-contract/.env.example: -------------------------------------------------------------------------------- 1 | # Address of the Rollup contract 2 | ROLLUP_ADDRESS= 3 | 4 | # Private key of the account with executor privileges in the UpgradeExecutor admin contract for the chain 5 | # (usually, the deployer of the chain) 6 | CHAIN_OWNER_PRIVATE_KEY= 7 | 8 | # Orbit chain configuration 9 | ORBIT_CHAIN_ID= 10 | ORBIT_CHAIN_RPC= 11 | 12 | # Parent chain id 13 | PARENT_CHAIN_ID=421614 14 | 15 | # The RewardDistributor contract will distribute the amounts received among the recipients configured here, 16 | # based on the weight of each recipient. For example, if one recipient has a weight of 75%, they will receive 17 | # 75% of the amount held in the contract at the time of distribution. 18 | # 19 | # Weights are expressed in percentages multiplied by 100. For example, to allocate 12,5% of the amount to 20 | # a specific recipient, you'll define the weight as 1250. To allocate 80%, you'll define the weight as 8000. 21 | # 22 | # You can configure as many recipients as you wish. 23 | RECIPIENT_ADDRESSES=["0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890"] 24 | RECIPIENT_WEIGHTS=[7500, 2500] 25 | -------------------------------------------------------------------------------- /examples/setup-fee-distributor-contract/README.md: -------------------------------------------------------------------------------- 1 | # Arbitrum Orbit SDK 2 | 3 | ## Setup fee distributor contract 4 | 5 | By default, individual addresses are set to collect each of the fee types of an Orbit chain. However, some chains may require multiple addresses to receive the collected fees of any of the available types. In those cases, there's the possibility of using a distributor contract that can gather all fees of a specific type and distribute those among multiple addresses. 6 | 7 | This example shows how to configure a distributor contract to manage the Orbit base fees of a chain. You can also use this same example to configure additional distributor contracts to manage other fee types. 8 | 9 | You can find more information about the different fee types, and how to configure other fee types in [How to manage fee collector addresses](https://docs.arbitrum.io/launch-orbit-chain/how-tos/manage-fee-collectors) 10 | 11 | ## Variables needed 12 | 13 | - ROLLUP_ADDRESS: address of the Rollup contract 14 | - CHAIN_OWNER_PRIVATE_KEY: private key of the account with executor privileges in the UpgradeExecutor admin contract for the chain 15 | - ORBIT_CHAIN_ID: chainId of the Orbit chain 16 | - ORBIT_CHAIN_RPC: RPC of the Orbit chain 17 | - PARENT_CHAIN_ID: chainId of the parent chain 18 | - RECIPIENT_ADDRESSES: Array of addresses to configure in the distributor contract 19 | - RECIPIENT_WEIGHTS: Array of weights of each of the configured addresses 20 | 21 | ## How to configure the distribution addresses in the .env file 22 | 23 | The .env file contains two arrays: RECIPIENT_ADDRESSES and RECIPIENT_WEIGHTS. The first one will have the addresses that will receive the amounts that the distributor contract receives. The second one will define the weights of each of those addresses. For example, if one recipient has a weight of 75%, they will receive 75% of the amount held in the contract at the time of distribution. Both arrays must have the same length. 24 | 25 | Weights are expressed in percentages multiplied by 100. For example, to allocate 12,5% of the amount to a specific recipient, you'll define the weight as 1250. To allocate 80%, you'll define the weight as 8000. 26 | 27 | You can configure as many recipients as you wish. 28 | 29 | ## Setup 30 | 31 | 1. Install dependencies 32 | 33 | ```bash 34 | yarn install 35 | ``` 36 | 37 | 2. Create .env file and add the env vars 38 | 39 | ```bash 40 | cp .env.example .env 41 | ``` 42 | 43 | 3. Run the example 44 | ```bash 45 | yarn dev 46 | ``` 47 | 48 | ## References 49 | 50 | - [How to manage fee collector addresses](https://docs.arbitrum.io/launch-orbit-chain/how-tos/manage-fee-collectors) 51 | -------------------------------------------------------------------------------- /examples/setup-fee-distributor-contract/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "setup-fee-distributor-contract", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "tsc --outDir dist && node ./dist/index.js" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^20.9.0", 11 | "typescript": "^5.2.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/setup-fee-distributor-contract/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "include": ["./**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "incremental": true, 5 | 6 | "lib": ["ES2021", "DOM"], 7 | "target": "ES2021", 8 | 9 | "moduleResolution": "node", 10 | 11 | // JavaScript support 12 | "allowJs": false, 13 | "checkJs": false, 14 | 15 | // Skip type checking for node modules 16 | "skipLibCheck": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/upgrade-executor-add-account/.env.example: -------------------------------------------------------------------------------- 1 | # Address of the Rollup contract 2 | ROLLUP_ADDRESS= 3 | 4 | # Private key of the account with executor privileges in the UpgradeExecutor admin contract for the chain 5 | # (usually, the deployer of the chain) 6 | ROLLUP_OWNER_PRIVATE_KEY= 7 | 8 | # Address of the account to give the executor role 9 | NEW_EXECUTOR_ACCOUNT_ADDRESS= 10 | 11 | # Orbit chain configuration 12 | ORBIT_CHAIN_ID= 13 | ORBIT_CHAIN_RPC= 14 | 15 | # (Optional) Infura RPC endpoint for the parent chain (Arbitrum Sepolia), if not provided, the script might fail with timeout 16 | PARENT_CHAIN_RPC=https://arbitrum-sepolia.infura.io/v3/YOUR_API_KEY -------------------------------------------------------------------------------- /examples/upgrade-executor-add-account/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "upgrade-executor-add-account", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "tsc --outDir dist && node ./dist/index.js" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^20.9.0", 11 | "typescript": "^5.2.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/upgrade-executor-add-account/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "include": ["./**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "workspaces": [ 3 | "src", 4 | "examples/*" 5 | ], 6 | "private": true, 7 | "type": "module", 8 | "scripts": { 9 | "prebuild": "rm -rf ./src/dist", 10 | "build": "tsc --project ./tsconfig.json --module commonjs --outDir ./src/dist --declaration", 11 | "dev": "yarn build --watch", 12 | "generate": "wagmi generate", 13 | "generate:node-config-type": "yarn build && node ./src/dist/scripts/generateNodeConfigType.js", 14 | "postgenerate:node-config-type": "prettier --write ./src/types/NodeConfig.generated.ts", 15 | "test:unit": "vitest --config vitest.unit.config.ts", 16 | "test:integration": "vitest --config vitest.integration.config.ts", 17 | "postinstall": "patch-package", 18 | "prettier:check": "prettier --check '**/*.{ts,md,json,yml}'", 19 | "prettier:format": "prettier --write '**/*.{ts,md,json,yml}'", 20 | "audit:ci": "audit-ci --config ./audit-ci.jsonc" 21 | }, 22 | "devDependencies": { 23 | "@offchainlabs/prettier-config": "0.2.1", 24 | "@wagmi/cli": "^1.5.2", 25 | "audit-ci": "^7.0.1", 26 | "dotenv": "^16.3.1", 27 | "patch-package": "^8.0.0", 28 | "postinstall-postinstall": "^2.1.0", 29 | "prettier": "^2.8.3", 30 | "ts-morph": "^21.0.1", 31 | "typescript": "^5.2.2", 32 | "vitest": "^3.0.9" 33 | }, 34 | "resolutions": { 35 | "**/@wagmi/cli/viem/ws": "8.17.1", 36 | "**/@ethersproject/providers/ws": "7.5.10", 37 | "**/elliptic": "6.6.1", 38 | "**/nanoid": "3.3.8", 39 | "**/base-x": "3.0.11" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /patches/@wagmi+cli+1.5.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@wagmi/cli/dist/plugins/index.d.ts b/node_modules/@wagmi/cli/dist/plugins/index.d.ts 2 | index 04a78d1..0eb6430 100644 3 | --- a/node_modules/@wagmi/cli/dist/plugins/index.d.ts 4 | +++ b/node_modules/@wagmi/cli/dist/plugins/index.d.ts 5 | @@ -119,7 +119,9 @@ declare const apiUrls: { 6 | 137: string; 7 | 80001: string; 8 | 42161: string; 9 | + 42170: string; 10 | 421613: string; 11 | + 421614: string; 12 | 56: string; 13 | 97: string; 14 | 128: string; 15 | diff --git a/node_modules/@wagmi/cli/dist/plugins/index.js b/node_modules/@wagmi/cli/dist/plugins/index.js 16 | index 8e23da2..02b7c49 100644 17 | --- a/node_modules/@wagmi/cli/dist/plugins/index.js 18 | +++ b/node_modules/@wagmi/cli/dist/plugins/index.js 19 | @@ -386,12 +386,12 @@ function blockExplorer({ 20 | throw new Error(parsed.data.result); 21 | return parsed.data.result; 22 | }, 23 | - request({ address }) { 24 | + request({ address, implementation }) { 25 | if (!address) 26 | throw new Error("address is required"); 27 | return { 28 | url: `${baseUrl}?module=contract&action=getabi&address=${getAddress({ 29 | - address 30 | + address, implementation 31 | })}${apiKey ? `&apikey=${apiKey}` : ""}` 32 | }; 33 | } 34 | @@ -1386,7 +1386,9 @@ var apiUrls = { 35 | [137]: "https://api.polygonscan.com/api", 36 | [80001]: "https://api-testnet.polygonscan.com/api", 37 | [42161]: "https://api.arbiscan.io/api", 38 | + [42170]: "https://api-nova.arbiscan.io/api", 39 | [421613]: "https://api-goerli.arbiscan.io/api", 40 | + [421614]: "https://api-sepolia.arbiscan.io/api", 41 | [56]: "https://api.bscscan.com/api", 42 | [97]: "https://api-testnet.bscscan.com/api", 43 | [128]: "https://api.hecoinfo.com/api", 44 | @@ -1409,16 +1411,16 @@ function etherscan({ 45 | address: typeof x.address === "string" ? { [chainId]: x.address } : x.address 46 | })); 47 | return blockExplorer({ 48 | - apiKey, 49 | - baseUrl: apiUrls[chainId], 50 | + apiKey: `${apiKey}&chainid=${chainId}`, 51 | + baseUrl: `https://api.etherscan.io/v2/api`, 52 | cacheDuration, 53 | contracts, 54 | - getAddress({ address }) { 55 | + getAddress({ address, implementation = {} }) { 56 | if (!address) 57 | throw new Error("address is required"); 58 | if (typeof address === "string") 59 | return address; 60 | - const contractAddress = address[chainId]; 61 | + const contractAddress = implementation[chainId] ?? address[chainId]; 62 | if (!contractAddress) 63 | throw new Error( 64 | `No address found for chainId "${chainId}". Make sure chainId "${chainId}" is set as an address.` 65 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('@offchainlabs/prettier-config'), 3 | // override here 4 | }; 5 | -------------------------------------------------------------------------------- /src/__snapshots__/createRollupPrepareConfig.unit.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`creates config with defaults 1`] = ` 4 | { 5 | "baseStake": 100000000000000000n, 6 | "chainConfig": "{\\"homesteadBlock\\":0,\\"daoForkBlock\\":null,\\"daoForkSupport\\":true,\\"eip150Block\\":0,\\"eip150Hash\\":\\"0x0000000000000000000000000000000000000000000000000000000000000000\\",\\"eip155Block\\":0,\\"eip158Block\\":0,\\"byzantiumBlock\\":0,\\"constantinopleBlock\\":0,\\"petersburgBlock\\":0,\\"istanbulBlock\\":0,\\"muirGlacierBlock\\":0,\\"berlinBlock\\":0,\\"londonBlock\\":0,\\"clique\\":{\\"period\\":0,\\"epoch\\":0},\\"arbitrum\\":{\\"EnableArbOS\\":true,\\"AllowDebugPrecompiles\\":false,\\"DataAvailabilityCommittee\\":false,\\"InitialArbOSVersion\\":20,\\"GenesisBlockNum\\":0,\\"MaxCodeSize\\":24576,\\"MaxInitCodeSize\\":49152,\\"InitialChainOwner\\":\\"0xd8da6bf26964af9d7eed9e03e53415d37aa96045\\"},\\"chainId\\":69420}", 7 | "chainId": 69420n, 8 | "confirmPeriodBlocks": 150n, 9 | "extraChallengeTimeBlocks": 0n, 10 | "genesisBlockNum": 0n, 11 | "loserStakeEscrow": "0x0000000000000000000000000000000000000000", 12 | "owner": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", 13 | "sequencerInboxMaxTimeVariation": { 14 | "delayBlocks": 5760n, 15 | "delaySeconds": 86400n, 16 | "futureBlocks": 48n, 17 | "futureSeconds": 3600n, 18 | }, 19 | "stakeToken": "0x0000000000000000000000000000000000000000", 20 | "wasmModuleRoot": "0x8b104a2e80ac6165dc58b9048de12f301d70b02a0ab51396c22b4b4b802a16a4", 21 | } 22 | `; 23 | 24 | exports[`creates config with overrides 1`] = ` 25 | { 26 | "baseStake": 100000000000000000n, 27 | "chainConfig": "{\\"homesteadBlock\\":0,\\"daoForkBlock\\":null,\\"daoForkSupport\\":true,\\"eip150Block\\":0,\\"eip150Hash\\":\\"0x0000000000000000000000000000000000000000000000000000000000000000\\",\\"eip155Block\\":0,\\"eip158Block\\":0,\\"byzantiumBlock\\":0,\\"constantinopleBlock\\":0,\\"petersburgBlock\\":0,\\"istanbulBlock\\":0,\\"muirGlacierBlock\\":0,\\"berlinBlock\\":0,\\"londonBlock\\":0,\\"clique\\":{\\"period\\":0,\\"epoch\\":0},\\"arbitrum\\":{\\"EnableArbOS\\":true,\\"AllowDebugPrecompiles\\":false,\\"DataAvailabilityCommittee\\":true,\\"InitialArbOSVersion\\":30,\\"GenesisBlockNum\\":0,\\"MaxCodeSize\\":24576,\\"MaxInitCodeSize\\":49152,\\"InitialChainOwner\\":\\"0xd8da6bf26964af9d7eed9e03e53415d37aa96045\\"},\\"chainId\\":69420}", 28 | "chainId": 69420n, 29 | "confirmPeriodBlocks": 4200n, 30 | "extraChallengeTimeBlocks": 5n, 31 | "genesisBlockNum": 0n, 32 | "loserStakeEscrow": "0x0000000000000000000000000000000000000001", 33 | "owner": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", 34 | "sequencerInboxMaxTimeVariation": { 35 | "delayBlocks": 200n, 36 | "delaySeconds": 5n, 37 | "futureBlocks": 100n, 38 | "futureSeconds": 1n, 39 | }, 40 | "stakeToken": "0x0000000000000000000000000000000000000002", 41 | "wasmModuleRoot": "0xWasmModuleRoot", 42 | } 43 | `; 44 | -------------------------------------------------------------------------------- /src/__snapshots__/createRollupPrepareTransactionReceipt.unit.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`successfully parses core contracts from a tx receipt on RollupCreator v1.1 1`] = ` 4 | { 5 | "adminProxy": "0x38E3258488C2341606230E27c4a440e81c987fCF", 6 | "bridge": "0xac51D5f4B14f520D111D14777e8965b224CE2919", 7 | "challengeManager": "0x5f4273F26836B3Def34Bc11274Dd7D38200E8E2d", 8 | "deployedAtBlockNumber": 49500532, 9 | "inbox": "0x6fDBF2541c1168E6D9f7A3A131204a33Ace085df", 10 | "nativeToken": "0x0000000000000000000000000000000000000000", 11 | "outbox": "0x2732C86946c20b9Eba0d7fA0beCcfd7517FCD285", 12 | "rollup": "0x1644590Fd2223264ea8Cda8927B038CcCFE0Da76", 13 | "rollupEventInbox": "0x60F938D87f929D1B413BA6bc9d8cA8310710BF1a", 14 | "sequencerInbox": "0x96bA492C55Af83dfC88D52A1e584e4061716e9e8", 15 | "upgradeExecutor": "0x5FbdF85F9be59A8084dbD8a7BD457AD3c13C95aE", 16 | "validatorUtils": "0xB11EB62DD2B352886A4530A9106fE427844D515f", 17 | "validatorWalletCreator": "0xEb9885B6c0e117D339F47585cC06a2765AaE2E0b", 18 | } 19 | `; 20 | 21 | exports[`successfully parses core contracts from a tx receipt on RollupCreator v2.1 1`] = ` 22 | { 23 | "adminProxy": "0x4c6f9229f13cD1922B0E237c5A01CA6466217B11", 24 | "bridge": "0xFa35d3bEaCE95bfaaDC91EbA202821e952354C54", 25 | "challengeManager": "0xE1F48151c01B2861365bA22A8F1E676ceBD145Dd", 26 | "deployedAtBlockNumber": 71259460, 27 | "inbox": "0x83fac80e23863bAdA976Bddf1aF02B53402e4829", 28 | "nativeToken": "0x0000000000000000000000000000000000000000", 29 | "outbox": "0xC062C937CE2f4f7Abb6998bF958B7D67A4348926", 30 | "rollup": "0x66d0e72952f4f69aF9D33C1B7C31Fa9aCDbCAF63", 31 | "rollupEventInbox": "0xD75c19D04476dF0B6367b064CBA084EC00038f7d", 32 | "sequencerInbox": "0x43528b8Be52e8D084F147d167487f361553463b5", 33 | "upgradeExecutor": "0xcC86ED288eD5DC90Ef9b0e58A12f354840a0B659", 34 | "validatorUtils": "0x7C100c97a54e2D309a194752Df2f66922A802be3", 35 | "validatorWalletCreator": "0xFAd2C6Cb969Ab7B18d78BD63e512b650bb70B570", 36 | } 37 | `; 38 | 39 | exports[`successfully parses core contracts from a tx receipt on RollupCreator v3.1 1`] = ` 40 | { 41 | "adminProxy": "0xf58519b0A94fd7a12C99c30f6FE984EFea1ac1de", 42 | "bridge": "0x633dE45a02bB399fA519B8F91f62F9Ef57ACDE52", 43 | "challengeManager": "0x0938581d28E37EbD23Ef448fd02F8beB206e0383", 44 | "deployedAtBlockNumber": 8131558, 45 | "inbox": "0x05F27c4F65abBe0c59a13A924148Da3eafF91C53", 46 | "nativeToken": "0x0000000000000000000000000000000000000000", 47 | "outbox": "0x5F54ebfC67fa30565b725e709A5712dcD67C2880", 48 | "rollup": "0x6677e09F9475Bb66C320Cd50C5db8Ae75D9E42b7", 49 | "rollupEventInbox": "0x12d28390eB4515e98cd467Cec394BE1a2c79E481", 50 | "sequencerInbox": "0xdddbBeD40a93985DDC1fbeaF8d588804919eF707", 51 | "upgradeExecutor": "0x96Eb6Da3FA34494AfDdc3466ffdc9DFc194008c3", 52 | "validatorWalletCreator": "0x1cB01EA4b15884DEf0e64201C342681B897Db38F", 53 | } 54 | `; 55 | -------------------------------------------------------------------------------- /src/__snapshots__/getDefaultChallengeGracePeriodBlocks.unit.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`returns default value for challengeGracePeriodBlocks based on parent chain 1`] = ` 4 | { 5 | "1": 14400n, 6 | "11155111": 14400n, 7 | "1337": 14400n, 8 | "412346": 14400n, 9 | "42161": 14400n, 10 | "421614": 14400n, 11 | "42170": 14400n, 12 | "8453": 86400n, 13 | "84532": 86400n, 14 | } 15 | `; 16 | -------------------------------------------------------------------------------- /src/__snapshots__/getDefaultConfirmPeriodBlocks.unit.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`returns default value for confirmPeriodBlocks based on parent chain 1`] = ` 4 | { 5 | "1": 50400n, 6 | "11155111": 150n, 7 | "1337": 150n, 8 | "412346": 150n, 9 | "42161": 50400n, 10 | "421614": 150n, 11 | "42170": 50400n, 12 | "8453": 302400n, 13 | "84532": 900n, 14 | } 15 | `; 16 | -------------------------------------------------------------------------------- /src/__snapshots__/getDefaultMinimumAssertionPeriod.unit.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`returns default value for minimumAssertionPeriod based on parent chain 1`] = ` 4 | { 5 | "1": 75n, 6 | "11155111": 75n, 7 | "1337": 75n, 8 | "412346": 75n, 9 | "42161": 75n, 10 | "421614": 75n, 11 | "42170": 75n, 12 | "8453": 450n, 13 | "84532": 450n, 14 | } 15 | `; 16 | -------------------------------------------------------------------------------- /src/__snapshots__/getDefaultSequencerInboxMaxTimeVariation.unit.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`returns default value for sequencerInboxMaxTimeVariation based on parent chain 1`] = ` 4 | { 5 | "1": { 6 | "delayBlocks": 28800n, 7 | "delaySeconds": 345600n, 8 | "futureBlocks": 300n, 9 | "futureSeconds": 3600n, 10 | }, 11 | "11155111": { 12 | "delayBlocks": 28800n, 13 | "delaySeconds": 345600n, 14 | "futureBlocks": 300n, 15 | "futureSeconds": 3600n, 16 | }, 17 | "1337": { 18 | "delayBlocks": 28800n, 19 | "delaySeconds": 345600n, 20 | "futureBlocks": 300n, 21 | "futureSeconds": 3600n, 22 | }, 23 | "412346": { 24 | "delayBlocks": 28800n, 25 | "delaySeconds": 345600n, 26 | "futureBlocks": 300n, 27 | "futureSeconds": 3600n, 28 | }, 29 | "42161": { 30 | "delayBlocks": 28800n, 31 | "delaySeconds": 345600n, 32 | "futureBlocks": 300n, 33 | "futureSeconds": 3600n, 34 | }, 35 | "421614": { 36 | "delayBlocks": 28800n, 37 | "delaySeconds": 345600n, 38 | "futureBlocks": 300n, 39 | "futureSeconds": 3600n, 40 | }, 41 | "42170": { 42 | "delayBlocks": 28800n, 43 | "delaySeconds": 345600n, 44 | "futureBlocks": 300n, 45 | "futureSeconds": 3600n, 46 | }, 47 | "8453": { 48 | "delayBlocks": 172800n, 49 | "delaySeconds": 345600n, 50 | "futureBlocks": 1800n, 51 | "futureSeconds": 3600n, 52 | }, 53 | "84532": { 54 | "delayBlocks": 172800n, 55 | "delaySeconds": 345600n, 56 | "futureBlocks": 1800n, 57 | "futureSeconds": 3600n, 58 | }, 59 | } 60 | `; 61 | -------------------------------------------------------------------------------- /src/__snapshots__/getDefaultValidatorAfkBlocks.unit.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`returns default value for validatorAfkBlocks based on parent chain 1`] = ` 4 | { 5 | "1": 201600n, 6 | "11155111": 201600n, 7 | "1337": 201600n, 8 | "412346": 201600n, 9 | "42161": 201600n, 10 | "421614": 201600n, 11 | "42170": 201600n, 12 | "8453": 1209600n, 13 | "84532": 1209600n, 14 | } 15 | `; 16 | -------------------------------------------------------------------------------- /src/__snapshots__/prepareChainConfig.unit.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`creates chain config with custom params 1`] = ` 4 | { 5 | "arbitrum": { 6 | "AllowDebugPrecompiles": false, 7 | "DataAvailabilityCommittee": true, 8 | "EnableArbOS": true, 9 | "GenesisBlockNum": 0, 10 | "InitialArbOSVersion": 20, 11 | "InitialChainOwner": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", 12 | "MaxCodeSize": 40960, 13 | "MaxInitCodeSize": 81920, 14 | }, 15 | "berlinBlock": 0, 16 | "byzantiumBlock": 0, 17 | "chainId": 69420, 18 | "clique": { 19 | "epoch": 0, 20 | "period": 0, 21 | }, 22 | "constantinopleBlock": 0, 23 | "daoForkBlock": null, 24 | "daoForkSupport": true, 25 | "eip150Block": 0, 26 | "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", 27 | "eip155Block": 0, 28 | "eip158Block": 0, 29 | "homesteadBlock": 0, 30 | "istanbulBlock": 0, 31 | "londonBlock": 0, 32 | "muirGlacierBlock": 0, 33 | "petersburgBlock": 0, 34 | } 35 | `; 36 | 37 | exports[`creates chain config with defaults 1`] = ` 38 | { 39 | "arbitrum": { 40 | "AllowDebugPrecompiles": false, 41 | "DataAvailabilityCommittee": false, 42 | "EnableArbOS": true, 43 | "GenesisBlockNum": 0, 44 | "InitialArbOSVersion": 32, 45 | "InitialChainOwner": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", 46 | "MaxCodeSize": 24576, 47 | "MaxInitCodeSize": 49152, 48 | }, 49 | "berlinBlock": 0, 50 | "byzantiumBlock": 0, 51 | "chainId": 69420, 52 | "clique": { 53 | "epoch": 0, 54 | "period": 0, 55 | }, 56 | "constantinopleBlock": 0, 57 | "daoForkBlock": null, 58 | "daoForkSupport": true, 59 | "eip150Block": 0, 60 | "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", 61 | "eip155Block": 0, 62 | "eip158Block": 0, 63 | "homesteadBlock": 0, 64 | "istanbulBlock": 0, 65 | "londonBlock": 0, 66 | "muirGlacierBlock": 0, 67 | "petersburgBlock": 0, 68 | } 69 | `; 70 | -------------------------------------------------------------------------------- /src/actions/buildInvalidateKeysetHash.ts: -------------------------------------------------------------------------------- 1 | import { Chain, Hex, PrepareTransactionRequestParameters, PublicClient, Transport } from 'viem'; 2 | import { sequencerInboxABI } from '../contracts/SequencerInbox'; 3 | import { 4 | ActionParameters, 5 | PrepareTransactionRequestReturnTypeWithChainId, 6 | WithAccount, 7 | WithUpgradeExecutor, 8 | } from '../types/Actions'; 9 | import { Prettify } from '../types/utils'; 10 | import { validateParentChain } from '../types/ParentChain'; 11 | import { prepareUpgradeExecutorCallParameters } from '../prepareUpgradeExecutorCallParameters'; 12 | 13 | export type BuildInvalidateKeysetHashParameters = Prettify< 14 | WithUpgradeExecutor< 15 | WithAccount< 16 | ActionParameters< 17 | { 18 | keysetHash: Hex; 19 | }, 20 | 'sequencerInbox', 21 | Curried 22 | > 23 | > 24 | > 25 | >; 26 | 27 | export type BuildInvalidateKeysetHashReturnType = PrepareTransactionRequestReturnTypeWithChainId; 28 | 29 | export async function buildInvalidateKeysetHash( 30 | client: PublicClient, 31 | { 32 | account, 33 | upgradeExecutor, 34 | sequencerInbox: sequencerInboxAddress, 35 | params, 36 | }: BuildInvalidateKeysetHashParameters, 37 | ): Promise { 38 | const { chainId } = validateParentChain(client); 39 | 40 | const request = await client.prepareTransactionRequest({ 41 | chain: client.chain, 42 | account, 43 | ...prepareUpgradeExecutorCallParameters({ 44 | to: sequencerInboxAddress, 45 | upgradeExecutor, 46 | args: [params.keysetHash], 47 | abi: sequencerInboxABI, 48 | functionName: 'invalidateKeysetHash', 49 | }), 50 | } satisfies PrepareTransactionRequestParameters); 51 | 52 | return { ...request, chainId }; 53 | } 54 | -------------------------------------------------------------------------------- /src/actions/buildSetIsBatchPoster.ts: -------------------------------------------------------------------------------- 1 | import { Address, Chain, PrepareTransactionRequestParameters, PublicClient, Transport } from 'viem'; 2 | import { sequencerInboxABI } from '../contracts/SequencerInbox'; 3 | import { 4 | ActionParameters, 5 | PrepareTransactionRequestReturnTypeWithChainId, 6 | WithAccount, 7 | WithUpgradeExecutor, 8 | } from '../types/Actions'; 9 | import { Prettify } from '../types/utils'; 10 | import { prepareUpgradeExecutorCallParameters } from '../prepareUpgradeExecutorCallParameters'; 11 | import { validateParentChain } from '../types/ParentChain'; 12 | 13 | type Args = { 14 | batchPoster: Address; 15 | }; 16 | 17 | export type BuildSetIsBatchPosterParameters = Prettify< 18 | WithUpgradeExecutor>> 19 | >; 20 | 21 | export type BuildSetIsBatchPosterReturnType = PrepareTransactionRequestReturnTypeWithChainId; 22 | 23 | export async function buildSetIsBatchPoster( 24 | client: PublicClient, 25 | { 26 | account, 27 | upgradeExecutor, 28 | sequencerInbox: sequencerInboxAddress, 29 | params, 30 | }: BuildSetIsBatchPosterParameters & { params: { enable: boolean } }, 31 | ): Promise { 32 | const { chainId } = validateParentChain(client); 33 | 34 | const request = await client.prepareTransactionRequest({ 35 | chain: client.chain, 36 | account, 37 | ...prepareUpgradeExecutorCallParameters({ 38 | to: sequencerInboxAddress, 39 | upgradeExecutor, 40 | args: [params.batchPoster, params.enable], 41 | abi: sequencerInboxABI, 42 | functionName: 'setIsBatchPoster', 43 | }), 44 | } satisfies PrepareTransactionRequestParameters); 45 | 46 | return { ...request, chainId }; 47 | } 48 | 49 | export async function buildEnableBatchPoster( 50 | client: PublicClient, 51 | args: BuildSetIsBatchPosterParameters, 52 | ): Promise { 53 | return buildSetIsBatchPoster(client, { 54 | ...args, 55 | params: { 56 | ...args.params, 57 | enable: true, 58 | }, 59 | }); 60 | } 61 | 62 | export async function buildDisableBatchPoster( 63 | client: PublicClient, 64 | args: BuildSetIsBatchPosterParameters, 65 | ): Promise { 66 | return buildSetIsBatchPoster(client, { 67 | ...args, 68 | params: { 69 | ...args.params, 70 | enable: false, 71 | }, 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /src/actions/buildSetMaxTimeVariation.ts: -------------------------------------------------------------------------------- 1 | import { Chain, PrepareTransactionRequestParameters, PublicClient, Transport } from 'viem'; 2 | import { sequencerInboxABI } from '../contracts/SequencerInbox'; 3 | import { 4 | ActionParameters, 5 | PrepareTransactionRequestReturnTypeWithChainId, 6 | WithAccount, 7 | WithUpgradeExecutor, 8 | } from '../types/Actions'; 9 | import { Prettify } from '../types/utils'; 10 | import { prepareUpgradeExecutorCallParameters } from '../prepareUpgradeExecutorCallParameters'; 11 | import { validateParentChain } from '../types/ParentChain'; 12 | 13 | type Args = { 14 | delayBlocks: bigint; 15 | futureBlocks: bigint; 16 | delaySeconds: bigint; 17 | futureSeconds: bigint; 18 | }; 19 | export type BuildSetMaxTimeVariationParameters = Prettify< 20 | WithUpgradeExecutor>> 21 | >; 22 | 23 | export type BuildSetMaxTimeVariationReturnType = PrepareTransactionRequestReturnTypeWithChainId; 24 | 25 | export async function buildSetMaxTimeVariation( 26 | client: PublicClient, 27 | { 28 | account, 29 | upgradeExecutor, 30 | sequencerInbox: sequencerInboxAddress, 31 | params, 32 | }: BuildSetMaxTimeVariationParameters, 33 | ): Promise { 34 | const { chainId } = validateParentChain(client); 35 | 36 | const request = await client.prepareTransactionRequest({ 37 | chain: client.chain, 38 | account, 39 | ...prepareUpgradeExecutorCallParameters({ 40 | to: sequencerInboxAddress, 41 | upgradeExecutor, 42 | args: [params], 43 | abi: sequencerInboxABI, 44 | functionName: 'setMaxTimeVariation', 45 | }), 46 | } satisfies PrepareTransactionRequestParameters); 47 | 48 | return { ...request, chainId }; 49 | } 50 | -------------------------------------------------------------------------------- /src/actions/buildSetValidKeyset.ts: -------------------------------------------------------------------------------- 1 | import { Chain, Hex, PrepareTransactionRequestParameters, PublicClient, Transport } from 'viem'; 2 | import { sequencerInboxABI } from '../contracts/SequencerInbox'; 3 | import { 4 | ActionParameters, 5 | PrepareTransactionRequestReturnTypeWithChainId, 6 | WithAccount, 7 | WithUpgradeExecutor, 8 | } from '../types/Actions'; 9 | import { Prettify } from '../types/utils'; 10 | import { validateParentChain } from '../types/ParentChain'; 11 | import { prepareUpgradeExecutorCallParameters } from '../prepareUpgradeExecutorCallParameters'; 12 | 13 | export type BuildSetValidKeysetParameters = Prettify< 14 | WithUpgradeExecutor< 15 | WithAccount< 16 | ActionParameters< 17 | { 18 | keyset: Hex; 19 | }, 20 | 'sequencerInbox', 21 | Curried 22 | > 23 | > 24 | > 25 | >; 26 | 27 | export type BuildSetValidKeysetReturnType = PrepareTransactionRequestReturnTypeWithChainId; 28 | 29 | export async function buildSetValidKeyset( 30 | client: PublicClient, 31 | { 32 | account, 33 | upgradeExecutor, 34 | sequencerInbox: sequencerInboxAddress, 35 | params, 36 | }: BuildSetValidKeysetParameters, 37 | ): Promise { 38 | const { chainId } = validateParentChain(client); 39 | 40 | const request = await client.prepareTransactionRequest({ 41 | chain: client.chain, 42 | account, 43 | ...prepareUpgradeExecutorCallParameters({ 44 | to: sequencerInboxAddress, 45 | upgradeExecutor, 46 | args: [params.keyset], 47 | abi: sequencerInboxABI, 48 | functionName: 'setValidKeyset', 49 | }), 50 | } satisfies PrepareTransactionRequestParameters); 51 | 52 | return { ...request, chainId }; 53 | } 54 | -------------------------------------------------------------------------------- /src/actions/getMaxTimeVariation.ts: -------------------------------------------------------------------------------- 1 | import { Chain, PublicClient, Transport } from 'viem'; 2 | import { sequencerInboxABI } from '../contracts/SequencerInbox'; 3 | import { ActionParameters } from '../types/Actions'; 4 | 5 | export type GetMaxTimeVariationParameters = ActionParameters< 6 | {}, 7 | 'sequencerInbox', 8 | Curried 9 | >; 10 | 11 | export type GetMaxTimeVariationReturnType = { 12 | delayBlocks: bigint; 13 | futureBlocks: bigint; 14 | delaySeconds: bigint; 15 | futureSeconds: bigint; 16 | }; 17 | 18 | export async function getMaxTimeVariation( 19 | client: PublicClient, 20 | { sequencerInbox }: GetMaxTimeVariationParameters, 21 | ): Promise { 22 | const [delayBlocks, futureBlocks, delaySeconds, futureSeconds] = await client.readContract({ 23 | abi: sequencerInboxABI, 24 | functionName: 'maxTimeVariation', 25 | address: sequencerInbox, 26 | }); 27 | return { 28 | delayBlocks, 29 | futureBlocks, 30 | delaySeconds, 31 | futureSeconds, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getMaxTimeVariation'; 2 | export * from './isBatchPoster'; 3 | export * from './isValidKeysetHash'; 4 | export * from './buildInvalidateKeysetHash'; 5 | export * from './buildSetIsBatchPoster'; 6 | export * from './buildSetValidKeyset'; 7 | export * from './buildSetMaxTimeVariation'; 8 | -------------------------------------------------------------------------------- /src/actions/isBatchPoster.ts: -------------------------------------------------------------------------------- 1 | import { Address, Chain, PublicClient, ReadContractReturnType, Transport } from 'viem'; 2 | import { sequencerInboxABI } from '../contracts/SequencerInbox'; 3 | import { ActionParameters } from '../types/Actions'; 4 | 5 | type Args = { 6 | batchPoster: Address; 7 | }; 8 | export type IsBatchPosterParameters = ActionParameters< 9 | Args, 10 | 'sequencerInbox', 11 | Curried 12 | >; 13 | 14 | export type IsBatchPosterReturnType = ReadContractReturnType< 15 | typeof sequencerInboxABI, 16 | 'isBatchPoster' 17 | >; 18 | 19 | export async function isBatchPoster( 20 | client: PublicClient, 21 | { sequencerInbox, params }: IsBatchPosterParameters, 22 | ): Promise { 23 | return client.readContract({ 24 | abi: sequencerInboxABI, 25 | functionName: 'isBatchPoster', 26 | address: sequencerInbox, 27 | args: [params.batchPoster], 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/actions/isValidKeysetHash.ts: -------------------------------------------------------------------------------- 1 | import { Chain, Hex, PublicClient, ReadContractReturnType, Transport } from 'viem'; 2 | import { sequencerInboxABI } from '../contracts/SequencerInbox'; 3 | import { ActionParameters } from '../types/Actions'; 4 | 5 | type Args = { 6 | keysetHash: Hex; 7 | }; 8 | 9 | export type IsValidKeysetHashParameters = ActionParameters< 10 | Args, 11 | 'sequencerInbox', 12 | Curried 13 | >; 14 | 15 | export type IsValidKeysetHashReturnType = ReadContractReturnType< 16 | typeof sequencerInboxABI, 17 | 'isValidKeysetHash' 18 | >; 19 | 20 | export async function isValidKeysetHash( 21 | client: PublicClient, 22 | { sequencerInbox, params }: IsValidKeysetHashParameters, 23 | ): Promise { 24 | return client.readContract({ 25 | abi: sequencerInboxABI, 26 | functionName: 'isValidKeysetHash', 27 | address: sequencerInbox, 28 | args: [params.keysetHash], 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/arbAggregatorReadContract.ts: -------------------------------------------------------------------------------- 1 | import { Chain, GetFunctionArgs, PublicClient, ReadContractReturnType, Transport } from 'viem'; 2 | 3 | import { arbAggregatorABI, arbAggregatorAddress } from './contracts/ArbAggregator'; 4 | import { GetFunctionName } from './types/utils'; 5 | 6 | export type ArbAggregatorAbi = typeof arbAggregatorABI; 7 | export type ArbAggregatorFunctionName = GetFunctionName; 8 | 9 | export type ArbAggregatorReadContractParameters = { 10 | functionName: TFunctionName; 11 | } & GetFunctionArgs; 12 | 13 | export type ArbAggregatorReadContractReturnType = 14 | ReadContractReturnType; 15 | 16 | export function arbAggregatorReadContract< 17 | TChain extends Chain | undefined, 18 | TFunctionName extends ArbAggregatorFunctionName, 19 | >( 20 | client: PublicClient, 21 | params: ArbAggregatorReadContractParameters, 22 | ): Promise> { 23 | // @ts-ignore (todo: fix viem type issue) 24 | return client.readContract({ 25 | address: arbAggregatorAddress, 26 | abi: arbAggregatorABI, 27 | functionName: params.functionName, 28 | args: params.args, 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/arbGasInfoReadContract.ts: -------------------------------------------------------------------------------- 1 | import { Chain, GetFunctionArgs, PublicClient, ReadContractReturnType, Transport } from 'viem'; 2 | 3 | import { arbGasInfoABI, arbGasInfoAddress } from './contracts/ArbGasInfo'; 4 | import { GetFunctionName } from './types/utils'; 5 | 6 | export type ArbGasInfoAbi = typeof arbGasInfoABI; 7 | export type ArbGasInfoFunctionName = GetFunctionName; 8 | 9 | export type ArbGasInfoReadContractParameters = { 10 | functionName: TFunctionName; 11 | } & GetFunctionArgs; 12 | 13 | export type ArbGasInfoReadContractReturnType = 14 | ReadContractReturnType; 15 | 16 | export function arbGasInfoReadContract< 17 | TChain extends Chain | undefined, 18 | TFunctionName extends ArbGasInfoFunctionName, 19 | >( 20 | client: PublicClient, 21 | params: ArbGasInfoReadContractParameters, 22 | ): Promise> { 23 | // @ts-ignore (todo: fix viem type issue) 24 | return client.readContract({ 25 | address: arbGasInfoAddress, 26 | abi: arbGasInfoABI, 27 | functionName: params.functionName, 28 | args: params.args, 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/arbOwnerReadContract.ts: -------------------------------------------------------------------------------- 1 | import { Chain, GetFunctionArgs, PublicClient, ReadContractReturnType, Transport } from 'viem'; 2 | 3 | import { arbOwnerPublicABI, arbOwnerPublicAddress } from './contracts/ArbOwnerPublic'; 4 | import { GetFunctionName } from './types/utils'; 5 | 6 | export type ArbOwnerPublicAbi = typeof arbOwnerPublicABI; 7 | export type ArbOwnerPublicFunctionName = GetFunctionName; 8 | 9 | export type ArbOwnerReadContractParameters = { 10 | functionName: TFunctionName; 11 | } & GetFunctionArgs; 12 | 13 | export type ArbOwnerReadContractReturnType = 14 | ReadContractReturnType; 15 | 16 | export function arbOwnerReadContract< 17 | TChain extends Chain | undefined, 18 | TFunctionName extends ArbOwnerPublicFunctionName, 19 | >( 20 | client: PublicClient, 21 | params: ArbOwnerReadContractParameters, 22 | ): Promise> { 23 | // @ts-ignore (todo: fix viem type issue) 24 | return client.readContract({ 25 | address: arbOwnerPublicAddress, 26 | abi: arbOwnerPublicABI, 27 | functionName: params.functionName, 28 | args: params.args, 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/chains.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { getCustomParentChains, registerCustomParentChain } from './chains'; 4 | import { testHelper_createCustomParentChain } from './testHelpers'; 5 | 6 | describe('registerCustomParentChain', () => { 7 | it(`throws if "contracts.rollupCreator.address" is invalid`, () => { 8 | // omit contracts from the chain 9 | const { contracts, ...chain } = testHelper_createCustomParentChain(); 10 | 11 | expect(() => 12 | registerCustomParentChain({ 13 | ...chain, 14 | contracts: { 15 | rollupCreator: { 16 | address: '0x123', 17 | }, 18 | tokenBridgeCreator: { 19 | address: '0x123', 20 | }, 21 | }, 22 | }), 23 | ).toThrowError( 24 | `"contracts.rollupCreator.address" is invalid for custom parent chain with id ${chain.id}`, 25 | ); 26 | }); 27 | 28 | it(`throws if "contracts.tokenBridgeCreator.address" is invalid`, () => { 29 | // omit contracts from the chain 30 | const { contracts, ...chain } = testHelper_createCustomParentChain(); 31 | 32 | expect(() => 33 | registerCustomParentChain({ 34 | ...chain, 35 | contracts: { 36 | rollupCreator: { 37 | // use a correct address for the RollupCreator 38 | address: contracts.rollupCreator.address, 39 | }, 40 | tokenBridgeCreator: { 41 | address: '0x0', 42 | }, 43 | }, 44 | }), 45 | ).toThrowError( 46 | `"contracts.tokenBridgeCreator.address" is invalid for custom parent chain with id ${chain.id}`, 47 | ); 48 | }); 49 | 50 | it('successfully registers a custom parent chain', () => { 51 | const chain = testHelper_createCustomParentChain(); 52 | 53 | // assert before 54 | expect(getCustomParentChains().map((c) => c.id)).not.includes(chain.id); 55 | 56 | // register 57 | registerCustomParentChain(chain); 58 | 59 | // assert after 60 | expect(getCustomParentChains().map((c) => c.id)).includes(chain.id); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { parseEther } from 'viem'; 2 | 3 | /** 4 | * Approximate value necessary to pay for retryables fees for `createRollup`. 5 | */ 6 | export const createRollupDefaultRetryablesFees = parseEther('0.125'); 7 | 8 | /** 9 | * Approximate value necessary to pay for retryables fees for `createTokenBridge`. 10 | */ 11 | export const createTokenBridgeDefaultRetryablesFees = parseEther('0.02'); 12 | -------------------------------------------------------------------------------- /src/contracts/ArbAggregator.ts: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // ArbAggregator 3 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | 5 | /** 6 | * [__View Contract on Arbitrum Sepolia Blockscout__](https://sepolia-explorer.arbitrum.io/address/0x000000000000000000000000000000000000006d) 7 | */ 8 | export const arbAggregatorABI = [ 9 | { 10 | stateMutability: 'nonpayable', 11 | type: 'function', 12 | inputs: [{ name: 'newBatchPoster', internalType: 'address', type: 'address' }], 13 | name: 'addBatchPoster', 14 | outputs: [], 15 | }, 16 | { 17 | stateMutability: 'view', 18 | type: 'function', 19 | inputs: [], 20 | name: 'getBatchPosters', 21 | outputs: [{ name: '', internalType: 'address[]', type: 'address[]' }], 22 | }, 23 | { 24 | stateMutability: 'view', 25 | type: 'function', 26 | inputs: [], 27 | name: 'getDefaultAggregator', 28 | outputs: [{ name: '', internalType: 'address', type: 'address' }], 29 | }, 30 | { 31 | stateMutability: 'view', 32 | type: 'function', 33 | inputs: [{ name: 'batchPoster', internalType: 'address', type: 'address' }], 34 | name: 'getFeeCollector', 35 | outputs: [{ name: '', internalType: 'address', type: 'address' }], 36 | }, 37 | { 38 | stateMutability: 'view', 39 | type: 'function', 40 | inputs: [{ name: 'addr', internalType: 'address', type: 'address' }], 41 | name: 'getPreferredAggregator', 42 | outputs: [ 43 | { name: '', internalType: 'address', type: 'address' }, 44 | { name: '', internalType: 'bool', type: 'bool' }, 45 | ], 46 | }, 47 | { 48 | stateMutability: 'view', 49 | type: 'function', 50 | inputs: [{ name: 'aggregator', internalType: 'address', type: 'address' }], 51 | name: 'getTxBaseFee', 52 | outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], 53 | }, 54 | { 55 | stateMutability: 'nonpayable', 56 | type: 'function', 57 | inputs: [ 58 | { name: 'batchPoster', internalType: 'address', type: 'address' }, 59 | { name: 'newFeeCollector', internalType: 'address', type: 'address' }, 60 | ], 61 | name: 'setFeeCollector', 62 | outputs: [], 63 | }, 64 | { 65 | stateMutability: 'nonpayable', 66 | type: 'function', 67 | inputs: [ 68 | { name: 'aggregator', internalType: 'address', type: 'address' }, 69 | { name: 'feeInL1Gas', internalType: 'uint256', type: 'uint256' }, 70 | ], 71 | name: 'setTxBaseFee', 72 | outputs: [], 73 | }, 74 | ] as const; 75 | 76 | /** 77 | * [__View Contract on Arbitrum Sepolia Blockscout__](https://sepolia-explorer.arbitrum.io/address/0x000000000000000000000000000000000000006d) 78 | */ 79 | export const arbAggregatorAddress = '0x000000000000000000000000000000000000006D'; 80 | 81 | /** 82 | * [__View Contract on Arbitrum Sepolia Blockscout__](https://sepolia-explorer.arbitrum.io/address/0x000000000000000000000000000000000000006d) 83 | */ 84 | export const arbAggregatorConfig = { 85 | address: arbAggregatorAddress, 86 | abi: arbAggregatorABI, 87 | } as const; 88 | -------------------------------------------------------------------------------- /src/contracts/ArbOwnerPublic.ts: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // ArbOwnerPublic 3 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | 5 | /** 6 | * [__View Contract on Arbitrum Sepolia Blockscout__](https://sepolia-explorer.arbitrum.io/address/0x000000000000000000000000000000000000006b) 7 | */ 8 | export const arbOwnerPublicABI = [ 9 | { 10 | type: 'event', 11 | anonymous: false, 12 | inputs: [{ name: 'rectifiedOwner', internalType: 'address', type: 'address', indexed: false }], 13 | name: 'ChainOwnerRectified', 14 | }, 15 | { 16 | stateMutability: 'view', 17 | type: 'function', 18 | inputs: [], 19 | name: 'getAllChainOwners', 20 | outputs: [{ name: '', internalType: 'address[]', type: 'address[]' }], 21 | }, 22 | { 23 | stateMutability: 'view', 24 | type: 'function', 25 | inputs: [], 26 | name: 'getBrotliCompressionLevel', 27 | outputs: [{ name: '', internalType: 'uint64', type: 'uint64' }], 28 | }, 29 | { 30 | stateMutability: 'view', 31 | type: 'function', 32 | inputs: [], 33 | name: 'getInfraFeeAccount', 34 | outputs: [{ name: '', internalType: 'address', type: 'address' }], 35 | }, 36 | { 37 | stateMutability: 'view', 38 | type: 'function', 39 | inputs: [], 40 | name: 'getNetworkFeeAccount', 41 | outputs: [{ name: '', internalType: 'address', type: 'address' }], 42 | }, 43 | { 44 | stateMutability: 'view', 45 | type: 'function', 46 | inputs: [], 47 | name: 'getScheduledUpgrade', 48 | outputs: [ 49 | { name: 'arbosVersion', internalType: 'uint64', type: 'uint64' }, 50 | { name: 'scheduledForTimestamp', internalType: 'uint64', type: 'uint64' }, 51 | ], 52 | }, 53 | { 54 | stateMutability: 'view', 55 | type: 'function', 56 | inputs: [{ name: 'addr', internalType: 'address', type: 'address' }], 57 | name: 'isChainOwner', 58 | outputs: [{ name: '', internalType: 'bool', type: 'bool' }], 59 | }, 60 | { 61 | stateMutability: 'nonpayable', 62 | type: 'function', 63 | inputs: [{ name: 'ownerToRectify', internalType: 'address', type: 'address' }], 64 | name: 'rectifyChainOwner', 65 | outputs: [], 66 | }, 67 | ] as const; 68 | 69 | /** 70 | * [__View Contract on Arbitrum Sepolia Blockscout__](https://sepolia-explorer.arbitrum.io/address/0x000000000000000000000000000000000000006b) 71 | */ 72 | export const arbOwnerPublicAddress = '0x000000000000000000000000000000000000006b'; 73 | 74 | /** 75 | * [__View Contract on Arbitrum Sepolia Blockscout__](https://sepolia-explorer.arbitrum.io/address/0x000000000000000000000000000000000000006b) 76 | */ 77 | export const arbOwnerPublicConfig = { 78 | address: arbOwnerPublicAddress, 79 | abi: arbOwnerPublicABI, 80 | } as const; 81 | -------------------------------------------------------------------------------- /src/contracts/Rollup/index.ts: -------------------------------------------------------------------------------- 1 | // export the latest version 2 | export * from './v3.1'; 3 | -------------------------------------------------------------------------------- /src/contracts/RollupCreator/index.ts: -------------------------------------------------------------------------------- 1 | // export the latest version 2 | export * from './v3.1'; 3 | -------------------------------------------------------------------------------- /src/contracts/SequencerInbox/index.ts: -------------------------------------------------------------------------------- 1 | // export the latest version 2 | export * from './v3.1'; 3 | -------------------------------------------------------------------------------- /src/contracts/TokenBridgeCreator/index.ts: -------------------------------------------------------------------------------- 1 | // export the latest version 2 | export * from './v1.2'; 3 | -------------------------------------------------------------------------------- /src/contracts/UpgradeExecutor.ts: -------------------------------------------------------------------------------- 1 | import { parseAbi } from 'viem'; 2 | 3 | export const upgradeExecutorABI = parseAbi([ 4 | 'function execute(address upgrade, bytes upgradeCallData)', 5 | 'function executeCall(address target, bytes targetCallData)', 6 | 'function hasRole(bytes32 role, address account) public view returns (bool)', 7 | 'function grantRole(bytes32 role, address account)', 8 | 'function revokeRole(bytes32 role, address account)', 9 | ]); 10 | -------------------------------------------------------------------------------- /src/contracts/WETH.ts: -------------------------------------------------------------------------------- 1 | // Sources: 2 | // * https://docs.arbitrum.io/build-decentralized-apps/reference/contract-addresses#core-contracts-1 3 | // * https://github.com/OffchainLabs/arbitrum-token-bridge/blob/master/packages/arb-token-bridge-ui/src/util/networksNitroTestnode.ts 4 | // * https://docs.base.org/chain/base-contracts#base-mainnet 5 | // * https://docs.base.org/chain/base-contracts#base-testnet-sepolia 6 | export const wethAddress = { 7 | 1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 8 | 1337: '0x217788c286797D56Cd59aF5e493f3699C39cbbe8', 9 | 8453: '0x4200000000000000000000000000000000000006', 10 | 42161: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', 11 | 42170: '0x722E8BdD2ce80A4422E880164f2079488e115365', 12 | 84532: '0x4200000000000000000000000000000000000006', 13 | 412346: '0xF5fE98ee962e3E077A75FBe6fE8aBaeF80F3c12d', 14 | 421614: '0x980B62Da83eFf3D4576C647993b0c1D7faf17c73', 15 | 11155111: '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9', 16 | } as const; 17 | 18 | // nitro testnode l1 and l2 are based on https://github.com/OffchainLabs/nitro-testnode/pull/117 19 | -------------------------------------------------------------------------------- /src/createRollupDefaults.ts: -------------------------------------------------------------------------------- 1 | import { zeroAddress, parseGwei } from 'viem'; 2 | 3 | export const defaults = { 4 | nativeToken: zeroAddress, 5 | deployFactoriesToL2: true, 6 | maxFeePerGasForRetryables: parseGwei(String('0.1')), 7 | batchPosterManager: zeroAddress, 8 | feeTokenPricer: zeroAddress, 9 | }; 10 | -------------------------------------------------------------------------------- /src/createRollupEnoughCustomFeeTokenAllowance.ts: -------------------------------------------------------------------------------- 1 | import { Address, PublicClient, Transport, Chain } from 'viem'; 2 | 3 | import { fetchAllowance, fetchDecimals } from './utils/erc20'; 4 | import { getRollupCreatorAddress } from './utils/getRollupCreatorAddress'; 5 | 6 | import { Prettify } from './types/utils'; 7 | import { WithRollupCreatorAddressOverride } from './types/createRollupTypes'; 8 | import { createRollupGetRetryablesFeesWithDefaults } from './createRollupGetRetryablesFees'; 9 | import { scaleFrom18DecimalsToNativeTokenDecimals } from './utils/decimals'; 10 | 11 | export type CreateRollupEnoughCustomFeeTokenAllowanceParams = 12 | Prettify< 13 | WithRollupCreatorAddressOverride<{ 14 | nativeToken: Address; 15 | maxFeePerGasForRetryables?: bigint; 16 | account: Address; 17 | publicClient: PublicClient; 18 | }> 19 | >; 20 | 21 | export async function createRollupEnoughCustomFeeTokenAllowance({ 22 | nativeToken, 23 | maxFeePerGasForRetryables, 24 | account, 25 | publicClient, 26 | rollupCreatorAddressOverride, 27 | }: CreateRollupEnoughCustomFeeTokenAllowanceParams) { 28 | const allowance = await fetchAllowance({ 29 | address: nativeToken, 30 | owner: account, 31 | spender: rollupCreatorAddressOverride ?? getRollupCreatorAddress(publicClient), 32 | publicClient, 33 | }); 34 | 35 | const fees = await createRollupGetRetryablesFeesWithDefaults(publicClient, { 36 | account, 37 | nativeToken, 38 | maxFeePerGasForRetryables, 39 | }); 40 | 41 | const decimals = await fetchDecimals({ 42 | address: nativeToken, 43 | publicClient, 44 | }); 45 | 46 | return allowance >= scaleFrom18DecimalsToNativeTokenDecimals({ amount: fees, decimals }); 47 | } 48 | -------------------------------------------------------------------------------- /src/createRollupFetchCoreContracts.ts: -------------------------------------------------------------------------------- 1 | import { Address, PublicClient, Chain, Transport } from 'viem'; 2 | 3 | import { CoreContracts } from './types/CoreContracts'; 4 | import { createRollupFetchTransactionHash } from './createRollupFetchTransactionHash'; 5 | import { createRollupPrepareTransactionReceipt } from './createRollupPrepareTransactionReceipt'; 6 | 7 | export type CreateRollupFetchCoreContractsParams = { 8 | /** 9 | * Address of the Rollup contract. 10 | */ 11 | rollup: Address; 12 | /** 13 | * Number of the block in which the Rollup contract was deployed. 14 | * 15 | * This parameter is used to reduce the span of blocks to query, so it doesn't have to be exactly the right block number. 16 | * However, for the query to work properly, it has to be **less than or equal to** the right block number. 17 | */ 18 | rollupDeploymentBlockNumber?: bigint; 19 | publicClient: PublicClient; 20 | }; 21 | 22 | export async function createRollupFetchCoreContracts({ 23 | rollup, 24 | rollupDeploymentBlockNumber, 25 | publicClient, 26 | }: CreateRollupFetchCoreContractsParams): Promise { 27 | // getting core contract addresses 28 | const transactionHash = await createRollupFetchTransactionHash({ 29 | rollup, 30 | publicClient, 31 | fromBlock: rollupDeploymentBlockNumber, 32 | }); 33 | 34 | const transactionReceipt = createRollupPrepareTransactionReceipt( 35 | await publicClient.waitForTransactionReceipt({ 36 | hash: transactionHash, 37 | }), 38 | ); 39 | 40 | return transactionReceipt.getCoreContracts(); 41 | } 42 | -------------------------------------------------------------------------------- /src/createRollupFetchTransactionHash.ts: -------------------------------------------------------------------------------- 1 | import { Address, PublicClient, Transport, Chain, GetLogsReturnType } from 'viem'; 2 | import { AbiEvent } from 'abitype'; 3 | 4 | import { getLogsWithBatching } from './utils/getLogsWithBatching'; 5 | import { getEarliestRollupCreatorDeploymentBlockNumber } from './utils/getEarliestRollupCreatorDeploymentBlockNumber'; 6 | 7 | export type CreateRollupFetchTransactionHashParams = { 8 | rollup: Address; 9 | publicClient: PublicClient; 10 | fromBlock?: bigint; 11 | }; 12 | 13 | const RollupInitializedEventAbi = { 14 | anonymous: false, 15 | inputs: [ 16 | { 17 | indexed: false, 18 | internalType: 'bytes32', 19 | name: 'machineHash', 20 | type: 'bytes32', 21 | }, 22 | { 23 | indexed: false, 24 | internalType: 'uint256', 25 | name: 'chainId', 26 | type: 'uint256', 27 | }, 28 | ], 29 | name: 'RollupInitialized', 30 | type: 'event', 31 | } as const satisfies AbiEvent; 32 | 33 | export async function getRollupInitializedEvents({ 34 | rollup, 35 | publicClient, 36 | fromBlock, 37 | }: CreateRollupFetchTransactionHashParams) { 38 | // Find the RollupInitialized event from that Rollup contract 39 | const rollupInitializedEvents = await getLogsWithBatching(publicClient, { 40 | address: rollup, 41 | event: RollupInitializedEventAbi, 42 | fromBlock: fromBlock ?? getEarliestRollupCreatorDeploymentBlockNumber(publicClient), 43 | }); 44 | 45 | if (rollupInitializedEvents.length !== 1) { 46 | throw new Error( 47 | `Expected to find 1 RollupInitialized event for rollup address ${rollup} but found ${rollupInitializedEvents.length}`, 48 | ); 49 | } 50 | 51 | return rollupInitializedEvents; 52 | } 53 | 54 | export async function createRollupFetchTransactionHash({ 55 | rollup, 56 | publicClient, 57 | fromBlock, 58 | }: CreateRollupFetchTransactionHashParams) { 59 | // Get the transaction hash that emitted that event 60 | const transactionHash = (await getRollupInitializedEvents({ rollup, publicClient, fromBlock }))[0] 61 | .transactionHash; 62 | 63 | if (!transactionHash) { 64 | throw new Error( 65 | `No transactionHash found in RollupInitialized event for rollup address ${rollup}`, 66 | ); 67 | } 68 | 69 | return transactionHash; 70 | } 71 | -------------------------------------------------------------------------------- /src/createRollupGetCallValue.ts: -------------------------------------------------------------------------------- 1 | import { Chain, PublicClient, Transport, Address } from 'viem'; 2 | 3 | import { CreateRollupParams } from './types/createRollupTypes'; 4 | import { createRollupGetRetryablesFeesWithDefaults } from './createRollupGetRetryablesFees'; 5 | 6 | import { isNonZeroAddress } from './utils/isNonZeroAddress'; 7 | 8 | export async function createRollupGetCallValue( 9 | publicClient: PublicClient, 10 | params: CreateRollupParams & { account: Address }, 11 | ): Promise { 12 | // when not deploying deterministic factories to L2, no callvalue is necessary, as no retryable tickets will be created 13 | if (!params.deployFactoriesToL2) { 14 | return BigInt(0); 15 | } 16 | 17 | // when using a custom fee token, the retryable tickets will be paid for in the custom fee token, so no callvalue is necessary 18 | if (isNonZeroAddress(params.nativeToken)) { 19 | return BigInt(0); 20 | } 21 | 22 | return createRollupGetRetryablesFeesWithDefaults(publicClient, params); 23 | } 24 | -------------------------------------------------------------------------------- /src/createRollupGetMaxDataSize.ts: -------------------------------------------------------------------------------- 1 | import { 2 | mainnet, 3 | arbitrumOne, 4 | arbitrumNova, 5 | base, 6 | sepolia, 7 | arbitrumSepolia, 8 | baseSepolia, 9 | nitroTestnodeL1, 10 | nitroTestnodeL2, 11 | } from './chains'; 12 | import { ParentChainId } from './types/ParentChain'; 13 | 14 | export function createRollupGetMaxDataSize(parentChainId: ParentChainId): bigint { 15 | // doing switch here to make sure it's exhaustive when checking against `ParentChainId` 16 | switch (parentChainId) { 17 | // mainnet L1 18 | case mainnet.id: 19 | // testnet L1 20 | case sepolia.id: 21 | // local nitro-testnode L1 22 | case nitroTestnodeL1.id: 23 | return BigInt(117_964); 24 | 25 | // mainnet L2 26 | case arbitrumOne.id: 27 | case arbitrumNova.id: 28 | case base.id: 29 | // testnet L2 30 | case arbitrumSepolia.id: 31 | case baseSepolia.id: 32 | // local nitro-testnode L2 33 | case nitroTestnodeL2.id: 34 | return BigInt(104_857); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/createRollupGetRetryablesFees.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | import { createPublicClient, http, parseGwei } from 'viem'; 3 | import { sepolia } from 'viem/chains'; 4 | 5 | import { createRollupGetRetryablesFees } from './createRollupGetRetryablesFees'; 6 | 7 | const sepoliaClient = createPublicClient({ 8 | chain: sepolia, 9 | transport: http('https://ethereum-sepolia-rpc.publicnode.com'), 10 | }); 11 | 12 | it('successfully fetches retryable fees for an eth-based chain', async () => { 13 | const fees = await createRollupGetRetryablesFees(sepoliaClient, { 14 | account: '0x38f918D0E9F1b721EDaA41302E399fa1B79333a9', 15 | maxFeePerGasForRetryables: parseGwei('0.1'), 16 | }); 17 | 18 | expect(fees).toBeTypeOf('bigint'); 19 | expect(fees).toBeGreaterThanOrEqual(124708400000000000n); 20 | }); 21 | 22 | it('successfully fetches retryable fees for a custom gas token chain', async () => { 23 | const fees = await createRollupGetRetryablesFees(sepoliaClient, { 24 | account: '0x38f918D0E9F1b721EDaA41302E399fa1B79333a9', 25 | nativeToken: '0x0625afb445c3b6b7b929342a04a22599fd5dbb59', 26 | maxFeePerGasForRetryables: parseGwei('0.1'), 27 | }); 28 | 29 | expect(fees).toBeTypeOf('bigint'); 30 | expect(fees).toEqual(124800000000000000n); 31 | }); 32 | -------------------------------------------------------------------------------- /src/createRollupPrepareCustomFeeTokenApprovalTransactionRequest.ts: -------------------------------------------------------------------------------- 1 | import { Address, PublicClient, Transport, Chain } from 'viem'; 2 | 3 | import { approvePrepareTransactionRequest, fetchDecimals } from './utils/erc20'; 4 | import { validateParentChain } from './types/ParentChain'; 5 | import { getRollupCreatorAddress } from './utils/getRollupCreatorAddress'; 6 | 7 | import { Prettify } from './types/utils'; 8 | import { WithRollupCreatorAddressOverride } from './types/createRollupTypes'; 9 | import { createRollupGetRetryablesFeesWithDefaults } from './createRollupGetRetryablesFees'; 10 | import { scaleFrom18DecimalsToNativeTokenDecimals } from './utils/decimals'; 11 | 12 | export type CreateRollupPrepareCustomFeeTokenApprovalTransactionRequestParams< 13 | TChain extends Chain | undefined, 14 | > = Prettify< 15 | WithRollupCreatorAddressOverride<{ 16 | amount?: bigint; 17 | nativeToken: Address; 18 | maxFeePerGasForRetryables?: bigint; 19 | account: Address; 20 | publicClient: PublicClient; 21 | }> 22 | >; 23 | 24 | export async function createRollupPrepareCustomFeeTokenApprovalTransactionRequest< 25 | TChain extends Chain | undefined, 26 | >({ 27 | amount, 28 | nativeToken, 29 | maxFeePerGasForRetryables, 30 | account, 31 | publicClient, 32 | rollupCreatorAddressOverride, 33 | }: CreateRollupPrepareCustomFeeTokenApprovalTransactionRequestParams) { 34 | const { chainId } = validateParentChain(publicClient); 35 | 36 | const fees = await createRollupGetRetryablesFeesWithDefaults(publicClient, { 37 | account, 38 | nativeToken, 39 | maxFeePerGasForRetryables, 40 | }); 41 | 42 | const decimals = await fetchDecimals({ 43 | address: nativeToken, 44 | publicClient, 45 | }); 46 | 47 | const request = await approvePrepareTransactionRequest({ 48 | address: nativeToken, 49 | owner: account, 50 | spender: rollupCreatorAddressOverride ?? getRollupCreatorAddress(publicClient), 51 | amount: amount ?? scaleFrom18DecimalsToNativeTokenDecimals({ amount: fees, decimals }), 52 | publicClient, 53 | }); 54 | 55 | return { ...request, chainId }; 56 | } 57 | -------------------------------------------------------------------------------- /src/createRollupPrepareDeploymentParamsConfigDefaults.ts: -------------------------------------------------------------------------------- 1 | import { zeroAddress, zeroHash } from 'viem'; 2 | 3 | import { getConsensusReleaseByVersion } from './wasmModuleRoot'; 4 | import { CreateRollupPrepareDeploymentParamsConfigResult as Config } from './createRollupPrepareDeploymentParamsConfig'; 5 | 6 | const bufferConfig: Config['bufferConfig'] = { 7 | threshold: BigInt(2 ** 32), 8 | max: BigInt(2 ** 32), 9 | replenishRateInBasis: BigInt(500), 10 | }; 11 | 12 | const genesisAssertionState: Config['genesisAssertionState'] = { 13 | globalState: { 14 | bytes32Vals: [zeroHash, zeroHash], 15 | u64Vals: [BigInt(0), BigInt(0)], 16 | }, 17 | machineStatus: 1, 18 | endHistoryRoot: zeroHash, 19 | } as const; 20 | 21 | export const defaults = { 22 | anyTrustFastConfirmer: zeroAddress, 23 | bufferConfig, 24 | genesisAssertionState, 25 | genesisInboxCount: BigInt(0), 26 | layerZeroBlockEdgeHeight: BigInt(2 ** 26), 27 | layerZeroBigStepEdgeHeight: BigInt(2 ** 19), 28 | layerZeroSmallStepEdgeHeight: BigInt(2 ** 23), 29 | numBigStepLevel: 1, 30 | wasmModuleRoot: getConsensusReleaseByVersion(32).wasmModuleRoot, 31 | } as const; 32 | -------------------------------------------------------------------------------- /src/createRollupPrepareTransaction.ts: -------------------------------------------------------------------------------- 1 | import { Transaction, decodeFunctionData } from 'viem'; 2 | import { DecodeFunctionDataReturnType } from 'viem/_types/utils/abi/decodeFunctionData'; 3 | 4 | import { rollupCreatorABI as rollupCreatorV3Dot1ABI } from './contracts/RollupCreator'; 5 | import { rollupCreatorABI as rollupCreatorV2Dot1ABI } from './contracts/RollupCreator/v2.1'; 6 | import { rollupCreatorABI as rollupCreatorV1Dot1ABI } from './contracts/RollupCreator/v1.1'; 7 | 8 | import { 9 | CreateRollupFunctionInputs, 10 | RollupCreatorABI, 11 | RollupCreatorVersion, 12 | RollupCreatorLatestVersion, 13 | } from './types/createRollupTypes'; 14 | 15 | function createRollupDecodeFunctionData>( 16 | data: `0x${string}`, 17 | ): DecodeFunctionDataReturnType { 18 | let result: DecodeFunctionDataReturnType | null = null; 19 | 20 | // try parsing from multiple RollupCreator versions 21 | [ 22 | // v3.1 23 | rollupCreatorV3Dot1ABI, 24 | // v2.1 25 | rollupCreatorV2Dot1ABI, 26 | // v1.1 27 | rollupCreatorV1Dot1ABI, 28 | ].forEach((abi) => { 29 | try { 30 | result = decodeFunctionData({ abi, data }) as DecodeFunctionDataReturnType; 31 | } catch (error) { 32 | // do nothing 33 | } 34 | }); 35 | 36 | if (result === null) { 37 | throw new Error(`[createRollupPrepareTransaction] failed to decode function data`); 38 | } 39 | 40 | return result; 41 | } 42 | 43 | export type CreateRollupTransaction = Transaction & { 44 | getInputs< 45 | TVersion extends RollupCreatorVersion = RollupCreatorLatestVersion, 46 | >(): CreateRollupFunctionInputs; 47 | }; 48 | 49 | export function createRollupPrepareTransaction(tx: Transaction): CreateRollupTransaction { 50 | return { 51 | ...tx, 52 | getInputs: function () { 53 | const { functionName, args } = createRollupDecodeFunctionData(tx.input); 54 | 55 | if (functionName !== 'createRollup') { 56 | throw new Error(` 57 | [createRollupPrepareTransaction] expected function name to be "createRollup" but got "${functionName}" 58 | `); 59 | } 60 | 61 | if (typeof args === 'undefined') { 62 | throw new Error(`[createRollupPrepareTransaction] failed to decode function data`); 63 | } 64 | 65 | return args; 66 | }, 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /src/createRollupPrepareTransactionReceipt.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | import { createPublicClient, http } from 'viem'; 3 | import { sepolia, arbitrumSepolia } from 'viem/chains'; 4 | 5 | import { createRollupPrepareTransactionReceipt } from './createRollupPrepareTransactionReceipt'; 6 | 7 | const client = createPublicClient({ 8 | chain: arbitrumSepolia, 9 | transport: http(), 10 | }); 11 | 12 | const sepoliaClient = createPublicClient({ 13 | chain: sepolia, 14 | transport: http('https://sepolia.gateway.tenderly.co'), 15 | }); 16 | 17 | // https://sepolia.arbiscan.io/tx/0x5b0b49e0259289fc89949a55a5ad35a8939440a55065d29b14e5e7ef7494efff 18 | it('successfully parses core contracts from a tx receipt on RollupCreator v1.1', async () => { 19 | const txReceipt = await client.getTransactionReceipt({ 20 | hash: '0x5b0b49e0259289fc89949a55a5ad35a8939440a55065d29b14e5e7ef7494efff', 21 | }); 22 | expect(createRollupPrepareTransactionReceipt(txReceipt).getCoreContracts()).toMatchSnapshot(); 23 | }); 24 | 25 | // https://sepolia.arbiscan.io/tx/0x77db43157182a69ce0e6d2a0564d2dabb43b306d48ea7b4d877160d6a1c9b66d 26 | it('successfully parses core contracts from a tx receipt on RollupCreator v2.1', async () => { 27 | const txReceipt = await client.getTransactionReceipt({ 28 | hash: '0x77db43157182a69ce0e6d2a0564d2dabb43b306d48ea7b4d877160d6a1c9b66d', 29 | }); 30 | expect(createRollupPrepareTransactionReceipt(txReceipt).getCoreContracts()).toMatchSnapshot(); 31 | }); 32 | 33 | // https://sepolia.etherscan.io/tx/0x751f1b2bab2806769f663db2141d434e4d8c9b65bc4a5d7ca10ed597f918191f 34 | it('successfully parses core contracts from a tx receipt on RollupCreator v3.1', async () => { 35 | const txReceipt = await sepoliaClient.getTransactionReceipt({ 36 | hash: '0x751f1b2bab2806769f663db2141d434e4d8c9b65bc4a5d7ca10ed597f918191f', 37 | }); 38 | expect(createRollupPrepareTransactionReceipt(txReceipt).getCoreContracts()).toMatchSnapshot(); 39 | }); 40 | -------------------------------------------------------------------------------- /src/createSafePrepareTransactionReceipt.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TransactionReceipt, 3 | getAbiItem, 4 | getEventSelector, 5 | Log, 6 | decodeEventLog, 7 | Address, 8 | } from 'viem'; 9 | import { SafeProxyFactoryAbi } from './createSafePrepareTransactionRequest'; 10 | 11 | function findProxyCreationEventLog(txReceipt: TransactionReceipt) { 12 | const abiItem = getAbiItem({ abi: SafeProxyFactoryAbi, name: 'ProxyCreation' }); 13 | const eventSelector = getEventSelector(abiItem); 14 | const log = txReceipt.logs.find((log) => log.topics[0] === eventSelector); 15 | 16 | if (typeof log === 'undefined') { 17 | throw new Error( 18 | `No "ProxyCreation" logs found in logs for transaction: ${txReceipt.transactionHash}`, 19 | ); 20 | } 21 | 22 | return log; 23 | } 24 | 25 | function decodeProxyCreationEventLog(log: Log) { 26 | const decodedEventLog = decodeEventLog({ ...log, abi: SafeProxyFactoryAbi }); 27 | 28 | if (decodedEventLog.eventName !== 'ProxyCreation') { 29 | throw new Error(`Expected "ProxyCreation" event but found: ${decodedEventLog.eventName}`); 30 | } 31 | 32 | return decodedEventLog; 33 | } 34 | 35 | export type CreateSafeTransactionReceipt = TransactionReceipt & { 36 | getSafeContract(): Address; 37 | }; 38 | 39 | /** 40 | * Adds a getSafeContract() function to a regular {@link TransactionReceipt} for transactions that create a new Safe using the default SafeFactory 41 | */ 42 | export function createSafePrepareTransactionReceipt( 43 | txReceipt: TransactionReceipt, 44 | ): CreateSafeTransactionReceipt { 45 | return { 46 | ...txReceipt, 47 | getSafeContract: function () { 48 | const eventLog = findProxyCreationEventLog(txReceipt); 49 | const decodedEventLog = decodeProxyCreationEventLog(eventLog); 50 | const { proxy } = decodedEventLog.args; 51 | return proxy; 52 | }, 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/createTokenBridge-testHelpers.ts: -------------------------------------------------------------------------------- 1 | import { PublicClient, Address } from 'viem'; 2 | import { exec } from 'node:child_process'; 3 | import { promisify } from 'util'; 4 | 5 | import { getNitroTestnodePrivateKeyAccounts } from './testHelpers'; 6 | 7 | const execPromise = promisify(exec); 8 | const testnodeAccounts = getNitroTestnodePrivateKeyAccounts(); 9 | 10 | export async function deployTokenBridgeCreator({ 11 | publicClient, 12 | }: { 13 | publicClient: PublicClient; 14 | }): Promise
{ 15 | // https://github.com/OffchainLabs/token-bridge-contracts/blob/main/scripts/local-deployment/deployCreatorAndCreateTokenBridge.ts#L109C19-L109C61 16 | const weth = '0x05EcEffc7CBA4e43a410340E849052AD43815aCA'; 17 | 18 | const { stdout } = await execPromise(` 19 | docker run --rm --net=host \ 20 | -e BASECHAIN_RPC=${publicClient.transport.url} \ 21 | -e BASECHAIN_DEPLOYER_KEY=${testnodeAccounts.deployer.privateKey} \ 22 | -e BASECHAIN_WETH=${weth} \ 23 | -e GAS_LIMIT_FOR_L2_FACTORY_DEPLOYMENT=10000000 \ 24 | $(docker build -q token-bridge-contracts) deploy:token-bridge-creator`); 25 | 26 | const match = stdout.match(/L1TokenBridgeCreator: (0x[0-9a-fA-F]{40})/); 27 | 28 | if (!match) { 29 | throw Error(`Failed to parse token bridge creator address from output: ${stdout}`); 30 | } 31 | 32 | return match[1] as Address; 33 | } 34 | -------------------------------------------------------------------------------- /src/createTokenBridgeEnoughCustomFeeTokenAllowance.ts: -------------------------------------------------------------------------------- 1 | import { Address, PublicClient, Transport, Chain } from 'viem'; 2 | 3 | import { fetchAllowance, fetchDecimals } from './utils/erc20'; 4 | import { createTokenBridgeDefaultRetryablesFees } from './constants'; 5 | 6 | import { Prettify } from './types/utils'; 7 | import { WithTokenBridgeCreatorAddressOverride } from './types/createTokenBridgeTypes'; 8 | import { getTokenBridgeCreatorAddress } from './utils/getTokenBridgeCreatorAddress'; 9 | import { scaleFrom18DecimalsToNativeTokenDecimals } from './utils/decimals'; 10 | 11 | export type CreateTokenBridgeEnoughCustomFeeTokenAllowanceParams = 12 | Prettify< 13 | WithTokenBridgeCreatorAddressOverride<{ 14 | nativeToken: Address; 15 | owner: Address; 16 | publicClient: PublicClient; 17 | }> 18 | >; 19 | 20 | export async function createTokenBridgeEnoughCustomFeeTokenAllowance< 21 | TChain extends Chain | undefined, 22 | >({ 23 | nativeToken, 24 | owner, 25 | publicClient, 26 | tokenBridgeCreatorAddressOverride, 27 | }: CreateTokenBridgeEnoughCustomFeeTokenAllowanceParams) { 28 | const allowance = await fetchAllowance({ 29 | address: nativeToken, 30 | owner, 31 | spender: tokenBridgeCreatorAddressOverride ?? getTokenBridgeCreatorAddress(publicClient), 32 | publicClient, 33 | }); 34 | 35 | const decimals = await fetchDecimals({ 36 | address: nativeToken, 37 | publicClient, 38 | }); 39 | 40 | return ( 41 | allowance >= 42 | scaleFrom18DecimalsToNativeTokenDecimals({ 43 | amount: createTokenBridgeDefaultRetryablesFees, 44 | decimals, 45 | }) 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/createTokenBridgeFetchTokenBridgeContracts.ts: -------------------------------------------------------------------------------- 1 | import { Address, PublicClient, Transport, Chain } from 'viem'; 2 | 3 | import { tokenBridgeCreatorABI } from './contracts/TokenBridgeCreator'; 4 | 5 | import { Prettify } from './types/utils'; 6 | import { WithTokenBridgeCreatorAddressOverride } from './types/createTokenBridgeTypes'; 7 | import { TokenBridgeContracts } from './types/TokenBridgeContracts'; 8 | import { getTokenBridgeCreatorAddress } from './utils/getTokenBridgeCreatorAddress'; 9 | 10 | export type CreateTokenBridgeFetchTokenBridgeContractsParams = 11 | Prettify< 12 | WithTokenBridgeCreatorAddressOverride<{ 13 | inbox: Address; 14 | parentChainPublicClient: PublicClient; 15 | }> 16 | >; 17 | 18 | export async function createTokenBridgeFetchTokenBridgeContracts({ 19 | inbox, 20 | parentChainPublicClient, 21 | tokenBridgeCreatorAddressOverride, 22 | }: CreateTokenBridgeFetchTokenBridgeContractsParams): Promise { 23 | const tokenBridgeCreatorAddress = 24 | tokenBridgeCreatorAddressOverride ?? getTokenBridgeCreatorAddress(parentChainPublicClient); 25 | 26 | // getting parent chain addresses 27 | const [ 28 | parentChainRouter, 29 | parentChainStandardGateway, 30 | parentChainCustomGateway, 31 | parentChainWethGateway, 32 | parentChainWeth, 33 | ] = await parentChainPublicClient.readContract({ 34 | address: tokenBridgeCreatorAddress, 35 | abi: tokenBridgeCreatorABI, 36 | functionName: 'inboxToL1Deployment', 37 | args: [inbox], 38 | }); 39 | 40 | const parentChainMulticall = await parentChainPublicClient.readContract({ 41 | address: tokenBridgeCreatorAddress, 42 | abi: tokenBridgeCreatorABI, 43 | functionName: 'l1Multicall', 44 | }); 45 | 46 | const parentChainContracts = { 47 | router: parentChainRouter, 48 | standardGateway: parentChainStandardGateway, 49 | customGateway: parentChainCustomGateway, 50 | wethGateway: parentChainWethGateway, 51 | weth: parentChainWeth, 52 | multicall: parentChainMulticall, 53 | }; 54 | 55 | // getting orbit chain addresses 56 | const [ 57 | orbitChainRouter, 58 | orbitChainStandardGateway, 59 | orbitChainCustomGateway, 60 | orbitChainWethGateway, 61 | orbitChainWeth, 62 | orbitChainProxyAdmin, 63 | orbitChainBeaconProxyFactory, 64 | orbitChainUpgradeExecutor, 65 | orbitChainMulticall, 66 | ] = await parentChainPublicClient.readContract({ 67 | address: tokenBridgeCreatorAddress, 68 | abi: tokenBridgeCreatorABI, 69 | functionName: 'inboxToL2Deployment', 70 | args: [inbox], 71 | }); 72 | const orbitChainContracts = { 73 | router: orbitChainRouter, 74 | standardGateway: orbitChainStandardGateway, 75 | customGateway: orbitChainCustomGateway, 76 | wethGateway: orbitChainWethGateway, 77 | weth: orbitChainWeth, 78 | proxyAdmin: orbitChainProxyAdmin, 79 | beaconProxyFactory: orbitChainBeaconProxyFactory, 80 | upgradeExecutor: orbitChainUpgradeExecutor, 81 | multicall: orbitChainMulticall, 82 | }; 83 | 84 | return { 85 | parentChainContracts, 86 | orbitChainContracts, 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /src/createTokenBridgePrepareCustomFeeTokenApprovalTransactionRequest.ts: -------------------------------------------------------------------------------- 1 | import { Address, PublicClient, Transport, Chain, maxInt256 } from 'viem'; 2 | 3 | import { approvePrepareTransactionRequest, fetchDecimals } from './utils/erc20'; 4 | 5 | import { Prettify } from './types/utils'; 6 | import { validateParentChain } from './types/ParentChain'; 7 | import { WithTokenBridgeCreatorAddressOverride } from './types/createTokenBridgeTypes'; 8 | import { getTokenBridgeCreatorAddress } from './utils/getTokenBridgeCreatorAddress'; 9 | import { createTokenBridgeDefaultRetryablesFees } from './constants'; 10 | import { scaleFrom18DecimalsToNativeTokenDecimals } from './utils/decimals'; 11 | 12 | export type CreateTokenBridgePrepareCustomFeeTokenApprovalTransactionRequestParams< 13 | TChain extends Chain | undefined, 14 | > = Prettify< 15 | WithTokenBridgeCreatorAddressOverride<{ 16 | amount?: bigint; 17 | nativeToken: Address; 18 | owner: Address; 19 | publicClient: PublicClient; 20 | }> 21 | >; 22 | 23 | export async function createTokenBridgePrepareCustomFeeTokenApprovalTransactionRequest< 24 | TChain extends Chain | undefined, 25 | >({ 26 | amount, 27 | nativeToken, 28 | owner, 29 | publicClient, 30 | tokenBridgeCreatorAddressOverride, 31 | }: CreateTokenBridgePrepareCustomFeeTokenApprovalTransactionRequestParams) { 32 | const { chainId } = validateParentChain(publicClient); 33 | 34 | const decimals = await fetchDecimals({ 35 | address: nativeToken, 36 | publicClient, 37 | }); 38 | 39 | const request = await approvePrepareTransactionRequest({ 40 | address: nativeToken, 41 | owner, 42 | spender: tokenBridgeCreatorAddressOverride ?? getTokenBridgeCreatorAddress(publicClient), 43 | amount: 44 | amount ?? 45 | scaleFrom18DecimalsToNativeTokenDecimals({ 46 | amount: createTokenBridgeDefaultRetryablesFees, 47 | decimals, 48 | }), 49 | publicClient, 50 | }); 51 | 52 | return { ...request, chainId }; 53 | } 54 | -------------------------------------------------------------------------------- /src/createTokenBridgePrepareSetWethGatewayTransactionReceipt.ts: -------------------------------------------------------------------------------- 1 | import { PublicClient, Transport, Chain, TransactionReceipt } from 'viem'; 2 | import { 3 | ParentToChildMessageStatus, 4 | ParentToChildMessageWaitForStatusResult, 5 | ParentTransactionReceipt, 6 | } from '@arbitrum/sdk'; 7 | 8 | import { publicClientToProvider } from './ethers-compat/publicClientToProvider'; 9 | import { viemTransactionReceiptToEthersTransactionReceipt } from './ethers-compat/viemTransactionReceiptToEthersTransactionReceipt'; 10 | import { ethersTransactionReceiptToViemTransactionReceipt } from './ethers-compat/ethersTransactionReceiptToViemTransactionReceipt'; 11 | 12 | type RedeemedRetryableTicket = Extract< 13 | ParentToChildMessageWaitForStatusResult, 14 | { status: ParentToChildMessageStatus.REDEEMED } 15 | >; 16 | 17 | export type WaitForRetryablesParameters = { 18 | orbitPublicClient: PublicClient; 19 | }; 20 | 21 | export type WaitForRetryablesResult = [TransactionReceipt]; 22 | 23 | export type CreateTokenBridgeSetWethGatewayTransactionReceipt = 24 | TransactionReceipt & { 25 | waitForRetryables( 26 | params: WaitForRetryablesParameters, 27 | ): Promise; 28 | }; 29 | 30 | export function createTokenBridgePrepareSetWethGatewayTransactionReceipt< 31 | TChain extends Chain | undefined, 32 | >(txReceipt: TransactionReceipt): CreateTokenBridgeSetWethGatewayTransactionReceipt { 33 | return { 34 | ...txReceipt, 35 | waitForRetryables: async function ({ 36 | orbitPublicClient, 37 | }: WaitForRetryablesParameters): Promise { 38 | const ethersTxReceipt = viemTransactionReceiptToEthersTransactionReceipt(txReceipt); 39 | const parentChainTxReceipt = new ParentTransactionReceipt(ethersTxReceipt); 40 | const orbitProvider = publicClientToProvider(orbitPublicClient); 41 | const messages = await parentChainTxReceipt.getParentToChildMessages(orbitProvider); 42 | const messagesResults = await Promise.all(messages.map((message) => message.waitForStatus())); 43 | 44 | if (messagesResults.length !== 1) { 45 | throw Error(`Unexpected number of retryable tickets: ${messagesResults.length}`); 46 | } 47 | 48 | if (messagesResults[0].status !== ParentToChildMessageStatus.REDEEMED) { 49 | throw Error(`Unexpected status for retryable ticket: ${messages[0].retryableCreationId}`); 50 | } 51 | 52 | return ( 53 | // these type casts are both fine as we already checked everything above 54 | (messagesResults as unknown as [RedeemedRetryableTicket]) 55 | // 56 | .map((result) => 57 | ethersTransactionReceiptToViemTransactionReceipt(result.childTxReceipt), 58 | ) as WaitForRetryablesResult 59 | ); 60 | }, 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /src/decorators/arbAggregatorActions.integration.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect, describe } from 'vitest'; 2 | import { createPublicClient, http } from 'viem'; 3 | import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; 4 | 5 | import { nitroTestnodeL2 } from '../chains'; 6 | import { arbAggregatorActions } from './arbAggregatorActions'; 7 | import { getNitroTestnodePrivateKeyAccounts } from '../testHelpers'; 8 | 9 | const testnodeAccounts = getNitroTestnodePrivateKeyAccounts(); 10 | const l2RollupOwner = testnodeAccounts.l2RollupOwner; 11 | const randomAccount = privateKeyToAccount(generatePrivateKey()); 12 | 13 | const nitroTestnodeL2Client = createPublicClient({ 14 | chain: nitroTestnodeL2, 15 | transport: http(), 16 | }).extend(arbAggregatorActions); 17 | 18 | describe('ArgAggregator decorator tests', () => { 19 | it('successfully fetches the batch posters and the fee collectors', async () => { 20 | const batchPosters = await nitroTestnodeL2Client.arbAggregatorReadContract({ 21 | functionName: 'getBatchPosters', 22 | }); 23 | 24 | expect(batchPosters).toHaveLength(2); 25 | expect(batchPosters[0]).toEqual('0xA4b000000000000000000073657175656e636572'); 26 | 27 | const batchPosterFeeCollector = await nitroTestnodeL2Client.arbAggregatorReadContract({ 28 | functionName: 'getFeeCollector', 29 | args: [batchPosters[0]], 30 | }); 31 | 32 | expect(batchPosterFeeCollector).toEqual('0xA4b000000000000000000073657175656e636572'); 33 | }); 34 | 35 | it('succesfully updates the fee collector of a batch poster', async () => { 36 | // Get the batch posters 37 | const batchPosters = await nitroTestnodeL2Client.arbAggregatorReadContract({ 38 | functionName: 'getBatchPosters', 39 | }); 40 | 41 | // Set the fee collector of the batch poster to the random address 42 | const setFeeCollectorTransactionRequest = 43 | await nitroTestnodeL2Client.arbAggregatorPrepareTransactionRequest({ 44 | functionName: 'setFeeCollector', 45 | args: [batchPosters[1], randomAccount.address], 46 | upgradeExecutor: false, 47 | account: l2RollupOwner.address, 48 | }); 49 | const txHash = await nitroTestnodeL2Client.sendRawTransaction({ 50 | serializedTransaction: await l2RollupOwner.signTransaction(setFeeCollectorTransactionRequest), 51 | }); 52 | await nitroTestnodeL2Client.waitForTransactionReceipt({ hash: txHash }); 53 | 54 | // Check the fee collector has changed 55 | const batchPosterFeeCollector = await nitroTestnodeL2Client.arbAggregatorReadContract({ 56 | functionName: 'getFeeCollector', 57 | args: [batchPosters[1]], 58 | }); 59 | expect(batchPosterFeeCollector).toEqual(randomAccount.address); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/decorators/arbAggregatorActions.ts: -------------------------------------------------------------------------------- 1 | import { Transport, Chain, PrepareTransactionRequestReturnType, PublicClient } from 'viem'; 2 | 3 | import { 4 | arbAggregatorReadContract, 5 | ArbAggregatorFunctionName, 6 | ArbAggregatorReadContractParameters, 7 | ArbAggregatorReadContractReturnType, 8 | } from '../arbAggregatorReadContract'; 9 | import { 10 | arbAggregatorPrepareTransactionRequest, 11 | ArbAggregatorPrepareTransactionRequestFunctionName, 12 | ArbAggregatorPrepareTransactionRequestParameters, 13 | } from '../arbAggregatorPrepareTransactionRequest'; 14 | 15 | export type ArbAggregatorActions = { 16 | arbAggregatorReadContract: ( 17 | args: ArbAggregatorReadContractParameters, 18 | ) => Promise>; 19 | 20 | arbAggregatorPrepareTransactionRequest: < 21 | TFunctionName extends ArbAggregatorPrepareTransactionRequestFunctionName, 22 | >( 23 | args: ArbAggregatorPrepareTransactionRequestParameters, 24 | ) => Promise & { chainId: number }>; 25 | }; 26 | 27 | export function arbAggregatorActions< 28 | TTransport extends Transport = Transport, 29 | TChain extends Chain | undefined = Chain | undefined, 30 | >(client: PublicClient): ArbAggregatorActions { 31 | return { 32 | arbAggregatorReadContract: (args) => arbAggregatorReadContract(client, args), 33 | 34 | arbAggregatorPrepareTransactionRequest: (args) => 35 | arbAggregatorPrepareTransactionRequest(client, args), 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/decorators/arbGasInfoPublicActions.ts: -------------------------------------------------------------------------------- 1 | import { Transport, Chain, PublicClient } from 'viem'; 2 | 3 | import { 4 | arbGasInfoReadContract, 5 | ArbGasInfoFunctionName, 6 | ArbGasInfoReadContractParameters, 7 | ArbGasInfoReadContractReturnType, 8 | } from '../arbGasInfoReadContract'; 9 | 10 | export type ArbGasInfoPublicActions = { 11 | arbGasInfoReadContract: ( 12 | args: ArbGasInfoReadContractParameters, 13 | ) => Promise>; 14 | }; 15 | 16 | export function arbGasInfoPublicActions< 17 | TTransport extends Transport = Transport, 18 | TChain extends Chain | undefined = Chain | undefined, 19 | >(client: PublicClient): ArbGasInfoPublicActions { 20 | return { 21 | arbGasInfoReadContract: (args) => arbGasInfoReadContract(client, args), 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/decorators/arbOwnerPrepareTransactionRequest.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect, expectTypeOf } from 'vitest'; 2 | 3 | import { 4 | AbiEncodingLengthMismatchError, 5 | AbiFunctionNotFoundError, 6 | InvalidAddressError, 7 | createPublicClient, 8 | http, 9 | } from 'viem'; 10 | import { nitroTestnodeL2 } from '../chains'; 11 | import { arbOwnerPublicActions } from './arbOwnerPublicActions'; 12 | import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; 13 | 14 | const client = createPublicClient({ 15 | chain: nitroTestnodeL2, 16 | transport: http(), 17 | }).extend(arbOwnerPublicActions); 18 | const randomAccount = privateKeyToAccount(generatePrivateKey()); 19 | 20 | it('Infer parameters based on function name', async () => { 21 | expect( 22 | client.arbOwnerPrepareTransactionRequest({ 23 | functionName: 'addChainOwner', 24 | // @ts-expect-error Args are missing 25 | args: [], 26 | upgradeExecutor: false, 27 | account: randomAccount.address, 28 | }), 29 | ).rejects.toThrow(AbiEncodingLengthMismatchError); 30 | 31 | expect( 32 | client.arbOwnerPrepareTransactionRequest({ 33 | functionName: 'addChainOwner', 34 | // @ts-expect-error Args are of the wrong type 35 | args: [10n], 36 | upgradeExecutor: false, 37 | account: randomAccount.address, 38 | }), 39 | ).rejects.toThrow(InvalidAddressError); 40 | 41 | expect( 42 | client 43 | // @ts-expect-error Args are required for `addChainOwner` 44 | .arbOwnerPrepareTransactionRequest({ 45 | functionName: 'addChainOwner', 46 | upgradeExecutor: false, 47 | account: randomAccount.address, 48 | }), 49 | ).rejects.toThrow(AbiEncodingLengthMismatchError); 50 | 51 | expectTypeOf>().toBeCallableWith( 52 | { 53 | functionName: 'addChainOwner', 54 | args: [randomAccount.address], 55 | upgradeExecutor: false, 56 | account: randomAccount.address, 57 | }, 58 | ); 59 | 60 | // Function doesn't exist 61 | expect( 62 | client.arbOwnerReadContract({ 63 | // @ts-expect-error Function not available 64 | functionName: 'notExisting', 65 | }), 66 | ).rejects.toThrowError(AbiFunctionNotFoundError); 67 | }); 68 | -------------------------------------------------------------------------------- /src/decorators/arbOwnerPublicActions.ts: -------------------------------------------------------------------------------- 1 | import { Transport, Chain, PrepareTransactionRequestReturnType, PublicClient } from 'viem'; 2 | 3 | import { 4 | arbOwnerReadContract, 5 | ArbOwnerPublicFunctionName, 6 | ArbOwnerReadContractParameters, 7 | ArbOwnerReadContractReturnType, 8 | } from '../arbOwnerReadContract'; 9 | import { 10 | arbOwnerPrepareTransactionRequest, 11 | ArbOwnerPrepareTransactionRequestFunctionName, 12 | ArbOwnerPrepareTransactionRequestParameters, 13 | } from '../arbOwnerPrepareTransactionRequest'; 14 | 15 | export type ArbOwnerPublicActions = { 16 | arbOwnerReadContract: ( 17 | args: ArbOwnerReadContractParameters, 18 | ) => Promise>; 19 | 20 | arbOwnerPrepareTransactionRequest: < 21 | TFunctionName extends ArbOwnerPrepareTransactionRequestFunctionName, 22 | >( 23 | args: ArbOwnerPrepareTransactionRequestParameters, 24 | ) => Promise & { chainId: number }>; 25 | }; 26 | 27 | export function arbOwnerPublicActions< 28 | TTransport extends Transport = Transport, 29 | TChain extends Chain | undefined = Chain | undefined, 30 | >(client: PublicClient): ArbOwnerPublicActions { 31 | return { 32 | arbOwnerReadContract: (args) => arbOwnerReadContract(client, args), 33 | 34 | arbOwnerPrepareTransactionRequest: (args) => arbOwnerPrepareTransactionRequest(client, args), 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/ethers-compat/ethersTransactionReceiptToViemTransactionReceipt.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Log as EthersLog, 3 | TransactionReceipt as EthersTransactionReceipt, 4 | } from '@ethersproject/abstract-provider'; 5 | import { 6 | Log as ViemLog, 7 | Hash, 8 | Hex, 9 | Address, 10 | TransactionReceipt as ViemTransactionReceipt, 11 | } from 'viem'; 12 | 13 | function ethersLogToViemLog(log: EthersLog): ViemLog { 14 | return { 15 | blockNumber: BigInt(log.blockNumber), 16 | blockHash: log.blockHash as Hash, 17 | transactionIndex: log.transactionIndex, 18 | removed: log.removed, 19 | address: log.address as Address, 20 | data: log.data as Hex, 21 | topics: log.topics as [Hex, ...Hex[]], 22 | transactionHash: log.transactionHash as Hash, 23 | logIndex: log.logIndex, 24 | }; 25 | } 26 | 27 | export function ethersTransactionReceiptToViemTransactionReceipt( 28 | receipt: EthersTransactionReceipt, 29 | ): ViemTransactionReceipt { 30 | return { 31 | blockHash: receipt.blockHash as `0x${string}`, 32 | blockNumber: BigInt(receipt.blockNumber), 33 | contractAddress: receipt.contractAddress as Address, 34 | cumulativeGasUsed: receipt.cumulativeGasUsed.toBigInt(), 35 | effectiveGasPrice: receipt.effectiveGasPrice.toBigInt(), 36 | from: receipt.from as Address, 37 | gasUsed: receipt.gasUsed.toBigInt(), 38 | logs: receipt.logs.map((log) => ethersLogToViemLog(log)), 39 | logsBloom: receipt.logsBloom as `0x${string}`, 40 | status: receipt.status === 1 ? 'success' : 'reverted', 41 | to: receipt.to as Address, 42 | transactionHash: receipt.transactionHash as `0x${string}`, 43 | transactionIndex: receipt.transactionIndex, 44 | type: '0x' + receipt.type.toString(16), 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/ethers-compat/publicClientToProvider.ts: -------------------------------------------------------------------------------- 1 | import { PublicClient, Transport, Chain } from 'viem'; 2 | import { providers } from 'ethers'; 3 | 4 | // based on https://wagmi.sh/react/ethers-adapters#reference-implementation 5 | export function publicClientToProvider( 6 | publicClient: PublicClient, 7 | ) { 8 | const { chain } = publicClient; 9 | 10 | if (typeof chain === 'undefined') { 11 | throw new Error(`[publicClientToProvider] "chain" is undefined`); 12 | } 13 | 14 | const network = { 15 | chainId: chain.id, 16 | name: chain.name, 17 | ensAddress: chain.contracts?.ensRegistry?.address, 18 | }; 19 | 20 | const transportUrl = publicClient.transport.url as string | undefined; 21 | const url = transportUrl ?? chain.rpcUrls.default.http[0]; 22 | 23 | return new providers.StaticJsonRpcProvider(url, network); 24 | } 25 | -------------------------------------------------------------------------------- /src/ethers-compat/publicClientToProvider.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | import { createPublicClient, http } from 'viem'; 3 | import { arbitrumSepolia } from 'viem/chains'; 4 | 5 | import { publicClientToProvider } from './publicClientToProvider'; 6 | 7 | it(`successfully converts PublicClient to Provider`, () => { 8 | const publicClient = createPublicClient({ 9 | chain: arbitrumSepolia, 10 | transport: http(), 11 | }); 12 | 13 | const provider = publicClientToProvider(publicClient); 14 | 15 | expect(provider.network.chainId).toEqual(publicClient.chain.id); 16 | expect(provider.network.name).toEqual(publicClient.chain.name); 17 | 18 | expect(provider.connection.url).toEqual('https://sepolia-rollup.arbitrum.io/rpc'); 19 | }); 20 | 21 | it(`successfully converts PublicClient to Provider (custom Transport)`, () => { 22 | const publicClient = createPublicClient({ 23 | chain: arbitrumSepolia, 24 | transport: http('https://arbitrum-sepolia.gateway.tenderly.co'), 25 | }); 26 | 27 | const provider = publicClientToProvider(publicClient); 28 | 29 | expect(provider.network.chainId).toEqual(publicClient.chain.id); 30 | expect(provider.network.name).toEqual(publicClient.chain.name); 31 | 32 | expect(provider.connection.url).toEqual('https://arbitrum-sepolia.gateway.tenderly.co'); 33 | }); 34 | -------------------------------------------------------------------------------- /src/ethers-compat/viemTransactionReceiptToEthersTransactionReceipt.ts: -------------------------------------------------------------------------------- 1 | import { Log as ViemLog, TransactionReceipt as ViemTransactionReceipt } from 'viem'; 2 | import { 3 | Log as EthersLog, 4 | TransactionReceipt as EthersTransactionReceipt, 5 | } from '@ethersproject/abstract-provider'; 6 | import { BigNumber } from 'ethers'; 7 | 8 | function viemLogToEthersLog(log: ViemLog): EthersLog { 9 | return { 10 | blockNumber: Number(log.blockNumber), 11 | blockHash: log.blockHash!, 12 | transactionIndex: log.transactionIndex!, 13 | removed: log.removed, 14 | address: log.address, 15 | data: log.data, 16 | topics: log.topics, 17 | transactionHash: log.transactionHash!, 18 | logIndex: log.logIndex!, 19 | }; 20 | } 21 | 22 | export function viemTransactionReceiptToEthersTransactionReceipt( 23 | receipt: ViemTransactionReceipt, 24 | ): EthersTransactionReceipt { 25 | return { 26 | to: receipt.to!, 27 | from: receipt.from!, 28 | contractAddress: receipt.contractAddress!, 29 | transactionIndex: receipt.transactionIndex, 30 | gasUsed: BigNumber.from(receipt.gasUsed), 31 | logsBloom: receipt.logsBloom, 32 | blockHash: receipt.blockHash, 33 | transactionHash: receipt.transactionHash, 34 | logs: receipt.logs.map((log) => viemLogToEthersLog(log)), 35 | blockNumber: Number(receipt.blockNumber), 36 | // todo: if we need this we can add it later 37 | confirmations: -1, 38 | cumulativeGasUsed: BigNumber.from(receipt.cumulativeGasUsed), 39 | effectiveGasPrice: BigNumber.from(receipt.effectiveGasPrice), 40 | // all transactions that we care about are well past byzantium 41 | byzantium: true, 42 | type: Number(receipt.type), 43 | status: receipt.status === 'success' ? 1 : 0, 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/getDefaultChallengeGracePeriodBlocks.ts: -------------------------------------------------------------------------------- 1 | import { Client, Transport, Chain } from 'viem'; 2 | 3 | import { ParentChainId, validateParentChain } from './types/ParentChain'; 4 | import { getParentChainBlockTime } from './getParentChainBlockTime'; 5 | 6 | export function getDefaultChallengeGracePeriodBlocks( 7 | parentChainIdOrClient: ParentChainId | Client, 8 | ): bigint { 9 | const { chainId: parentChainId, isCustom: parentChainIsCustom } = 10 | validateParentChain(parentChainIdOrClient); 11 | 12 | if (parentChainIsCustom) { 13 | throw new Error( 14 | `[getDefaultChallengeGracePeriodBlocks] can't provide defaults for custom parent chain with id ${parentChainId}`, 15 | ); 16 | } 17 | 18 | const blocksPerMinute = 60 / getParentChainBlockTime(parentChainId); 19 | 20 | // 2 days 21 | return BigInt(2 * 24 * 60 * blocksPerMinute); 22 | } 23 | -------------------------------------------------------------------------------- /src/getDefaultChallengeGracePeriodBlocks.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | 3 | import { chains } from './chains'; 4 | import { ParentChainId } from './types/ParentChain'; 5 | import { getDefaultChallengeGracePeriodBlocks } from './getDefaultChallengeGracePeriodBlocks'; 6 | 7 | it('returns default value for challengeGracePeriodBlocks based on parent chain', () => { 8 | expect( 9 | chains 10 | .filter((chain) => chain.id !== 333333) 11 | .reduce((acc, value) => { 12 | return { 13 | ...acc, 14 | // it's ok to cast as we've filtered out 333333 above 15 | [value.id]: getDefaultChallengeGracePeriodBlocks(value.id as ParentChainId), 16 | }; 17 | }, {}), 18 | ).toMatchSnapshot(); 19 | }); 20 | -------------------------------------------------------------------------------- /src/getDefaultConfirmPeriodBlocks.ts: -------------------------------------------------------------------------------- 1 | import { Client, Transport, Chain } from 'viem'; 2 | 3 | import { ParentChainId, validateParentChain } from './types/ParentChain'; 4 | import { parentChainIsMainnet } from './parentChainIsMainnet'; 5 | import { getParentChainBlockTime } from './getParentChainBlockTime'; 6 | 7 | export function getDefaultConfirmPeriodBlocks( 8 | parentChainIdOrClient: ParentChainId | Client, 9 | ): bigint { 10 | const { chainId: parentChainId, isCustom: parentChainIsCustom } = 11 | validateParentChain(parentChainIdOrClient); 12 | 13 | if (parentChainIsCustom) { 14 | throw new Error( 15 | `[getDefaultConfirmPeriodBlocks] can't provide defaults for custom parent chain with id ${parentChainId}`, 16 | ); 17 | } 18 | 19 | const isMainnet = parentChainIsMainnet(parentChainId); 20 | const blocksPerMinute = 60 / getParentChainBlockTime(parentChainId); 21 | 22 | return isMainnet 23 | ? // 7 days 24 | BigInt(7 * 24 * 60 * blocksPerMinute) 25 | : // 30 minutes 26 | BigInt(30 * blocksPerMinute); 27 | } 28 | -------------------------------------------------------------------------------- /src/getDefaultConfirmPeriodBlocks.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | 3 | import { chains } from './chains'; 4 | import { ParentChainId } from './types/ParentChain'; 5 | import { getDefaultConfirmPeriodBlocks } from './getDefaultConfirmPeriodBlocks'; 6 | 7 | it('returns default value for confirmPeriodBlocks based on parent chain', () => { 8 | expect( 9 | chains 10 | .filter((chain) => chain.id !== 333333) 11 | .reduce((acc, value) => { 12 | return { 13 | ...acc, 14 | // it's ok to cast as we've filtered out 333333 above 15 | [value.id]: getDefaultConfirmPeriodBlocks(value.id as ParentChainId), 16 | }; 17 | }, {}), 18 | ).toMatchSnapshot(); 19 | }); 20 | -------------------------------------------------------------------------------- /src/getDefaultMinimumAssertionPeriod.ts: -------------------------------------------------------------------------------- 1 | import { Client, Transport, Chain } from 'viem'; 2 | 3 | import { ParentChainId, validateParentChain } from './types/ParentChain'; 4 | import { getParentChainBlockTime } from './getParentChainBlockTime'; 5 | 6 | export function getDefaultMinimumAssertionPeriod( 7 | parentChainIdOrClient: ParentChainId | Client, 8 | ): bigint { 9 | const { chainId: parentChainId, isCustom: parentChainIsCustom } = 10 | validateParentChain(parentChainIdOrClient); 11 | 12 | if (parentChainIsCustom) { 13 | throw new Error( 14 | `[getDefaultMinimumAssertionPeriod] can't provide defaults for custom parent chain with id ${parentChainId}`, 15 | ); 16 | } 17 | 18 | const blocksPerMinute = 60 / getParentChainBlockTime(parentChainId); 19 | 20 | // 15 minutes 21 | return BigInt(15 * blocksPerMinute); 22 | } 23 | -------------------------------------------------------------------------------- /src/getDefaultMinimumAssertionPeriod.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | 3 | import { chains } from './chains'; 4 | import { ParentChainId } from './types/ParentChain'; 5 | import { getDefaultMinimumAssertionPeriod } from './getDefaultMinimumAssertionPeriod'; 6 | 7 | it('returns default value for minimumAssertionPeriod based on parent chain', () => { 8 | expect( 9 | chains 10 | .filter((chain) => chain.id !== 333333) 11 | .reduce((acc, value) => { 12 | return { 13 | ...acc, 14 | // it's ok to cast as we've filtered out 333333 above 15 | [value.id]: getDefaultMinimumAssertionPeriod(value.id as ParentChainId), 16 | }; 17 | }, {}), 18 | ).toMatchSnapshot(); 19 | }); 20 | -------------------------------------------------------------------------------- /src/getDefaultSequencerInboxMaxTimeVariation.ts: -------------------------------------------------------------------------------- 1 | import { Client, Transport, Chain } from 'viem'; 2 | 3 | import { ParentChainId, validateParentChain } from './types/ParentChain'; 4 | import { getParentChainBlockTime } from './getParentChainBlockTime'; 5 | 6 | export type SequencerInboxMaxTimeVariation = { 7 | delayBlocks: bigint; 8 | futureBlocks: bigint; 9 | delaySeconds: bigint; 10 | futureSeconds: bigint; 11 | }; 12 | 13 | export function getDefaultSequencerInboxMaxTimeVariation( 14 | parentChainIdOrClient: ParentChainId | Client, 15 | ): SequencerInboxMaxTimeVariation { 16 | const { chainId: parentChainId, isCustom: parentChainIsCustom } = 17 | validateParentChain(parentChainIdOrClient); 18 | 19 | if (parentChainIsCustom) { 20 | throw new Error( 21 | `[getDefaultSequencerInboxMaxTimeVariation] can't provide defaults for custom parent chain with id ${parentChainId}`, 22 | ); 23 | } 24 | 25 | const delaySeconds = 60 * 60 * 24 * 4; // 4 days; 26 | const futureSeconds = 60 * 60; // 1 hour; 27 | 28 | const blockTime = getParentChainBlockTime(parentChainId); 29 | 30 | return { 31 | delayBlocks: BigInt(delaySeconds / blockTime), 32 | futureBlocks: BigInt(futureSeconds / blockTime), 33 | delaySeconds: BigInt(delaySeconds), 34 | futureSeconds: BigInt(futureSeconds), 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/getDefaultSequencerInboxMaxTimeVariation.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | 3 | import { chains } from './chains'; 4 | import { ParentChainId } from './types/ParentChain'; 5 | import { getDefaultSequencerInboxMaxTimeVariation } from './getDefaultSequencerInboxMaxTimeVariation'; 6 | 7 | it('returns default value for sequencerInboxMaxTimeVariation based on parent chain', () => { 8 | expect( 9 | chains 10 | .filter((chain) => chain.id !== 333333) 11 | .reduce((acc, value) => { 12 | return { 13 | ...acc, 14 | // it's ok to cast as we've filtered out 333333 above 15 | [value.id]: getDefaultSequencerInboxMaxTimeVariation(value.id as ParentChainId), 16 | }; 17 | }, {}), 18 | ).toMatchSnapshot(); 19 | }); 20 | -------------------------------------------------------------------------------- /src/getDefaultValidatorAfkBlocks.ts: -------------------------------------------------------------------------------- 1 | import { Client, Transport, Chain } from 'viem'; 2 | 3 | import { ParentChainId, validateParentChain } from './types/ParentChain'; 4 | import { getParentChainBlockTime } from './getParentChainBlockTime'; 5 | 6 | export function getDefaultValidatorAfkBlocks( 7 | parentChainIdOrClient: ParentChainId | Client, 8 | ): bigint { 9 | const { chainId: parentChainId, isCustom: parentChainIsCustom } = 10 | validateParentChain(parentChainIdOrClient); 11 | 12 | if (parentChainIsCustom) { 13 | throw new Error( 14 | `[getDefaultValidatorAfkBlocks] can't provide defaults for custom parent chain with id ${parentChainId}`, 15 | ); 16 | } 17 | 18 | const blocksPerMinute = 60 / getParentChainBlockTime(parentChainId); 19 | 20 | // https://github.com/OffchainLabs/nitro-contracts/blob/main/src/rollup/RollupAdminLogic.sol#L58-L60 21 | // 22 | // 28 days 23 | // 24 | // Since it can take 14 days under normal circumstances to confirm an assertion, this means 25 | // the validators will have been inactive for a further 14 days before the whitelist is removed. 26 | return BigInt(28 * 24 * 60 * blocksPerMinute); 27 | } 28 | -------------------------------------------------------------------------------- /src/getDefaultValidatorAfkBlocks.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | 3 | import { chains } from './chains'; 4 | import { ParentChainId } from './types/ParentChain'; 5 | import { getDefaultValidatorAfkBlocks } from './getDefaultValidatorAfkBlocks'; 6 | 7 | it('returns default value for validatorAfkBlocks based on parent chain', () => { 8 | expect( 9 | chains 10 | .filter((chain) => chain.id !== 333333) 11 | .reduce((acc, value) => { 12 | return { 13 | ...acc, 14 | // it's ok to cast as we've filtered out 333333 above 15 | [value.id]: getDefaultValidatorAfkBlocks(value.id as ParentChainId), 16 | }; 17 | }, {}), 18 | ).toMatchSnapshot(); 19 | }); 20 | -------------------------------------------------------------------------------- /src/getKeysets.ts: -------------------------------------------------------------------------------- 1 | import { Address, Chain, Hex, PublicClient, Transport, getAbiItem } from 'viem'; 2 | 3 | import { sequencerInboxABI } from './contracts/SequencerInbox'; 4 | import { createRollupFetchTransactionHash } from './createRollupFetchTransactionHash'; 5 | import { getLogsWithBatching } from './utils/getLogsWithBatching'; 6 | 7 | const SetValidKeysetEventAbi = getAbiItem({ abi: sequencerInboxABI, name: 'SetValidKeyset' }); 8 | const InvalidateKeysetEventAbi = getAbiItem({ abi: sequencerInboxABI, name: 'InvalidateKeyset' }); 9 | 10 | export type GetKeysetsParams = { 11 | /** Address of the sequencerInbox we're getting logs from */ 12 | sequencerInbox: Address; 13 | }; 14 | export type GetKeysetsReturnType = { 15 | /** Map of keyset hash to keyset bytes 16 | * keyset hash are used to invalidate a given keyset 17 | */ 18 | keysets: { 19 | [keysetHash: Hex]: Hex; 20 | }; 21 | }; 22 | 23 | /** 24 | * 25 | * @param {PublicClient} publicClient - The chain Viem Public Client 26 | * @param {GetKeysetsParams} GetKeysetsParams {@link GetKeysetsParams} 27 | * 28 | * @returns Promise<{@link GetKeysetsReturnType}> 29 | * 30 | * @example 31 | * const { keysets } = getKeysets(client, { 32 | * sequencerInbox: '0x211E1c4c7f1bF5351Ac850Ed10FD68CFfCF6c21b' 33 | * }); 34 | * 35 | */ 36 | export async function getKeysets( 37 | publicClient: PublicClient, 38 | { sequencerInbox }: GetKeysetsParams, 39 | ): Promise { 40 | let blockNumber: bigint; 41 | let createRollupTransactionHash: Address | null = null; 42 | const rollup = await publicClient.readContract({ 43 | functionName: 'rollup', 44 | address: sequencerInbox, 45 | abi: sequencerInboxABI, 46 | }); 47 | try { 48 | createRollupTransactionHash = await createRollupFetchTransactionHash({ 49 | rollup, 50 | publicClient, 51 | }); 52 | const receipt = await publicClient.waitForTransactionReceipt({ 53 | hash: createRollupTransactionHash, 54 | }); 55 | blockNumber = receipt.blockNumber; 56 | } catch (e) { 57 | console.warn(`[getKeysets] ${(e as any).message}`); 58 | blockNumber = 0n; 59 | } 60 | 61 | const events = await getLogsWithBatching(publicClient, { 62 | address: sequencerInbox, 63 | events: [SetValidKeysetEventAbi, InvalidateKeysetEventAbi], 64 | fromBlock: blockNumber, 65 | }); 66 | 67 | const keysets = events.reduce((acc, event) => { 68 | switch (event.eventName) { 69 | case SetValidKeysetEventAbi.name: { 70 | const { keysetHash, keysetBytes } = event.args; 71 | if (!keysetHash || !keysetBytes) { 72 | console.warn(`[getKeysets] Missing args for event: ${event.transactionHash}`); 73 | return acc; 74 | } 75 | acc.set(keysetHash, keysetBytes); 76 | return acc; 77 | } 78 | case InvalidateKeysetEventAbi.name: { 79 | const { keysetHash } = event.args; 80 | if (!keysetHash) { 81 | console.warn(`[getKeysets] Missing args for event: ${event.transactionHash}`); 82 | return acc; 83 | } 84 | acc.delete(keysetHash); 85 | return acc; 86 | } 87 | } 88 | }, new Map()); 89 | 90 | return { 91 | keysets: Object.fromEntries(keysets), 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /src/getParentChainBlockTime.ts: -------------------------------------------------------------------------------- 1 | import { ParentChainId } from './types/ParentChain'; 2 | import { base, baseSepolia } from './chains'; 3 | 4 | export function getParentChainBlockTime(parentChainId: ParentChainId) { 5 | const parentChainIsOpStack = parentChainId === base.id || parentChainId === baseSepolia.id; 6 | // For Arbitrum L2s built on top of Ethereum, or Arbitrum L3s built on top of an Arbitrum L2, `block.number` always returns the L1 block number. 7 | // L1 blocks are produced every 12 seconds. 8 | // 9 | // For Arbitrum L3s built on top of an OP Stack L2, `block.number` will return the L2 block number. 10 | // L2 blocks in OP Stack chains are produced every 2 seconds. 11 | return parentChainIsOpStack ? 2 : 12; 12 | } 13 | -------------------------------------------------------------------------------- /src/isAnyTrust.ts: -------------------------------------------------------------------------------- 1 | import { Address, Chain, PublicClient, Transport, decodeFunctionData, getAbiItem } from 'viem'; 2 | import { createRollupFetchTransactionHash } from './createRollupFetchTransactionHash'; 3 | 4 | import { rollupCreatorABI as rollupCreatorV3Dot1ABI } from './contracts/RollupCreator'; 5 | import { rollupCreatorABI as rollupCreatorV2Dot1ABI } from './contracts/RollupCreator/v2.1'; 6 | import { rollupCreatorABI as rollupCreatorV1Dot1ABI } from './contracts/RollupCreator/v1.1'; 7 | 8 | const createRollupV3Dot1ABI = getAbiItem({ abi: rollupCreatorV3Dot1ABI, name: 'createRollup' }); 9 | const createRollupV2Dot1ABI = getAbiItem({ abi: rollupCreatorV2Dot1ABI, name: 'createRollup' }); 10 | const createRollupV1Dot1ABI = getAbiItem({ abi: rollupCreatorV1Dot1ABI, name: 'createRollup' }); 11 | 12 | function parseConfig(config: { chainConfig: string }): boolean { 13 | return JSON.parse(config.chainConfig).arbitrum.DataAvailabilityCommittee; 14 | } 15 | 16 | export async function isAnyTrust({ 17 | rollup, 18 | publicClient, 19 | }: { 20 | rollup: Address; 21 | publicClient: PublicClient; 22 | }) { 23 | const createRollupTransactionHash = await createRollupFetchTransactionHash({ 24 | rollup, 25 | publicClient, 26 | }); 27 | 28 | const transaction = await publicClient.getTransaction({ 29 | hash: createRollupTransactionHash, 30 | }); 31 | 32 | let result: boolean | null = null; 33 | // try parsing from multiple RollupCreator versions 34 | [ 35 | // v3.1 36 | createRollupV3Dot1ABI, 37 | // v2.1 38 | createRollupV2Dot1ABI, 39 | // v1.1 40 | createRollupV1Dot1ABI, 41 | ].forEach((abi) => { 42 | try { 43 | const { 44 | args: [{ config }], 45 | } = decodeFunctionData({ 46 | abi: [abi], 47 | data: transaction.input, 48 | }); 49 | 50 | result = parseConfig(config); 51 | } catch (error) { 52 | // do nothing 53 | } 54 | }); 55 | 56 | if (result === null) { 57 | throw new Error( 58 | `[isAnyTrust] failed to decode input data for transaction ${createRollupTransactionHash}`, 59 | ); 60 | } 61 | 62 | return result; 63 | } 64 | -------------------------------------------------------------------------------- /src/isTokenBridgeDeployed.ts: -------------------------------------------------------------------------------- 1 | import { Address, Chain, PublicClient, Transport } from 'viem'; 2 | 3 | import { tokenBridgeCreatorABI } from './contracts/TokenBridgeCreator'; 4 | import { getTokenBridgeCreatorAddress } from './utils'; 5 | import { rollupABI } from './contracts/Rollup'; 6 | import { WithTokenBridgeCreatorAddressOverride } from './types/createTokenBridgeTypes'; 7 | 8 | /** 9 | * Checks whether the token bridge contracts were deployed for a given Rollup. 10 | * 11 | * @param {String} params.parentChainPublicClient - The parent chain viem PublicClient. 12 | * @param {String} params.orbitChainPublicClient - The orbit chain viem PublicClient. 13 | * @param {String} params.rollup - The address of the Rollup on the parent chain. 14 | * @param {String} params.tokenBridgeCreatorAddress - Specifies a custom address for the TokenBridgeCreator. By default, the address will be automatically detected based on the provided chain. 15 | * 16 | * @returns true if token bridge was already deployed 17 | */ 18 | export async function isTokenBridgeDeployed< 19 | TParentChain extends Chain | undefined, 20 | TOrbitChain extends Chain | undefined, 21 | >({ 22 | parentChainPublicClient, 23 | orbitChainPublicClient, 24 | rollup, 25 | tokenBridgeCreatorAddressOverride, 26 | }: WithTokenBridgeCreatorAddressOverride<{ 27 | parentChainPublicClient: PublicClient; 28 | orbitChainPublicClient: PublicClient; 29 | rollup: Address; 30 | }>): Promise { 31 | const inbox = await parentChainPublicClient.readContract({ 32 | address: rollup, 33 | abi: rollupABI, 34 | functionName: 'inbox', 35 | }); 36 | 37 | const [router] = await parentChainPublicClient.readContract({ 38 | address: 39 | tokenBridgeCreatorAddressOverride ?? getTokenBridgeCreatorAddress(parentChainPublicClient), 40 | abi: tokenBridgeCreatorABI, 41 | functionName: 'inboxToL2Deployment', 42 | args: [inbox], 43 | }); 44 | 45 | if (router) { 46 | const code = await orbitChainPublicClient.getBytecode({ address: router }); 47 | 48 | if (code) { 49 | return true; 50 | } 51 | } 52 | 53 | return false; 54 | } 55 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@arbitrum/orbit-sdk", 3 | "description": "TypeScript SDK for building Arbitrum Orbit chains", 4 | "version": "0.23.3", 5 | "main": "./dist/index.js", 6 | "files": [ 7 | "./dist" 8 | ], 9 | "exports": { 10 | ".": { 11 | "types": "./dist/index.d.ts", 12 | "default": "./dist/index.js" 13 | }, 14 | "./chains": { 15 | "types": "./dist/chains.d.ts", 16 | "default": "./dist/chains.js" 17 | }, 18 | "./utils": { 19 | "types": "./dist/utils/index.d.ts", 20 | "default": "./dist/utils/index.js" 21 | }, 22 | "./constants": { 23 | "types": "./dist/constants.d.ts", 24 | "default": "./dist/constants.js" 25 | }, 26 | "./contracts/*": { 27 | "types": "./dist/contracts/*", 28 | "default": "./dist/contracts/*" 29 | } 30 | }, 31 | "typesVersions": { 32 | "*": { 33 | "chains": [ 34 | "./dist/chains.d.ts" 35 | ], 36 | "utils": [ 37 | "./dist/utils/index.d.ts" 38 | ], 39 | "constants": [ 40 | "./dist/constants.d.ts" 41 | ], 42 | "contracts/*": [ 43 | "./dist/contracts/*" 44 | ] 45 | } 46 | }, 47 | "repository": "git+https://github.com/OffchainLabs/arbitrum-orbit-sdk.git", 48 | "author": "Offchain Labs, Inc.", 49 | "license": "Apache-2.0", 50 | "peerDependencies": { 51 | "viem": "^1.20.0" 52 | }, 53 | "dependencies": { 54 | "@arbitrum/sdk": "^4.0.4", 55 | "@arbitrum/token-bridge-contracts": "^1.2.2", 56 | "@offchainlabs/fund-distribution-contracts": "^1.0.1", 57 | "@safe-global/protocol-kit": "^4.1.7", 58 | "ethers": "^5.7.2" 59 | }, 60 | "resolutions": { 61 | "**/base-x": "3.0.11" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/parentChainIsArbitrum.ts: -------------------------------------------------------------------------------- 1 | import * as chains from './chains'; 2 | import { ParentChainId } from './types/ParentChain'; 3 | 4 | export function parentChainIsArbitrum(parentChainId: ParentChainId): boolean { 5 | // doing switch here to make sure it's exhaustive when checking against `ParentChainId` 6 | switch (parentChainId) { 7 | case chains.mainnet.id: 8 | case chains.base.id: 9 | case chains.sepolia.id: 10 | case chains.baseSepolia.id: 11 | case chains.nitroTestnodeL1.id: 12 | return false; 13 | 14 | case chains.arbitrumOne.id: 15 | case chains.arbitrumNova.id: 16 | case chains.arbitrumSepolia.id: 17 | case chains.nitroTestnodeL2.id: 18 | return true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/parentChainIsMainnet.ts: -------------------------------------------------------------------------------- 1 | import * as chains from './chains'; 2 | import { ParentChainId } from './types/ParentChain'; 3 | 4 | export function parentChainIsMainnet(parentChainId: ParentChainId): boolean { 5 | // doing switch here to make sure it's exhaustive when checking against `ParentChainId` 6 | switch (parentChainId) { 7 | case chains.mainnet.id: 8 | case chains.arbitrumOne.id: 9 | case chains.arbitrumNova.id: 10 | case chains.base.id: 11 | return true; 12 | 13 | case chains.sepolia.id: 14 | case chains.arbitrumSepolia.id: 15 | case chains.baseSepolia.id: 16 | case chains.nitroTestnodeL1.id: 17 | case chains.nitroTestnodeL2.id: 18 | return false; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/prepareChainConfig.ts: -------------------------------------------------------------------------------- 1 | import { ExcludeSome, Prettify, RequireSome } from './types/utils'; 2 | import { ChainConfig, ChainConfigArbitrumParams } from './types/ChainConfig'; 3 | 4 | export const defaults = { 5 | homesteadBlock: 0, 6 | daoForkBlock: null, 7 | daoForkSupport: true, 8 | eip150Block: 0, 9 | eip150Hash: '0x0000000000000000000000000000000000000000000000000000000000000000', 10 | eip155Block: 0, 11 | eip158Block: 0, 12 | byzantiumBlock: 0, 13 | constantinopleBlock: 0, 14 | petersburgBlock: 0, 15 | istanbulBlock: 0, 16 | muirGlacierBlock: 0, 17 | berlinBlock: 0, 18 | londonBlock: 0, 19 | clique: { 20 | period: 0, 21 | epoch: 0, 22 | }, 23 | arbitrum: { 24 | EnableArbOS: true, 25 | AllowDebugPrecompiles: false, 26 | DataAvailabilityCommittee: false, 27 | InitialArbOSVersion: 32, 28 | GenesisBlockNum: 0, 29 | MaxCodeSize: 24_576, 30 | MaxInitCodeSize: 49_152, 31 | }, 32 | }; 33 | 34 | export type PrepareChainConfigParams = Prettify< 35 | Pick & { 36 | arbitrum: PrepareChainConfigArbitrumParams; 37 | } 38 | >; 39 | 40 | export type PrepareChainConfigArbitrumParams = RequireSome< 41 | // exclude some fields that shouldn't be changed 42 | ExcludeSome< 43 | ChainConfigArbitrumParams, 44 | 'EnableArbOS' | 'GenesisBlockNum' | 'AllowDebugPrecompiles' 45 | >, 46 | // make InitialChainOwner required 47 | 'InitialChainOwner' 48 | >; 49 | 50 | export function prepareChainConfig(params: PrepareChainConfigParams): ChainConfig { 51 | return { 52 | ...defaults, 53 | chainId: params.chainId, 54 | arbitrum: { ...defaults.arbitrum, ...params.arbitrum }, 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/prepareChainConfig.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | 3 | import { prepareChainConfig, PrepareChainConfigParams } from './prepareChainConfig'; 4 | 5 | const chainId = 69_420; 6 | const vitalik: `0x${string}` = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045'; 7 | 8 | it('creates chain config with defaults', () => { 9 | const params = { 10 | chainId, 11 | arbitrum: { 12 | InitialChainOwner: vitalik, 13 | }, 14 | }; 15 | 16 | expect(prepareChainConfig(params)).toMatchSnapshot(); 17 | }); 18 | 19 | it('creates chain config with custom params', () => { 20 | const params: PrepareChainConfigParams = { 21 | chainId, 22 | arbitrum: { 23 | InitialChainOwner: vitalik, 24 | InitialArbOSVersion: 20, 25 | DataAvailabilityCommittee: true, 26 | MaxCodeSize: 40 * 1024, 27 | MaxInitCodeSize: 80 * 1024, 28 | }, 29 | }; 30 | 31 | expect(prepareChainConfig(params)).toMatchSnapshot(); 32 | }); 33 | -------------------------------------------------------------------------------- /src/prepareKeyset.ts: -------------------------------------------------------------------------------- 1 | function uint64ToBigEndian(value: number): Uint8Array { 2 | const buffer = new ArrayBuffer(8); 3 | const view = new DataView(buffer); 4 | view.setUint32(0, Math.floor(value / 0x100000000)); 5 | view.setUint32(4, value % 0x100000000); 6 | return new Uint8Array(buffer); 7 | } 8 | function byteToHex(byte: number): string { 9 | return byte.toString(16).padStart(2, '0'); 10 | } 11 | 12 | function uint16ToBigEndian(value: number): Uint8Array { 13 | const buffer = new ArrayBuffer(2); 14 | const view = new DataView(buffer); 15 | view.setUint16(0, value); 16 | return new Uint8Array(buffer); 17 | } 18 | 19 | export function prepareKeyset(publicKeys: string[], assumedHonest: number): `0x${string}` { 20 | const numberOfMembers = publicKeys.length; 21 | const membersBuffer: Uint8Array[] = []; 22 | 23 | // Encode assumed-honest and number of committee members 24 | membersBuffer.push(uint64ToBigEndian(assumedHonest)); 25 | membersBuffer.push(uint64ToBigEndian(numberOfMembers)); 26 | 27 | for (const key of publicKeys) { 28 | // Decode the base64 public key to get its hexadecimal representation 29 | const keyHex = Buffer.from(key, 'base64').toString('hex'); 30 | 31 | // Calculate the length of the public key in bytes (half the number of characters of its hexadecimal representation) 32 | const keyLength = keyHex.length / 2; 33 | 34 | // Encode the length as uint16 in big endian 35 | membersBuffer.push(uint16ToBigEndian(keyLength)); 36 | 37 | // Convert the hexadecimal public key to a byte array and add it to the buffer 38 | const keyBytes = Uint8Array.from(Buffer.from(keyHex, 'hex')); 39 | membersBuffer.push(keyBytes); 40 | } 41 | 42 | // Concatenate all Uint8Array elements into a single Uint8Array 43 | const totalLength = membersBuffer.reduce((acc, arr) => acc + arr.length, 0); 44 | const result = new Uint8Array(totalLength); 45 | let offset = 0; 46 | 47 | for (const arr of membersBuffer) { 48 | result.set(arr, offset); 49 | offset += arr.length; 50 | } 51 | 52 | return `0x${Array.from(result).map(byteToHex).join('')}`; 53 | } 54 | -------------------------------------------------------------------------------- /src/prepareKeysetHash.ts: -------------------------------------------------------------------------------- 1 | import { concatHex, Hex, hexToBigInt, keccak256, toHex } from 'viem'; 2 | 3 | export function prepareKeysetHash(keysetBytes: string): Hex { 4 | // prefix with 0x if not present 5 | const keysetBytesSanitized = keysetBytes.startsWith('0x') 6 | ? (keysetBytes as Hex) 7 | : (`0x${keysetBytes}` as Hex); 8 | 9 | // https://github.com/OffchainLabs/nitro-contracts/blob/v3.1.0/src/bridge/SequencerInbox.sol#L827-L828 10 | const keysetWord = hexToBigInt(keccak256(concatHex(['0xfe', keccak256(keysetBytesSanitized)]))); 11 | const keysetHash = toHex(keysetWord ^ (1n << 255n), { size: 32 }); 12 | 13 | return keysetHash; 14 | } 15 | -------------------------------------------------------------------------------- /src/prepareKeysetHash.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | 3 | import { prepareKeysetHash } from './prepareKeysetHash'; 4 | 5 | import { logs as logs_arbitrumNova } from './prepareKeysetHash.unit.testInputs-arbitrumNova'; 6 | import { logs as logs_xai } from './prepareKeysetHash.unit.testInputs-xai'; 7 | 8 | logs_arbitrumNova.forEach((log, index) => { 9 | it(`successfully calculates keyset hash (arbitrum nova example #${index + 1})`, () => { 10 | const { keysetBytes, keysetHash } = log.args; 11 | expect(prepareKeysetHash(keysetBytes)).toEqual(keysetHash); 12 | }); 13 | }); 14 | 15 | logs_xai.forEach((log, index) => { 16 | it(`successfully calculates keyset hash (xai example #${index + 1})`, () => { 17 | const { keysetBytes, keysetHash } = log.args; 18 | expect(prepareKeysetHash(keysetBytes)).toEqual(keysetHash); 19 | }); 20 | }); 21 | 22 | it(`successfully calculates keyset hash (no 0x prefix)`, () => { 23 | const { keysetBytes, keysetHash } = logs_arbitrumNova[0].args; 24 | expect(prepareKeysetHash(keysetBytes.slice(2))).toEqual(keysetHash); 25 | }); 26 | -------------------------------------------------------------------------------- /src/prepareUpgradeExecutorCallParameters.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | encodeFunctionData as viemEncodeFunctionData, 4 | EncodeFunctionDataParameters as ViemEncodeFunctionDataParameters, 5 | } from 'viem'; 6 | import { GetFunctionName } from './types/utils'; 7 | import { sequencerInboxABI } from './contracts/SequencerInbox'; 8 | import { arbOwnerABI } from './contracts/ArbOwner'; 9 | import { 10 | upgradeExecutorEncodeFunctionData, 11 | UpgradeExecutorFunctionName, 12 | } from './upgradeExecutorEncodeFunctionData'; 13 | 14 | type ABIs = typeof sequencerInboxABI | typeof arbOwnerABI; 15 | type FunctionName = GetFunctionName; 16 | 17 | type EncodeFunctionDataParameters< 18 | TAbi extends ABIs, 19 | TFunctionName extends FunctionName, 20 | > = ViemEncodeFunctionDataParameters; 21 | 22 | function encodeFunctionData>({ 23 | abi, 24 | functionName, 25 | args, 26 | }: EncodeFunctionDataParameters) { 27 | return viemEncodeFunctionData({ 28 | abi, 29 | functionName, 30 | args, 31 | } as unknown as ViemEncodeFunctionDataParameters); 32 | } 33 | 34 | export function prepareUpgradeExecutorCallParameters< 35 | TAbi extends ABIs, 36 | TFunctionName extends FunctionName, 37 | >( 38 | params: EncodeFunctionDataParameters & 39 | ( 40 | | { 41 | to: Address; 42 | upgradeExecutor: false; 43 | value?: bigint; 44 | } 45 | | { 46 | to: Address; 47 | upgradeExecutor: Address; 48 | value?: bigint; 49 | upgradeExecutorFunctionName?: Extract< 50 | UpgradeExecutorFunctionName, 51 | 'execute' | 'executeCall' 52 | >; 53 | } 54 | ), 55 | ) { 56 | const { upgradeExecutor, value = BigInt(0) } = params; 57 | if (!upgradeExecutor) { 58 | return { 59 | to: params.to, 60 | data: encodeFunctionData(params), 61 | value, 62 | }; 63 | } 64 | 65 | return { 66 | to: upgradeExecutor, 67 | data: upgradeExecutorEncodeFunctionData({ 68 | functionName: params.upgradeExecutorFunctionName ?? 'executeCall', 69 | args: [ 70 | params.to, // target 71 | encodeFunctionData(params), // targetCallData 72 | ], 73 | }), 74 | value, 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /src/rollupAdminLogicReadContract.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | Chain, 4 | GetFunctionArgs, 5 | PublicClient, 6 | ReadContractParameters, 7 | ReadContractReturnType, 8 | Transport, 9 | } from 'viem'; 10 | import { RollupAdminLogic__factory } from '@arbitrum/sdk/dist/lib/abi/factories/RollupAdminLogic__factory'; 11 | 12 | import { GetFunctionName } from './types/utils'; 13 | import { rollupABI } from './contracts/Rollup'; 14 | 15 | export type RollupAdminLogicAbi = typeof rollupABI; 16 | export type RollupAdminLogicFunctionName = GetFunctionName; 17 | 18 | export type RollupAdminLogicReadContractParameters< 19 | TFunctionName extends RollupAdminLogicFunctionName, 20 | > = { 21 | functionName: TFunctionName; 22 | rollup: Address; 23 | } & GetFunctionArgs; 24 | 25 | export type RollupAdminLogicReadContractReturnType< 26 | TFunctionName extends RollupAdminLogicFunctionName, 27 | > = ReadContractReturnType; 28 | 29 | export function rollupAdminLogicReadContract< 30 | TChain extends Chain | undefined, 31 | TFunctionName extends RollupAdminLogicFunctionName, 32 | >( 33 | client: PublicClient, 34 | params: RollupAdminLogicReadContractParameters, 35 | ): Promise> { 36 | return client.readContract({ 37 | address: params.rollup, 38 | abi: RollupAdminLogic__factory.abi, 39 | functionName: params.functionName, 40 | args: params.args, 41 | } as unknown as ReadContractParameters); 42 | } 43 | -------------------------------------------------------------------------------- /src/sequencerInboxReadContract.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | Chain, 4 | GetFunctionArgs, 5 | PublicClient, 6 | ReadContractReturnType, 7 | Transport, 8 | } from 'viem'; 9 | 10 | import { sequencerInboxABI } from './contracts/SequencerInbox'; 11 | import { 12 | SequencerInboxAbi, 13 | SequencerInboxFunctionName, 14 | } from './sequencerInboxPrepareTransactionRequest'; 15 | 16 | export type SequencerInboxReadContractParameters = 17 | { 18 | functionName: TFunctionName; 19 | // SequencerInbox address is different for each rollup, so user needs to pass it as a parameter 20 | sequencerInbox: Address; 21 | } & GetFunctionArgs; 22 | 23 | export type SequencerInboxReadContractReturnType = 24 | ReadContractReturnType; 25 | 26 | export function sequencerInboxReadContract< 27 | TChain extends Chain | undefined, 28 | TFunctionName extends SequencerInboxFunctionName, 29 | >( 30 | client: PublicClient, 31 | params: SequencerInboxReadContractParameters, 32 | ): Promise> { 33 | // @ts-ignore (todo: fix viem type issue) 34 | return client.readContract({ 35 | address: params.sequencerInbox, 36 | abi: sequencerInboxABI, 37 | functionName: params.functionName, 38 | args: params.args, 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /src/setAnyTrustFastConfirmerPrepareTransactionRequest.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | Chain, 4 | PrivateKeyAccount, 5 | PublicClient, 6 | Transport, 7 | encodeFunctionData, 8 | parseAbi, 9 | } from 'viem'; 10 | import { validateChain } from './utils/validateChain'; 11 | import { upgradeExecutorEncodeFunctionData } from './upgradeExecutorEncodeFunctionData'; 12 | 13 | /** 14 | * This type is for the parameters of the setAnyTrustFastConfirmerPrepareTransactionRequest function 15 | */ 16 | export type SetAnyTrustFastConfirmerPrepareTransactionRequestParams< 17 | TChain extends Chain | undefined, 18 | > = { 19 | publicClient: PublicClient; 20 | account: PrivateKeyAccount; 21 | rollup: Address; 22 | upgradeExecutor: Address; 23 | fastConfirmer: Address; 24 | }; 25 | 26 | /** 27 | * Prepares the transaction to set the AnyTrust fast confirmer address in the rollup contract 28 | * 29 | * Returns the transaction to sign and send to the blockchain. 30 | * 31 | * @param {SetAnyTrustFastConfirmerPrepareTransactionRequestParams} setAnyTrustFastConfirmerPrepareTransactionRequestParams {@link SetAnyTrustFastConfirmerPrepareTransactionRequestParams} 32 | * @param {PublicClient} setAnyTrustFastConfirmerPrepareTransactionRequestParams.publicClient - A Viem Public Client 33 | * @param {PrivateKeyAccount} setAnyTrustFastConfirmerPrepareTransactionRequestParams.account - The private key of the chain owner or an account with the executor role in the UpgradeExecutor 34 | * @param {Address} setAnyTrustFastConfirmerPrepareTransactionRequestParams.rollup - Address of the Rollup contract 35 | * @param {Address} setAnyTrustFastConfirmerPrepareTransactionRequestParams.upgradeExecutor - Address of the UpgradeExecutor contract 36 | * @param {Address} setAnyTrustFastConfirmerPrepareTransactionRequestParams.fastConfirmer - Address of the fast confirmer validator (usually a Safe multisig) 37 | * 38 | * @returns Promise<{@link TransactionRequest}> - the transaction to sign and send to the blockchain. 39 | */ 40 | export async function setAnyTrustFastConfirmerPrepareTransactionRequest< 41 | TChain extends Chain | undefined, 42 | >({ 43 | publicClient, 44 | account, 45 | rollup, 46 | upgradeExecutor, 47 | fastConfirmer, 48 | }: SetAnyTrustFastConfirmerPrepareTransactionRequestParams) { 49 | const chainId = validateChain(publicClient); 50 | 51 | // prepare the rollup transaction calldata 52 | const setAnyTrustFastConfirmerCalldata = encodeFunctionData({ 53 | abi: parseAbi(['function setAnyTrustFastConfirmer(address _anyTrustFastConfirmer)']), 54 | functionName: 'setAnyTrustFastConfirmer', 55 | args: [fastConfirmer], 56 | }); 57 | 58 | // prepare the transaction request 59 | // @ts-ignore (todo: fix viem type issue) 60 | const request = await publicClient.prepareTransactionRequest({ 61 | chain: publicClient.chain, 62 | to: upgradeExecutor, 63 | data: upgradeExecutorEncodeFunctionData({ 64 | functionName: 'executeCall', 65 | args: [ 66 | rollup, // target 67 | setAnyTrustFastConfirmerCalldata, // targetCallData 68 | ], 69 | }), 70 | account, 71 | }); 72 | 73 | return { ...request, chainId }; 74 | } 75 | -------------------------------------------------------------------------------- /src/setValidKeyset.ts: -------------------------------------------------------------------------------- 1 | import { PublicClient, Transport, Chain, WalletClient } from 'viem'; 2 | 3 | import { upgradeExecutorABI } from './contracts/UpgradeExecutor'; 4 | import { validateParentChain } from './types/ParentChain'; 5 | import { CoreContracts } from './types/CoreContracts'; 6 | import { setValidKeysetEncodeFunctionData } from './setValidKeysetEncodeFunctionData'; 7 | 8 | export type SetValidKeysetParams = { 9 | coreContracts: Pick; 10 | keyset: `0x${string}`; 11 | publicClient: PublicClient; 12 | walletClient: WalletClient; 13 | }; 14 | 15 | export async function setValidKeyset({ 16 | coreContracts, 17 | keyset, 18 | publicClient, 19 | walletClient, 20 | }: SetValidKeysetParams) { 21 | validateParentChain(publicClient); 22 | const account = walletClient.account?.address; 23 | 24 | if (typeof account === 'undefined') { 25 | throw new Error('account is undefined'); 26 | } 27 | 28 | // @ts-ignore (todo: fix viem type issue) 29 | const { request } = await publicClient.simulateContract({ 30 | address: coreContracts.upgradeExecutor, 31 | abi: upgradeExecutorABI, 32 | functionName: 'executeCall', 33 | args: [ 34 | coreContracts.sequencerInbox, // target 35 | setValidKeysetEncodeFunctionData(keyset), // targetCallData 36 | ], 37 | account, 38 | }); 39 | 40 | const hash = await walletClient.writeContract(request); 41 | const txReceipt = await publicClient.waitForTransactionReceipt({ hash }); 42 | 43 | return txReceipt; 44 | } 45 | -------------------------------------------------------------------------------- /src/setValidKeysetEncodeFunctionData.ts: -------------------------------------------------------------------------------- 1 | import { encodeFunctionData, parseAbi } from 'viem'; 2 | 3 | export function setValidKeysetEncodeFunctionData(keyset: `0x${string}`) { 4 | return encodeFunctionData({ 5 | abi: parseAbi(['function setValidKeyset(bytes keysetBytes)']), 6 | functionName: 'setValidKeyset', 7 | args: [keyset], 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /src/setValidKeysetPrepareTransactionRequest.ts: -------------------------------------------------------------------------------- 1 | import { Address, Chain } from 'viem'; 2 | 3 | import { validateParentChain } from './types/ParentChain'; 4 | import { SetValidKeysetParams } from './setValidKeyset'; 5 | import { setValidKeysetEncodeFunctionData } from './setValidKeysetEncodeFunctionData'; 6 | import { upgradeExecutorEncodeFunctionData } from './upgradeExecutorEncodeFunctionData'; 7 | 8 | export type SetValidKeysetPrepareTransactionRequestParams = Omit< 9 | SetValidKeysetParams, 10 | 'walletClient' 11 | > & { 12 | account: Address; 13 | }; 14 | 15 | export async function setValidKeysetPrepareTransactionRequest({ 16 | coreContracts, 17 | keyset, 18 | account, 19 | publicClient, 20 | }: SetValidKeysetPrepareTransactionRequestParams) { 21 | const { chainId } = validateParentChain(publicClient); 22 | 23 | // @ts-ignore (todo: fix viem type issue) 24 | const request = await publicClient.prepareTransactionRequest({ 25 | chain: publicClient.chain, 26 | to: coreContracts.upgradeExecutor, 27 | data: upgradeExecutorEncodeFunctionData({ 28 | functionName: 'executeCall', 29 | args: [ 30 | coreContracts.sequencerInbox, // target 31 | setValidKeysetEncodeFunctionData(keyset), // targetCallData 32 | ], 33 | }), 34 | account, 35 | }); 36 | 37 | return { ...request, chainId }; 38 | } 39 | -------------------------------------------------------------------------------- /src/types/Actions.ts: -------------------------------------------------------------------------------- 1 | import { Address, PrepareTransactionRequestReturnType } from 'viem'; 2 | import { Prettify } from './utils'; 3 | 4 | type isEmptyObject = Args extends Record ? true : false; 5 | 6 | /** 7 | * Actions require contract address, but as part of decorators, the address might have been passed already to the decorator. 8 | * 9 | * If the address was passed to the decorator, it's now optional (we still allow overrides of the address per action). 10 | * If the action doesn't have any other parameters beside the contract address, then parameters can either be { contract: address } or void 11 | */ 12 | export type ActionParameters = Prettify< 13 | Curried extends false 14 | ? isEmptyObject extends true 15 | ? { [key in ContractName]: Address } // Contract wasn't curried. Args is an empty object. Only requires the contract name 16 | : { params: Args } & { [key in ContractName]: Address } // Contract wasn't curried. Args is not empty. Requires both params and contract name 17 | : isEmptyObject extends true 18 | ? { [key in ContractName]: Address } | void // Contract was curried. Args is empty. Only requires the contract name. Allows no parameters 19 | : { params: Args } & { [key in ContractName]?: Address } // Contract was curried. Args is not empty. Requires params, contract name is optional 20 | >; 21 | 22 | export type WithAccount = Args & { 23 | account: Address; 24 | }; 25 | 26 | export type WithUpgradeExecutor = Args & { 27 | upgradeExecutor: Address | false; 28 | }; 29 | 30 | export type PrepareTransactionRequestReturnTypeWithChainId = PrepareTransactionRequestReturnType & { 31 | chainId: number; 32 | }; 33 | -------------------------------------------------------------------------------- /src/types/ChainConfig.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'viem'; 2 | 3 | export type ChainConfigArbitrumParams = { 4 | EnableArbOS: boolean; 5 | AllowDebugPrecompiles: boolean; 6 | InitialArbOSVersion: number; 7 | InitialChainOwner: Address; 8 | DataAvailabilityCommittee: boolean; 9 | GenesisBlockNum: number; 10 | MaxCodeSize: number; 11 | MaxInitCodeSize: number; 12 | }; 13 | 14 | export type ChainConfig = { 15 | chainId: number; 16 | homesteadBlock: number; 17 | daoForkBlock: null; 18 | daoForkSupport: boolean; 19 | eip150Block: number; 20 | eip150Hash: string; 21 | eip155Block: number; 22 | eip158Block: number; 23 | byzantiumBlock: number; 24 | constantinopleBlock: number; 25 | petersburgBlock: number; 26 | istanbulBlock: number; 27 | muirGlacierBlock: number; 28 | berlinBlock: number; 29 | londonBlock: number; 30 | clique: { 31 | period: number; 32 | epoch: number; 33 | }; 34 | arbitrum: ChainConfigArbitrumParams; 35 | }; 36 | -------------------------------------------------------------------------------- /src/types/CoreContracts.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'viem'; 2 | 3 | export type CoreContracts = { 4 | rollup: Address; 5 | nativeToken: Address; 6 | inbox: Address; 7 | outbox: Address; 8 | rollupEventInbox: Address; 9 | challengeManager: Address; 10 | adminProxy: Address; 11 | sequencerInbox: Address; 12 | bridge: Address; 13 | upgradeExecutor: Address; 14 | validatorUtils?: Address; 15 | validatorWalletCreator: Address; 16 | deployedAtBlockNumber: number; 17 | }; 18 | -------------------------------------------------------------------------------- /src/types/NodeConfig.ts: -------------------------------------------------------------------------------- 1 | import { ChainConfig } from './ChainConfig'; 2 | 3 | export type NodeConfigChainInfoJson = [ 4 | { 5 | 'chain-id': number; 6 | 'parent-chain-id': number; 7 | 'parent-chain-is-arbitrum': boolean; 8 | 'chain-name': string; 9 | 'chain-config': ChainConfig; 10 | 'rollup': { 11 | 'bridge': string; 12 | 'inbox': string; 13 | 'sequencer-inbox': string; 14 | 'rollup': string; 15 | 'validator-utils'?: string; 16 | 'validator-wallet-creator': string; 17 | 'stake-token': string; 18 | 'deployed-at': number; 19 | }; 20 | }, 21 | ]; 22 | 23 | export type NodeConfigDataAvailabilityRpcAggregatorBackendsJson = [ 24 | { 25 | url: string; 26 | pubkey: string; 27 | signermask: number; 28 | }, 29 | ]; 30 | -------------------------------------------------------------------------------- /src/types/ParentChain.ts: -------------------------------------------------------------------------------- 1 | import { Client, Transport, Chain } from 'viem'; 2 | 3 | import { chains, getCustomParentChains, nitroTestnodeL3 } from '../chains'; 4 | 5 | // exclude nitro-testnode L3 from the list of parent chains 6 | export type ParentChain = Exclude<(typeof chains)[number], { id: typeof nitroTestnodeL3.id }>; 7 | export type ParentChainId = ParentChain['id']; 8 | 9 | function isCustomParentChain(chainId: number): boolean { 10 | const ids = getCustomParentChains().map((chain) => chain.id); 11 | return ids.includes(chainId); 12 | } 13 | 14 | function isValidParentChainId(parentChainId: number | undefined): parentChainId is number { 15 | const ids = [...chains, ...getCustomParentChains()] 16 | // exclude nitro-testnode L3 from the list of parent chains 17 | .filter((chain) => chain.id !== nitroTestnodeL3.id) 18 | .map((chain) => chain.id) as Number[]; 19 | return ids.includes(Number(parentChainId)); 20 | } 21 | 22 | export function validateParentChain( 23 | chainIdOrClient: number | Client, 24 | ): { chainId: number; isCustom: true } | { chainId: ParentChainId; isCustom: false } { 25 | const chainId = typeof chainIdOrClient === 'number' ? chainIdOrClient : chainIdOrClient.chain?.id; 26 | 27 | if (!isValidParentChainId(chainId)) { 28 | throw new Error(`Parent chain not supported: ${chainId}`); 29 | } 30 | 31 | if (isCustomParentChain(chainId)) { 32 | return { chainId, isCustom: true }; 33 | } 34 | 35 | return { chainId: chainId as ParentChainId, isCustom: false }; 36 | } 37 | -------------------------------------------------------------------------------- /src/types/TokenBridgeContracts.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'viem'; 2 | 3 | type TokenBridgeParentChainContracts = { 4 | router: Address; 5 | standardGateway: Address; 6 | customGateway: Address; 7 | wethGateway: Address; 8 | weth: Address; 9 | multicall: Address; 10 | }; 11 | 12 | type TokenBridgeOrbitChainContracts = { 13 | router: Address; 14 | standardGateway: Address; 15 | customGateway: Address; 16 | wethGateway: Address; 17 | weth: Address; 18 | proxyAdmin: Address; 19 | beaconProxyFactory: Address; 20 | upgradeExecutor: Address; 21 | multicall: Address; 22 | }; 23 | 24 | export type TokenBridgeContracts = { 25 | parentChainContracts: TokenBridgeParentChainContracts; 26 | orbitChainContracts: TokenBridgeOrbitChainContracts; 27 | }; 28 | -------------------------------------------------------------------------------- /src/types/createRollupTypes.ts: -------------------------------------------------------------------------------- 1 | import { Address, GetFunctionArgs } from 'viem'; 2 | 3 | import { rollupCreatorABI as rollupCreatorV3Dot1ABI } from '../contracts/RollupCreator'; 4 | import { rollupCreatorABI as rollupCreatorV2Dot1ABI } from '../contracts/RollupCreator/v2.1'; 5 | import { rollupCreatorABI as rollupCreatorV1Dot1ABI } from '../contracts/RollupCreator/v1.1'; 6 | 7 | import { Prettify } from './utils'; 8 | 9 | export type RollupCreatorVersion = 'v3.1' | 'v2.1' | 'v1.1'; 10 | export type RollupCreatorLatestVersion = Extract; 11 | 12 | export type RollupCreatorABI = 13 | // 14 | TVersion extends 'v3.1' 15 | ? typeof rollupCreatorV3Dot1ABI 16 | : TVersion extends 'v2.1' 17 | ? typeof rollupCreatorV2Dot1ABI 18 | : TVersion extends 'v1.1' 19 | ? typeof rollupCreatorV1Dot1ABI 20 | : never; 21 | 22 | export type CreateRollupFunctionInputs< 23 | TVersion extends RollupCreatorVersion = RollupCreatorLatestVersion, 24 | > = GetFunctionArgs, 'createRollup'>['args']; 25 | 26 | type GetCreateRollupRequiredKeys< 27 | TVersion extends RollupCreatorVersion = RollupCreatorLatestVersion, 28 | > = 29 | // 30 | TVersion extends 'v3.1' 31 | ? 'config' | 'batchPosters' | 'validators' 32 | : TVersion extends 'v2.1' 33 | ? 'config' | 'batchPosters' | 'validators' 34 | : TVersion extends 'v1.1' 35 | ? 'config' | 'batchPoster' | 'validators' 36 | : never; 37 | 38 | export type CreateRollupParams = 39 | Prettify< 40 | // @ts-ignore this works perfectly fine, not sure why typescript is complaining 41 | Pick[0], GetCreateRollupRequiredKeys> & 42 | // @ts-ignore this works perfectly fine, not sure why typescript is complaining 43 | Partial[0], GetCreateRollupRequiredKeys>> 44 | >; 45 | 46 | export type WithRollupCreatorAddressOverride = T & { 47 | /** 48 | * Specifies a custom address for the RollupCreator. By default, the address will be automatically detected based on the provided chain. 49 | */ 50 | rollupCreatorAddressOverride?: Address; 51 | }; 52 | -------------------------------------------------------------------------------- /src/types/createTokenBridgeTypes.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'viem'; 2 | 3 | export type WithTokenBridgeCreatorAddressOverride = T & { 4 | /** 5 | * Specifies a custom address for the TokenBridgeCreator. By default, the address will be automatically detected based on the provided chain. 6 | */ 7 | tokenBridgeCreatorAddressOverride?: Address; 8 | }; 9 | -------------------------------------------------------------------------------- /src/types/utils.ts: -------------------------------------------------------------------------------- 1 | import { Abi } from 'viem'; 2 | 3 | // https://twitter.com/mattpocockuk/status/1622730173446557697 4 | export type Prettify = { 5 | [K in keyof T]: T[K]; 6 | } & {}; 7 | 8 | export type GetFunctionName = Extract['name']; 9 | 10 | /** 11 | * Creates a new type by making the specified keys required while keeping the remaining keys optional. 12 | * 13 | * @template T - The original object type. 14 | * @template K - The keys in `T` that should be required in the resulting type. 15 | * 16 | * @example 17 | * type Original = { 18 | * a: number; 19 | * b: string; 20 | * c: boolean; 21 | * }; 22 | * 23 | * type RequiredAandB = RequireSome; 24 | * // Resulting type: 25 | * // { 26 | * // a: number; // Required 27 | * // b: string; // Required 28 | * // c?: boolean; // Optional 29 | * //} 30 | */ 31 | export type RequireSome = Prettify< 32 | { 33 | [P in K]: T[P]; 34 | } & { 35 | [P in Exclude]?: T[P]; 36 | } 37 | >; 38 | 39 | /** 40 | * Creates a new type by excluding the specified keys from the original type. 41 | * 42 | * @template T - The original object type. 43 | * @template K - The keys in `T` that should be excluded from the resulting type. 44 | * 45 | * @example 46 | * type Original = { 47 | * a: number; 48 | * b: string; 49 | * c: boolean; 50 | * d: string; 51 | * }; 52 | * 53 | * type ExcludeD = ExcludeSome; 54 | * // Resulting type: 55 | * // { 56 | * // a: number; 57 | * // b: string; 58 | * // c: boolean; 59 | * //} 60 | */ 61 | export type ExcludeSome = Prettify<{ 62 | [P in Exclude]: T[P]; 63 | }>; 64 | -------------------------------------------------------------------------------- /src/upgradeExecutorEncodeFunctionData.ts: -------------------------------------------------------------------------------- 1 | import { encodeFunctionData, EncodeFunctionDataParameters, keccak256, toHex } from 'viem'; 2 | 3 | import { upgradeExecutorABI } from './contracts/UpgradeExecutor'; 4 | import { GetFunctionName, Prettify } from './types/utils'; 5 | 6 | // Roles 7 | /** 8 | * 0xa49807205ce4d355092ef5a8a18f56e8913cf4a201fbe287825b095693c21775 9 | */ 10 | export const UPGRADE_EXECUTOR_ROLE_ADMIN = keccak256(toHex('ADMIN_ROLE')); 11 | 12 | /** 13 | * 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 14 | */ 15 | export const UPGRADE_EXECUTOR_ROLE_EXECUTOR = keccak256(toHex('EXECUTOR_ROLE')); 16 | export type UpgradeExecutorRole = 17 | | typeof UPGRADE_EXECUTOR_ROLE_ADMIN 18 | | typeof UPGRADE_EXECUTOR_ROLE_EXECUTOR; 19 | 20 | // Types for upgradeExecutorEncodeFunctionData 21 | export type UpgradeExecutorAbi = typeof upgradeExecutorABI; 22 | export type UpgradeExecutorFunctionName = GetFunctionName; 23 | export type UpgradeExecutorEncodeFunctionDataParameters< 24 | TFunctionName extends UpgradeExecutorFunctionName, 25 | > = Prettify, 'abi'>>; 26 | 27 | // Encodes a function call to be sent through the UpgradeExecutor 28 | export function upgradeExecutorEncodeFunctionData< 29 | TFunctionName extends UpgradeExecutorFunctionName, 30 | >({ functionName, args }: UpgradeExecutorEncodeFunctionDataParameters) { 31 | // @ts-ignore (todo: fix viem type issue) 32 | return encodeFunctionData({ 33 | abi: upgradeExecutorABI, 34 | functionName, 35 | args, 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/assertChainId.ts: -------------------------------------------------------------------------------- 1 | import { Chain, Client, Transport } from 'viem'; 2 | 3 | export function assertChainId( 4 | client: Client, 5 | ): number { 6 | const chainId = client.chain?.id; 7 | 8 | if (!chainId) { 9 | throw new Error('Missing chain for publicClient'); 10 | } 11 | 12 | return chainId; 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/decimals.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'ethers'; 2 | import { 3 | scaleFrom18DecimalsToNativeTokenDecimals as ethers_scaleFrom18DecimalsToNativeTokenDecimals, 4 | scaleFromNativeTokenDecimalsTo18Decimals as ethers_scaleFromNativeTokenDecimalsTo18Decimals, 5 | } from '@arbitrum/sdk'; 6 | 7 | /** 8 | * Scales a value from 18 decimals to the number of decimals of the native token. 9 | * 10 | * @param param.amount The value to scale. 11 | * @param param.decimals The number of decimals of the native token. 12 | * 13 | * @returns The scaled value. 14 | */ 15 | export function scaleFrom18DecimalsToNativeTokenDecimals({ 16 | amount, 17 | decimals, 18 | }: { 19 | amount: bigint; 20 | decimals: number; 21 | }): bigint { 22 | const amountBigNumber = BigNumber.from(amount); 23 | const result = ethers_scaleFrom18DecimalsToNativeTokenDecimals({ 24 | amount: amountBigNumber, 25 | decimals, 26 | }); 27 | return BigInt(result.toString()); 28 | } 29 | 30 | /** 31 | * Scales a value from the number of decimals of the native token to 18 decimals. 32 | * 33 | * @param param.amount The value to scale. 34 | * @param param.decimals The number of decimals of the native token. 35 | * 36 | * @returns The scaled value. 37 | */ 38 | export function scaleFromNativeTokenDecimalsTo18Decimals({ 39 | amount, 40 | decimals, 41 | }: { 42 | amount: bigint; 43 | decimals: number; 44 | }): bigint { 45 | const amountBigNumber = BigNumber.from(amount); 46 | const result = ethers_scaleFromNativeTokenDecimalsTo18Decimals({ 47 | amount: amountBigNumber, 48 | decimals, 49 | }); 50 | return BigInt(result.toString()); 51 | } 52 | -------------------------------------------------------------------------------- /src/utils/erc20.ts: -------------------------------------------------------------------------------- 1 | import { Address, PublicClient, Transport, Chain, WalletClient, encodeFunctionData } from 'viem'; 2 | 3 | import { erc20ABI } from '../contracts/ERC20'; 4 | 5 | function approveEncodeFunctionData({ spender, amount }: { spender: Address; amount: bigint }) { 6 | return encodeFunctionData({ 7 | abi: erc20ABI, 8 | functionName: 'approve', 9 | args: [spender, amount], 10 | }); 11 | } 12 | 13 | export type ApprovePrepareTransactionRequestProps = { 14 | address: Address; 15 | owner: Address; 16 | spender: Address; 17 | amount: bigint; 18 | publicClient: PublicClient; 19 | }; 20 | 21 | export async function approvePrepareTransactionRequest({ 22 | address, 23 | owner, 24 | spender, 25 | amount, 26 | publicClient, 27 | }: ApprovePrepareTransactionRequestProps) { 28 | // @ts-ignore (todo: fix viem type issue) 29 | return await publicClient.prepareTransactionRequest({ 30 | chain: publicClient.chain, 31 | to: address, 32 | data: approveEncodeFunctionData({ spender, amount }), 33 | value: BigInt(0), 34 | account: owner, 35 | }); 36 | } 37 | 38 | export type ApproveProps = { 39 | address: Address; 40 | spender: Address; 41 | amount: bigint; 42 | publicClient: PublicClient; 43 | walletClient: WalletClient; 44 | }; 45 | 46 | export async function approve({ 47 | address, 48 | spender, 49 | amount, 50 | publicClient, 51 | walletClient, 52 | }: ApproveProps) { 53 | const account = walletClient.account?.address; 54 | 55 | if (typeof account === 'undefined') { 56 | throw new Error('[utils/erc20::approve] account is undefined'); 57 | } 58 | 59 | // @ts-ignore (todo: fix viem type issue) 60 | const { request } = await publicClient.simulateContract({ 61 | address: address, 62 | abi: erc20ABI, 63 | functionName: 'approve', 64 | args: [spender, amount], 65 | account, 66 | }); 67 | 68 | const hash = await walletClient.writeContract(request); 69 | return await publicClient.waitForTransactionReceipt({ hash: hash }); 70 | } 71 | 72 | export type FetchAllowanceProps = { 73 | address: Address; 74 | owner: Address; 75 | spender: Address; 76 | publicClient: PublicClient; 77 | }; 78 | 79 | export async function fetchAllowance({ 80 | address, 81 | owner, 82 | spender, 83 | publicClient, 84 | }: FetchAllowanceProps) { 85 | return publicClient.readContract({ 86 | address, 87 | abi: erc20ABI, 88 | functionName: 'allowance', 89 | args: [owner, spender], 90 | }); 91 | } 92 | 93 | export type FetchDecimalsProps = { 94 | address: Address; 95 | publicClient: PublicClient; 96 | }; 97 | 98 | export function fetchDecimals({ 99 | address, 100 | publicClient, 101 | }: FetchDecimalsProps) { 102 | return publicClient.readContract({ 103 | address, 104 | abi: erc20ABI, 105 | functionName: 'decimals', 106 | }); 107 | } 108 | -------------------------------------------------------------------------------- /src/utils/gasOverrides.ts: -------------------------------------------------------------------------------- 1 | export type GasOverrideOptions = { 2 | base?: bigint; 3 | percentIncrease?: bigint; 4 | }; 5 | 6 | export type TransactionRequestGasOverrides = { 7 | gasLimit?: GasOverrideOptions; 8 | }; 9 | 10 | export function applyPercentIncrease({ 11 | base, 12 | percentIncrease = 0n, 13 | }: { 14 | base: bigint; 15 | percentIncrease?: bigint; 16 | }) { 17 | return base + (base * percentIncrease) / 100n; 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/generateChainId.ts: -------------------------------------------------------------------------------- 1 | export function generateChainId() { 2 | return Math.floor(Math.random() * 100000000000) + 1; 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/getArbOSVersion.ts: -------------------------------------------------------------------------------- 1 | import { PublicClient, Transport, Chain, parseAbi } from 'viem'; 2 | import { ARB_SYS_ADDRESS } from '@arbitrum/sdk/dist/lib/dataEntities/constants'; 3 | 4 | /** 5 | * Returns the the ArbOS version from the provider passed in parameter. 6 | * 7 | * @param arbitrumPublicClient - viem public client 8 | * @throws if the provider is not an arbitrum chain 9 | * @returns the ArbOS version 10 | */ 11 | export async function getArbOSVersion( 12 | arbitrumPublicClient: PublicClient, 13 | ): Promise { 14 | const arbOSVersion = await arbitrumPublicClient.readContract({ 15 | address: ARB_SYS_ADDRESS, 16 | abi: parseAbi(['function arbOSVersion() view returns (uint256)']), 17 | functionName: 'arbOSVersion', 18 | }); 19 | // 20 | /** 21 | * Version of the ArbOS is starting at 55 22 | * {@see https://github.com/OffchainLabs/nitro/blob/a20a1c70cc11ac52c7cfe6a20f00c880c2009a8f/precompiles/ArbSys.go#L62-L66} 23 | */ 24 | return Number(arbOSVersion) - 55; 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/getArbOSVersion.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | import { createPublicClient, http } from 'viem'; 3 | import { arbitrum as arbitrumOne, sepolia } from 'viem/chains'; 4 | 5 | import { getArbOSVersion } from './getArbOSVersion'; 6 | 7 | it('returns the ArbOS version of Arbitrum One', async () => { 8 | const arbitrumOneClient = createPublicClient({ 9 | chain: arbitrumOne, 10 | transport: http(), 11 | }); 12 | 13 | expect(await getArbOSVersion(arbitrumOneClient)).toBe(32); 14 | }); 15 | 16 | it('throws if the chain is not an Arbitrum chain', async () => { 17 | const sepoliaClient = createPublicClient({ 18 | chain: sepolia, 19 | transport: http('https://gateway.tenderly.co/public/sepolia'), 20 | }); 21 | 22 | await expect(getArbOSVersion(sepoliaClient)).rejects.toThrowError(); 23 | }); 24 | -------------------------------------------------------------------------------- /src/utils/getBlockExplorerUrl.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from 'viem'; 2 | 3 | export function getBlockExplorerUrl(chain: Chain | undefined) { 4 | return chain?.blockExplorers?.default.url; 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/getClientVersion.ts: -------------------------------------------------------------------------------- 1 | import { Client, Transport, Chain } from 'viem'; 2 | 3 | type JsonRpcResult = { 4 | jsonrpc: '2.0'; 5 | id: 1; 6 | result: string; 7 | }; 8 | 9 | export async function getClientVersion( 10 | clientOrRpcUrl: Client | string, 11 | ): Promise { 12 | const rpcUrl = 13 | typeof clientOrRpcUrl === 'string' 14 | ? clientOrRpcUrl 15 | : clientOrRpcUrl.chain?.rpcUrls.default.http[0]; 16 | 17 | if (typeof rpcUrl === 'undefined') { 18 | throw new Error(`[getClientVersion] invalid rpc url: ${rpcUrl}`); 19 | } 20 | 21 | const response = await fetch(rpcUrl, { 22 | method: 'POST', 23 | headers: { 24 | 'Content-Type': 'application/json', 25 | }, 26 | body: JSON.stringify({ 27 | jsonrpc: '2.0', 28 | method: 'web3_clientVersion', 29 | params: [], 30 | id: 1, 31 | }), 32 | }); 33 | 34 | return ((await response.json()) as unknown as JsonRpcResult).result; 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/getClientVersion.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | import { createPublicClient, http } from 'viem'; 3 | import { arbitrumSepolia } from 'viem/chains'; 4 | 5 | import { getClientVersion } from './getClientVersion'; 6 | 7 | const arbitrumSepoliaPublicClient = createPublicClient({ 8 | chain: arbitrumSepolia, 9 | transport: http(), 10 | }); 11 | 12 | it('fetches client version with public client', async () => { 13 | const clientVersion = await getClientVersion(arbitrumSepoliaPublicClient); 14 | expect(clientVersion.startsWith('nitro/')).toBeTruthy(); 15 | }); 16 | 17 | it('fetches client version with rpc url', async () => { 18 | const clientVersion = await getClientVersion('https://sepolia-rollup.arbitrum.io/rpc'); 19 | expect(clientVersion.startsWith('nitro/')).toBeTruthy(); 20 | }); 21 | -------------------------------------------------------------------------------- /src/utils/getEarliestRollupCreatorDeploymentBlockNumber.ts: -------------------------------------------------------------------------------- 1 | import { Chain, PublicClient, Transport } from 'viem'; 2 | import { 3 | mainnet, 4 | arbitrumOne, 5 | arbitrumNova, 6 | base, 7 | sepolia, 8 | arbitrumSepolia, 9 | baseSepolia, 10 | nitroTestnodeL1, 11 | nitroTestnodeL2, 12 | } from '../chains'; 13 | import { validateParentChain } from '../types/ParentChain'; 14 | 15 | const earliestRollupCreatorDeploymentBlockNumber: Record = { 16 | // mainnet L1 17 | [mainnet.id]: 18_736_164n, 18 | // mainnet L2 19 | [arbitrumOne.id]: 150_599_584n, 20 | [arbitrumNova.id]: 47_798_739n, 21 | [base.id]: 12_978_604n, 22 | // testnet L1 23 | [sepolia.id]: 4_741_823n, 24 | // testnet L2 25 | [arbitrumSepolia.id]: 654_628n, 26 | [baseSepolia.id]: 10_606_961n, 27 | // local nitro-testnode 28 | [nitroTestnodeL1.id]: 0n, 29 | [nitroTestnodeL2.id]: 0n, 30 | }; 31 | 32 | export function getEarliestRollupCreatorDeploymentBlockNumber( 33 | publicClient: PublicClient, 34 | ) { 35 | const { chainId, isCustom } = validateParentChain(publicClient); 36 | 37 | if (isCustom) { 38 | return 0n; 39 | } 40 | 41 | return earliestRollupCreatorDeploymentBlockNumber[chainId]; 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/getImplementation.ts: -------------------------------------------------------------------------------- 1 | import { Chain, Transport, PublicClient, Address, zeroAddress } from 'viem'; 2 | 3 | export async function getImplementation({ 4 | client, 5 | address, 6 | }: { 7 | client: PublicClient; 8 | address: Address; 9 | }): Promise
{ 10 | const value = await client.getStorageAt({ 11 | address, 12 | // https://eips.ethereum.org/EIPS/eip-1967#logic-contract-address 13 | slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', 14 | }); 15 | 16 | if (typeof value === 'undefined') { 17 | return zeroAddress; 18 | } 19 | 20 | // strip zeros 21 | return `0x${value.slice(26)}`; 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/getImplementation.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | import { createPublicClient, http, zeroAddress } from 'viem'; 3 | import { arbitrumSepolia } from 'viem/chains'; 4 | 5 | import { getImplementation } from './getImplementation'; 6 | 7 | const arbitrumSepoliaPublicClient = createPublicClient({ 8 | chain: arbitrumSepolia, 9 | transport: http(), 10 | }); 11 | 12 | it('fetches no implementation address for RollupCreator v1.1.0 on Arbitrum Sepolia', async () => { 13 | const implementation = await getImplementation({ 14 | client: arbitrumSepoliaPublicClient, 15 | address: '0x06E341073b2749e0Bb9912461351f716DeCDa9b0', 16 | }); 17 | expect(implementation).toEqual(zeroAddress); 18 | }); 19 | 20 | it('fetches implementation address for TokenBridgeCreator v1.2.2 on Arbitrum Sepolia', async () => { 21 | const implementation = await getImplementation({ 22 | client: arbitrumSepoliaPublicClient, 23 | address: '0x56C486D3786fA26cc61473C499A36Eb9CC1FbD8E', 24 | }); 25 | expect(implementation).toEqual('0x6b6e01852716f2d7dab6b8729bb50e67be68bf04'); 26 | }); 27 | -------------------------------------------------------------------------------- /src/utils/getParentChainFromId.ts: -------------------------------------------------------------------------------- 1 | import { Chain, extractChain } from 'viem'; 2 | 3 | import { validateParentChain } from '../types/ParentChain'; 4 | import { chains, getCustomParentChains } from '../chains'; 5 | 6 | export function getParentChainFromId(chainId: number): Chain { 7 | const { chainId: parentChainId } = validateParentChain(chainId); 8 | 9 | return extractChain({ 10 | chains: [...chains, ...getCustomParentChains()], 11 | id: parentChainId, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/getParentChainLayer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | mainnet, 3 | sepolia, 4 | nitroTestnodeL1, 5 | arbitrumOne, 6 | arbitrumNova, 7 | base, 8 | arbitrumSepolia, 9 | baseSepolia, 10 | nitroTestnodeL2, 11 | } from '../chains'; 12 | import { ParentChainId } from '../types/ParentChain'; 13 | 14 | export function getParentChainLayer(parentChainId: ParentChainId): 1 | 2 { 15 | // doing switch here to make sure it's exhaustive when checking against `ParentChainId` 16 | switch (parentChainId) { 17 | // mainnet L1 18 | case mainnet.id: 19 | // testnet L1 20 | case sepolia.id: 21 | // local nitro-testnode L1 22 | case nitroTestnodeL1.id: 23 | return 1; 24 | 25 | // mainnet L2 26 | case arbitrumOne.id: 27 | case arbitrumNova.id: 28 | case base.id: 29 | // testnet L2 30 | case arbitrumSepolia.id: 31 | case baseSepolia.id: 32 | // local nitro-testnode L2 33 | case nitroTestnodeL2.id: 34 | return 2; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/getRollupCreatorAddress.ts: -------------------------------------------------------------------------------- 1 | import { Client, Transport, Chain, ChainContract, Address } from 'viem'; 2 | 3 | import { rollupCreatorAddress } from '../contracts/RollupCreator'; 4 | import { validateParentChain } from '../types/ParentChain'; 5 | 6 | export function getRollupCreatorAddress( 7 | client: Client, 8 | ): Address { 9 | const { chainId: parentChainId, isCustom: parentChainIsCustom } = validateParentChain(client); 10 | 11 | if (parentChainIsCustom) { 12 | const contract = client.chain?.contracts?.rollupCreator as ChainContract | undefined; 13 | const address = contract?.address; 14 | 15 | if (typeof address === 'undefined') { 16 | throw new Error( 17 | `Address for RollupCreator is missing on custom parent chain with id ${parentChainId}`, 18 | ); 19 | } 20 | 21 | return address; 22 | } 23 | 24 | return rollupCreatorAddress[parentChainId]; 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/getRollupCreatorAddress.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | import { createPublicClient, http } from 'viem'; 3 | import { sepolia } from 'viem/chains'; 4 | 5 | import { getRollupCreatorAddress } from './getRollupCreatorAddress'; 6 | import { registerCustomParentChain } from '../chains'; 7 | 8 | import { testHelper_createCustomParentChain } from '../testHelpers'; 9 | 10 | it(`successfully returns address for Sepolia`, () => { 11 | const client = createPublicClient({ 12 | chain: sepolia, 13 | transport: http(), 14 | }); 15 | 16 | expect(getRollupCreatorAddress(client)).toEqual('0x687Bc1D23390875a868Db158DA1cDC8998E31640'); 17 | }); 18 | 19 | it(`fails to return address for an unrecognized parent chain`, () => { 20 | const chain = testHelper_createCustomParentChain(); 21 | 22 | const client = createPublicClient({ 23 | chain, 24 | transport: http(), 25 | }); 26 | 27 | expect(() => getRollupCreatorAddress(client)).toThrowError( 28 | `Parent chain not supported: ${chain.id}`, 29 | ); 30 | }); 31 | 32 | it(`successfully returns address for a registered custom parent chain`, () => { 33 | const chain = testHelper_createCustomParentChain(); 34 | 35 | registerCustomParentChain(chain); 36 | 37 | const client = createPublicClient({ 38 | chain, 39 | transport: http(), 40 | }); 41 | 42 | expect(getRollupCreatorAddress(client)).toEqual(chain.contracts.rollupCreator.address); 43 | }); 44 | -------------------------------------------------------------------------------- /src/utils/getTokenBridgeCreatorAddress.ts: -------------------------------------------------------------------------------- 1 | import { Client, Transport, Chain, ChainContract } from 'viem'; 2 | 3 | import { tokenBridgeCreatorAddress } from '../contracts/TokenBridgeCreator'; 4 | import { validateParentChain } from '../types/ParentChain'; 5 | 6 | export function getTokenBridgeCreatorAddress( 7 | client: Client, 8 | ) { 9 | const { chainId: parentChainId, isCustom: parentChainIsCustom } = validateParentChain(client); 10 | 11 | if (parentChainIsCustom) { 12 | const contract = client.chain?.contracts?.tokenBridgeCreator as ChainContract | undefined; 13 | const address = contract?.address; 14 | 15 | if (typeof address === 'undefined') { 16 | throw new Error( 17 | `Address for TokenBridgeCreator is missing on custom parent chain with id ${parentChainId}`, 18 | ); 19 | } 20 | 21 | return address; 22 | } 23 | 24 | return tokenBridgeCreatorAddress[parentChainId]; 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/getTokenBridgeCreatorAddress.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | import { createPublicClient, http } from 'viem'; 3 | import { sepolia } from 'viem/chains'; 4 | 5 | import { getTokenBridgeCreatorAddress } from './getTokenBridgeCreatorAddress'; 6 | import { registerCustomParentChain } from '../chains'; 7 | 8 | import { testHelper_createCustomParentChain } from '../testHelpers'; 9 | 10 | it(`successfully returns address for Sepolia`, () => { 11 | const client = createPublicClient({ 12 | chain: sepolia, 13 | transport: http(), 14 | }); 15 | 16 | expect(getTokenBridgeCreatorAddress(client)).toEqual( 17 | '0x7edb2dfBeEf9417e0454A80c51EE0C034e45a570', 18 | ); 19 | }); 20 | 21 | it(`fails to return address for an unrecognized parent chain`, () => { 22 | const chain = testHelper_createCustomParentChain(); 23 | 24 | const client = createPublicClient({ 25 | chain, 26 | transport: http(), 27 | }); 28 | 29 | expect(() => getTokenBridgeCreatorAddress(client)).toThrowError( 30 | `Parent chain not supported: ${chain.id}`, 31 | ); 32 | }); 33 | 34 | it(`successfully returns address for a registered custom parent chain`, () => { 35 | const chain = testHelper_createCustomParentChain(); 36 | 37 | registerCustomParentChain(chain); 38 | 39 | const client = createPublicClient({ 40 | chain, 41 | transport: http(), 42 | }); 43 | 44 | expect(getTokenBridgeCreatorAddress(client)).toEqual(chain.contracts.tokenBridgeCreator.address); 45 | }); 46 | -------------------------------------------------------------------------------- /src/utils/getWethAddress.ts: -------------------------------------------------------------------------------- 1 | import { Client, Transport, Chain, ChainContract, Address } from 'viem'; 2 | 3 | import { wethAddress } from '../contracts/WETH'; 4 | import { validateParentChain } from '../types/ParentChain'; 5 | 6 | export function getWethAddress( 7 | client: Client, 8 | ): Address { 9 | const { chainId: parentChainId, isCustom: parentChainIsCustom } = validateParentChain(client); 10 | 11 | if (parentChainIsCustom) { 12 | const contract = client.chain?.contracts?.weth as ChainContract | undefined; 13 | const address = contract?.address; 14 | 15 | if (typeof address === 'undefined') { 16 | throw new Error( 17 | `Address for WETH is missing on custom parent chain with id ${parentChainId}`, 18 | ); 19 | } 20 | 21 | return address; 22 | } 23 | 24 | return wethAddress[parentChainId]; 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/getWethAddress.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | import { createPublicClient, http } from 'viem'; 3 | import { sepolia } from 'viem/chains'; 4 | 5 | import { getWethAddress } from './getWethAddress'; 6 | import { registerCustomParentChain } from '../chains'; 7 | 8 | import { testHelper_createCustomParentChain } from '../testHelpers'; 9 | 10 | it(`successfully returns address for Sepolia`, () => { 11 | const client = createPublicClient({ 12 | chain: sepolia, 13 | transport: http(), 14 | }); 15 | 16 | expect(getWethAddress(client)).toEqual('0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9'); 17 | }); 18 | 19 | it(`fails to return address for an unrecognized parent chain`, () => { 20 | const chain = testHelper_createCustomParentChain(); 21 | 22 | const client = createPublicClient({ 23 | chain, 24 | transport: http(), 25 | }); 26 | 27 | expect(() => getWethAddress(client)).toThrowError(`Parent chain not supported: ${chain.id}`); 28 | }); 29 | 30 | it(`successfully returns address for a registered custom parent chain`, () => { 31 | const chain = testHelper_createCustomParentChain(); 32 | 33 | registerCustomParentChain(chain); 34 | 35 | const client = createPublicClient({ 36 | chain, 37 | transport: http(), 38 | }); 39 | 40 | expect(getWethAddress(client)).toEqual(chain.contracts.weth.address); 41 | }); 42 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { generateChainId } from './generateChainId'; 2 | import { getParentChainFromId } from './getParentChainFromId'; 3 | import { getParentChainLayer } from './getParentChainLayer'; 4 | import { sanitizePrivateKey } from './sanitizePrivateKey'; 5 | import { getArbOSVersion } from './getArbOSVersion'; 6 | import { getClientVersion } from './getClientVersion'; 7 | import { getRollupCreatorAddress } from './getRollupCreatorAddress'; 8 | import { getTokenBridgeCreatorAddress } from './getTokenBridgeCreatorAddress'; 9 | import { getWethAddress } from './getWethAddress'; 10 | 11 | export { 12 | generateChainId, 13 | getParentChainFromId, 14 | getParentChainLayer, 15 | sanitizePrivateKey, 16 | getArbOSVersion, 17 | getClientVersion, 18 | getRollupCreatorAddress, 19 | getTokenBridgeCreatorAddress, 20 | getWethAddress, 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/isCustomFeeTokenChain.ts: -------------------------------------------------------------------------------- 1 | import { Address, PublicClient, Transport, Chain, parseAbi } from 'viem'; 2 | 3 | export async function isCustomFeeTokenChain({ 4 | rollup, 5 | parentChainPublicClient, 6 | }: { 7 | rollup: Address; 8 | parentChainPublicClient: PublicClient; 9 | }) { 10 | const bridge = await parentChainPublicClient.readContract({ 11 | address: rollup, 12 | abi: parseAbi(['function bridge() view returns (address)']), 13 | functionName: 'bridge', 14 | }); 15 | 16 | try { 17 | await parentChainPublicClient.readContract({ 18 | address: bridge, 19 | abi: parseAbi(['function nativeToken() view returns (address)']), 20 | functionName: 'nativeToken', 21 | }); 22 | } catch { 23 | return false; 24 | } 25 | 26 | return true; 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/isNonZeroAddress.ts: -------------------------------------------------------------------------------- 1 | import { Address, isAddress, zeroAddress } from 'viem'; 2 | 3 | export function isNonZeroAddress(address: Address | undefined): address is Address { 4 | return typeof address !== 'undefined' && isAddress(address) && address !== zeroAddress; 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/registerNewNetwork.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { createPublicClient, http } from 'viem'; 3 | 4 | import { prepareArbitrumNetwork } from './registerNewNetwork'; 5 | import { arbitrum } from 'viem/chains'; 6 | 7 | const client = createPublicClient({ 8 | chain: arbitrum, 9 | transport: http(), 10 | }); 11 | 12 | describe('prepareArbitrumNetwork', () => { 13 | it(`should create orbit chain network object for custom gas token chain (Xai)`, async () => { 14 | const network = await prepareArbitrumNetwork(client, { 15 | rollup: '0xc47dacfbaa80bd9d8112f4e8069482c2a3221336', 16 | }); 17 | expect(network).toMatchSnapshot(); 18 | }); 19 | 20 | it(`should create orbit chain network object for ETH gas token chain (Proof of Play Apex)`, async () => { 21 | const network = await prepareArbitrumNetwork(client, { 22 | rollup: '0x65AD139061B3f6DDb16170a07b925337ddf42407', 23 | }); 24 | expect(network).toMatchSnapshot(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/utils/sanitizePrivateKey.ts: -------------------------------------------------------------------------------- 1 | export function sanitizePrivateKey(privateKey: string): `0x${string}` { 2 | if (!privateKey.startsWith('0x')) { 3 | return `0x${privateKey}`; 4 | } 5 | 6 | return privateKey as `0x${string}`; 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/validateChain.ts: -------------------------------------------------------------------------------- 1 | import { Client, Transport, Chain } from 'viem'; 2 | 3 | import { chains } from '../chains'; 4 | 5 | type ChainId = (typeof chains)[number]['id']; 6 | 7 | function isValidChainId(chainId: number | undefined): chainId is ChainId { 8 | const ids = chains.map((chain) => chain.id) as Number[]; 9 | return ids.includes(Number(chainId)); 10 | } 11 | 12 | export function validateChain( 13 | chainIdOrClient: number | Client, 14 | ): ChainId { 15 | const chainId = typeof chainIdOrClient === 'number' ? chainIdOrClient : chainIdOrClient.chain?.id; 16 | 17 | if (!isValidChainId(chainId)) { 18 | throw new Error(`Chain not supported: ${chainId}`); 19 | } 20 | 21 | return chainId; 22 | } 23 | -------------------------------------------------------------------------------- /src/validateParentChain.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { it, expect } from 'vitest'; 2 | 3 | import { validateParentChain } from './types/ParentChain'; 4 | import { arbitrumOne, registerCustomParentChain } from './chains'; 5 | import { generateChainId } from './utils'; 6 | 7 | import { testHelper_createCustomParentChain } from './testHelpers'; 8 | 9 | it(`sucessfully validates arbitrum one`, () => { 10 | const result = validateParentChain(arbitrumOne.id); 11 | 12 | expect(result.chainId).toEqual(arbitrumOne.id); 13 | expect(result.isCustom).toEqual(false); 14 | }); 15 | 16 | it(`throws for an unregistered custom parent chain`, () => { 17 | const id = generateChainId(); 18 | 19 | expect(() => validateParentChain(id)).toThrowError(`Parent chain not supported: ${id}`); 20 | }); 21 | 22 | it(`sucessfully validates a registered custom parent chain`, () => { 23 | const chain = testHelper_createCustomParentChain(); 24 | 25 | registerCustomParentChain(chain); 26 | 27 | const result = validateParentChain(chain.id); 28 | 29 | expect(result.chainId).toEqual(chain.id); 30 | expect(result.isCustom).toEqual(true); 31 | }); 32 | -------------------------------------------------------------------------------- /src/wasmModuleRoot.unit.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { isKnownWasmModuleRoot } from './wasmModuleRoot'; 4 | 5 | describe('isKnownWasmModuleRoot', () => { 6 | it('returns true for a known wasm module root', () => { 7 | // https://github.com/OffchainLabs/nitro/releases/tag/consensus-v10.1 8 | expect( 9 | isKnownWasmModuleRoot('0xda4e3ad5e7feacb817c21c8d0220da7650fe9051ece68a3f0b1c5d38bbb27b21'), 10 | ).toEqual(true); 11 | // https://github.com/OffchainLabs/nitro/releases/tag/consensus-v11 12 | expect( 13 | isKnownWasmModuleRoot('0xf4389b835497a910d7ba3ebfb77aa93da985634f3c052de1290360635be40c4a'), 14 | ).toEqual(true); 15 | // https://github.com/OffchainLabs/nitro/releases/tag/consensus-v20 16 | expect( 17 | isKnownWasmModuleRoot('0x8b104a2e80ac6165dc58b9048de12f301d70b02a0ab51396c22b4b4b802a16a4'), 18 | ).toEqual(true); 19 | // https://github.com/OffchainLabs/nitro/releases/tag/consensus-v31 20 | expect( 21 | isKnownWasmModuleRoot('0x260f5fa5c3176a856893642e149cf128b5a8de9f828afec8d11184415dd8dc69'), 22 | ).toEqual(true); 23 | }); 24 | 25 | it('returns false for an unknown wasm module root', () => { 26 | expect( 27 | isKnownWasmModuleRoot('0x58e4fe5023f792d4ef584796c84d710303a5e12ea02d6e37e2b5e9c4332507c2'), 28 | ).toEqual(false); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /token-bridge-contracts/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-bullseye-slim 2 | RUN apt-get update && \ 3 | apt-get install -y git docker.io python3 chromium build-essential 4 | WORKDIR /workspace 5 | RUN git clone -b v1.2.2 https://github.com/OffchainLabs/token-bridge-contracts.git ./ 6 | RUN yarn install 7 | RUN yarn build 8 | ENTRYPOINT ["yarn"] 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src/**/*"], 3 | "exclude": ["./src/dist/**/*"], 4 | "compilerOptions": { 5 | "strict": true, 6 | "incremental": true, 7 | 8 | "lib": ["ES2021"], 9 | "target": "ES2021", 10 | 11 | "moduleResolution": "node", 12 | 13 | // JavaScript support 14 | "allowJs": false, 15 | "checkJs": false, 16 | 17 | // Skip type checking for node modules 18 | "skipLibCheck": true, 19 | "resolveJsonModule": true, 20 | "esModuleInterop": true, 21 | "allowSyntheticDefaultImports": true, 22 | 23 | // Show all keys in type without hiding in "... more ..." 24 | // Enabling this option cause typescript performance 25 | "noErrorTruncation": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /vitest.common.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | sequence: { 7 | concurrent: false, 8 | }, 9 | snapshotFormat: { 10 | escapeString: true, 11 | }, 12 | typecheck: { 13 | enabled: true, 14 | }, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /vitest.integration.config.ts: -------------------------------------------------------------------------------- 1 | import { configDefaults, defineConfig, mergeConfig } from 'vitest/config'; 2 | import commonConfig from './vitest.common'; 3 | 4 | export default mergeConfig( 5 | commonConfig, 6 | defineConfig({ 7 | test: { 8 | // allow tests to run for 7 minutes as retryables can take a while 9 | testTimeout: 7 * 60 * 1000, 10 | exclude: [...configDefaults.exclude, './src/**/*.unit.test.ts'], 11 | include: ['./src/**/*.integration.test.ts'], 12 | fileParallelism: false, // Run all integration tests sequentially 13 | }, 14 | }), 15 | ); 16 | -------------------------------------------------------------------------------- /vitest.unit.config.ts: -------------------------------------------------------------------------------- 1 | import { configDefaults, defineConfig, mergeConfig } from 'vitest/config'; 2 | import commonConfig from './vitest.common'; 3 | 4 | export default mergeConfig( 5 | commonConfig, 6 | defineConfig({ 7 | test: { 8 | // allow tests to run for 1 minute as some rpc calls can be slow 9 | testTimeout: 60 * 1000, 10 | exclude: [...configDefaults.exclude, './src/**/*.integration.test.ts'], 11 | include: ['./src/**/*.unit.test.ts'], 12 | }, 13 | }), 14 | ); 15 | --------------------------------------------------------------------------------