diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..9614f784 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: +- package-ecosystem: npm + directory: "/" + schedule: + interval: monthly + time: "03:00" + open-pull-requests-limit: 10 + reviewers: + - andreogle diff --git a/.github/workflows/continuous-build.yml b/.github/workflows/continuous-build.yml new file mode 100644 index 00000000..23532bac --- /dev/null +++ b/.github/workflows/continuous-build.yml @@ -0,0 +1,57 @@ +name: Continuous Build + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + documentation: + runs-on: ubuntu-latest + steps: + - name: Clone @api3/chains + uses: actions/checkout@v3 + + - name: Check hyperlinks + uses: gaurav-nelson/github-action-markdown-link-check@v1 + + lint-test: + runs-on: ubuntu-latest + steps: + - name: Clone @api3/chains + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: '18' + cache: 'yarn' + + - name: Get yarn cache directory path + id: deps-cache-dir-path + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + id: deps-cache + with: + path: ${{ steps.deps-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Run Validations + run: yarn validate + + required-checks-passed: + name: All required checks passed + runs-on: ubuntu-latest + needs: [documentation, lint-test] + steps: + - run: exit 0 + diff --git a/.gitignore b/.gitignore index 34fffa55..fbecf24e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -node_modules - .env +*.env +dist/ +node_modules/ diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 00000000..5702b26f --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn validate:chains +yarn lint diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..db295190 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,14 @@ +# NOTE: Keep in sync with .gitignore + +# Dependencies +/node_modules + +# Build files +/coverage +/dist + +# Logs +*.log + +# IDEs +.vscode diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..e3759614 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,22 @@ +{ + "bracketSpacing": true, + "printWidth": 120, + "singleQuote": true, + "trailingComma": "es5", + "useTabs": false, + "overrides": [ + { + "files": "*.ts", + "options": { + "parser": "typescript" + } + }, + { + "files": "*.md", + "options": { + "parser": "markdown", + "proseWrap": "always" + } + } + ] +} diff --git a/README.md b/README.md index 19534bd8..548e0813 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,37 @@ # @api3/chains -> A single source of truth for chain-related information +> The single source of truth for chain-related information -# Notes +- [Notes](#notes) + - [General](#general) + - [Deployment](#deployment) + - [hardhat-etherscan](#hardhat-etherscan) +- [API](#api) + - [CHAINS](#chains) + - [getChainByAlias](#getchainbyalias) + - [hardhatConfigNetworks](#hardhatconfignetworks) + - [hardhatEtherscan](#hardhatetherscan) + - [getEnvVariables](#getenvvariables) + - [Types](#types) +- [Scripts](#scripts) + - [chains:generate](#chainsgenerate) + - [chains:rename](#chainsrename) + - [env:write](#envwrite) +- [Development](#development) + - [Validation](#validation) +- [Building](#building) -## General +## Notes + +### General - `gnosis-testnet` provider may return an invalid gas price. -## Deployment +### Deployment - `metis-goerli-testnet` and `metis` do not support deterministic deployment. -## hardhat-etherscan +### hardhat-etherscan - `hardhat-etherscan` requires us to use a dummy API key with Blockscout block explorer APIs. We use `"DUMMY_VALUE"` but it could have been anything else. @@ -22,3 +41,164 @@ - `fantom-testnet` block explorer contract verification API does not work. - `gnosis-testnet` block explorer contract verification API does not work. + +## API + +The following variables/functions are exported from this package + +### CHAINS + +The single source of truth for the list of supported chains. A static array of `Chain` objects. + +```ts +import { CHAINS } from '@api3/chains'; +console.log(CHAINS); +/* +[ + { + name: 'Arbitrum testnet', + alias: 'arbitrum-goerli-testnet', + id: '421613', + ... + }, + ... +] +*/ +``` + +### getChainByAlias + +Returns a single `Chain` record found by it's alias. Throws an error if the `Chain` is not found. + +```ts +import { getChainByAlias } from '@api3/chains'; +console.log(getChainByAlias('ethereum')); +/* +{ + "name": "Ethereum", + "alias": "ethereum", + "id": "1", + ... +} +*/ +``` + +### hardhatConfigNetworks + +Returns an object where the key is each chain's alias and the value is an object that can be used as the `networks` field of [`hardhat.config.js`](https://hardhat.org/hardhat-runner/docs/config). + +```ts +import { hardhatConfigNetworks } from '@api3/chains'; +console.log(hardhatConfigNetworks()); +/* +{ + "arbitrum-goerli-testnet": { + accounts: { mnemonic: '' }, + chainId: '421613', + url: 'https://...', + }, + ... +} +*/ +``` + +### hardhatEtherscan + +Returns an object where the key is each chain's alias and the value is an object that can be used as the `etherscan` field of [`hardhat.config.js`](https://hardhat.org/hardhat-runner/docs/config) (requires the [`hardhat-etherscan` plugin](https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-etherscan)). + +```ts +import { hardhatEtherscan } from '@api3/chains'; +console.log(hardhatEtherscan()) +/* +{ + apiKey: { + 'arbitrumGoerli': { ... } + }, + customChains: [ + ... + ] +} +*/ +``` + +### getEnvVariables + +Returns an array of expected environment variable names for chains that have an API key required for the explorer. + +```ts +import { getEnvVariables } from '@api3/chains'; +console.log(getEnvVariables()); +/* +[ + 'MNEMONIC', + 'ETHERSCAN_API_KEY_arbitrum-goerli-testnet', + ... +] +*/ +``` + +### Types + +Types are also exported and can be found in `src/types.ts`. Types are generated from [zod](https://github.com/colinhacks/zod) schemas. These schemas are also used to validate each chain. + +## Scripts + +The following utility scripts are available + +### chains:generate + +Generates the latest `CHAINS` array and outputs the file to `src/generated/chains.ts` + +```sh +yarn chains:generate +``` + +### chains:rename + +Renames each JSON file using the `alias` as the filename. + +```sh +yarn chains:rename +``` + +### env:write + +Generates the default content for a `.env` file using the output of `getEnvVariables` as the keys. + +```sh +yarn env:write --path .env +``` + +## Development + +This project works by combining the various JSON files defined in the `chains/` directory into a single generated TypeScript file. This file is then validated to ensure that each chain description conforms to specific requirements. The TypeScript file is generated by running any of these commands + +```sh +yarn chains:generate + +# Alternatively, watch the chains/ directory and regenerate on file change +yarn dev +``` + +### Validation + +Validations can be run with the following commands. + +```sh +# Validate each chain JSON file conforms to the zod schemas +yarn validate:chains + +# Validate each chain's provider URL is correct and working +yarn validate:providers + +# Run all validations synchronously +yarn validate +``` + +## Building + +The TypeScript project can be compiled by running the following command. This regenerates the latest CHAINS array from the JSON files first, before running `tsc`. Files are output in the `dist/` directory + +```sh +yarn build +``` diff --git a/package.json b/package.json index 4ca2e0ce..4ffa5897 100644 --- a/package.json +++ b/package.json @@ -2,19 +2,43 @@ "name": "@api3/chains", "license": "MIT", "version": "1.4.0", + "author": "API3 DAO", "private": false, - "main": "src/index", + "main": "./dist/index.js", + "repository": "git@github.com:api3dao/chains", + "bugs": { + "url": "https://github.com/api3dao/chains/issues" + }, "files": [ - "chains", + "dist", "src" ], "scripts": { - "check-provider": "node scripts/check-provider", + "build": "yarn clean && yarn chains:generate && tsc -p tsconfig.build.json", + "clean": "rimraf ./dist", + "dev": "ts-node scripts/generate-chains.ts --watch", + "chains:generate": "ts-node scripts/generate-chains.ts", + "env:write": "ts-node scripts/write-env-variables.ts", + "lint": "exit 0", + "prepare": "husky install", + "prepublishOnly": "yarn validate:chains", "prettier": "prettier --write \"./**/*.{js,md,json}\"", - "rename-chain-files": "node scripts/rename-chain-files" + "providers:ping": "ts-node scripts/ping-providers.ts", + "validate": "yarn validate:chains", + "validate:chains": "ts-node scripts/validate-chains.ts" + }, + "dependencies": { + "zod": "^3.21.4" }, "devDependencies": { - "ethers": "^6.0.8", - "prettier": "^2.8.4" + "@types/node": "^18.15.11", + "@types/prettier": "^2.7.2", + "chokidar": "^3.5.3", + "ethers": "^6.2.3", + "husky": "^8.0.3", + "prettier": "^2.8.4", + "rimraf": "^4.4.1", + "ts-node": "^10.9.1", + "typescript": "^5.0.3" } } diff --git a/scripts/generate-chains.ts b/scripts/generate-chains.ts new file mode 100644 index 00000000..d399c012 --- /dev/null +++ b/scripts/generate-chains.ts @@ -0,0 +1,72 @@ +import fs from 'fs'; +import path from 'path'; +import chokidar from 'chokidar'; +import prettier from 'prettier'; + +const PRETTIER_CONFIG = path.join(__dirname, '../.prettierrc'); +const INPUT_DIR = path.join(__dirname, '../chains'); +const OUTPUT_DIR = path.join(__dirname, '../src/generated'); +const OUTPUT_FILE = `${OUTPUT_DIR}/chains.ts`; + +const HEADER_CONTENT = `// =========================================================================== +// DO NOT EDIT THIS FILE MANUALLY! +// +// The contents have been added automatically. +// See: scripts/generate-chains.ts for more information +// =========================================================================== + +import { Chain } from '../types'; +`; + +function mergeJsonFiles() { + const fileNames = fs.readdirSync(INPUT_DIR); + const jsonFiles = fileNames.filter((fileName) => fileName.endsWith('.json')); + const combinedChains: any = []; + + for (const jsonFile of jsonFiles) { + const filePath = path.join(INPUT_DIR, jsonFile); + const fileContentRaw = fs.readFileSync(filePath, 'utf-8'); + const fileContent = JSON.parse(fileContentRaw); + combinedChains.push(fileContent); + } + + const rawContent = `${HEADER_CONTENT}\nexport const CHAINS: Chain[] = ${JSON.stringify(combinedChains)};\n\n`; + + const prettierConfig = JSON.parse(fs.readFileSync(PRETTIER_CONFIG, 'utf-8')); + const formattedContent = prettier.format(rawContent, { parser: 'typescript', ...prettierConfig }); + + if (!fs.existsSync(OUTPUT_DIR)) { + fs.mkdirSync(OUTPUT_DIR); + } + + fs.writeFileSync(OUTPUT_FILE, formattedContent); + console.log(`Combined chains been saved as ${OUTPUT_FILE}`); +} + +function watchJsonFiles() { + // ignored: by default we want to ignore dotfiles while watching + // persistent: continue the process as long as the directory is being watched + // See: https://github.com/paulmillr/chokidar#api + const watcher = chokidar.watch([INPUT_DIR], { ignored: /^\./, persistent: true }); + + watcher + .on('add', (path) => { + console.log(`File ${path} has been added`); + mergeJsonFiles() + }) + .on('change', (path) => { + console.log(`File ${path} has been changed`); + mergeJsonFiles() + }) + .on('unlink', (path) => { + console.log(`File ${path} has been removed`); + mergeJsonFiles() + }); +} + +if (process.argv.includes('--watch')) { + watchJsonFiles(); +} else { + mergeJsonFiles() +} + diff --git a/scripts/check-provider.js b/scripts/ping-providers.ts similarity index 56% rename from scripts/check-provider.js rename to scripts/ping-providers.ts index 4c216ac2..87143ca6 100644 --- a/scripts/check-provider.js +++ b/scripts/ping-providers.ts @@ -1,17 +1,17 @@ -const ethers = require("ethers"); -const { getChains, getChain } = require("../src/chains"); +import { JsonRpcProvider } from 'ethers'; +import { CHAINS, getChainByAlias } from '../src'; -const chains = process.env.CHAIN ? [getChain(process.env.CHAIN)] : getChains(); +const chains = process.env.CHAIN ? [getChainByAlias(process.env.CHAIN)] : CHAINS; -chains.map(async (chain) => { - const provider = new ethers.JsonRpcProvider(chain.providerUrl); +chains.forEach(async (chain) => { + const provider = new JsonRpcProvider(chain.providerUrl); const chainId = (await provider.getNetwork()).chainId; - if (chainId != chain.id) { + if (chainId.toString() !== chain.id) { throw new Error( `${chain.alias} provider reports chain ID to be ${chainId}, while it is defined to be ${chain.id}` ); } - const blockTimestamp = (await provider.getBlock()).timestamp; + const blockTimestamp = (await provider.getBlock('latest'))!.timestamp; const deltaTime = Math.floor(new Date().getTime() / 1000) - blockTimestamp; const tolerance = 5 * 60; if (Math.abs(deltaTime) > tolerance) { diff --git a/scripts/rename-chain-files.js b/scripts/rename-chain-files.js deleted file mode 100644 index c9b791a6..00000000 --- a/scripts/rename-chain-files.js +++ /dev/null @@ -1,15 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const { getChainFilePaths } = require("../src/chains"); - -const chainFilePaths = getChainFilePaths(); - -chainFilePaths.map((chainFilePath) => { - const chain = JSON.parse(fs.readFileSync(chainFilePath, "utf8")); - const newChainFilePath = path.resolve( - chainFilePath, - "..", - `${chain.alias}.json` - ); - fs.renameSync(chainFilePath, newChainFilePath); -}); diff --git a/scripts/validate-chains.ts b/scripts/validate-chains.ts new file mode 100644 index 00000000..ec1415d9 --- /dev/null +++ b/scripts/validate-chains.ts @@ -0,0 +1,48 @@ +import fs from 'fs'; +import path from 'path'; +import { CHAINS, chainSchema } from '../src'; + +const INPUT_DIR = './chains'; + +const fileNames = fs.readdirSync(INPUT_DIR); +const jsonFiles = fileNames.filter((fileName) => fileName.endsWith('.json')); + +const jsonChains: any[] = jsonFiles.map((filePath: string) => { + const fullPath = path.join(INPUT_DIR, filePath); + const fileContentRaw = fs.readFileSync(fullPath, 'utf-8'); + return JSON.parse(fileContentRaw); +}); + +// Validation: Ensure that each JSON file is represented in the CHAINS array +if (CHAINS.length !== jsonChains.length) { + console.log('Generated chains differs in length to the number of JSON files'); + console.log(`Generated CHAINS length = ${CHAINS.length}. Expected ${jsonChains.length} chains`); + console.log('Try regenerating chains'); + process.exit(1); +} + +// Validation: Ensure that each JSON file is named by the chain's alias +jsonFiles.forEach((filePath: string, index: number) => { + const chain = jsonChains[index]!; + if (filePath.replace('.json', '') !== chain.alias) { + console.log('JSON file name must match the chain\'s alias'); + console.log(`Current value: ${filePath}.json. Expected: ${chain.alias}.json`); + process.exit(1); + } +}); + +// Validation: Ensure each JSON file content conforms to the required schema +jsonChains.forEach((chain: any) => { + const res = chainSchema.safeParse(chain); + if (!res.success) { + const errors = res.error.issues.map((issue) => { + return ` path: '${issue.path.join('.')}' => '${issue.message}' `; + }); + console.log(`Chain name:${chain.name} contains the following errors:\n${errors.join('\n')}\n`); + process.exit(1); + } +}); + +console.log('Successfully validated chains!'); +process.exit(0); + diff --git a/scripts/write-env-variables.ts b/scripts/write-env-variables.ts new file mode 100644 index 00000000..1814c266 --- /dev/null +++ b/scripts/write-env-variables.ts @@ -0,0 +1,19 @@ +import fs from 'fs'; +import { getEnvVariables } from '../src'; + +const args = process.argv.slice(2); +const pathArgIndex = args.findIndex((arg) => arg === '--path'); + +if (pathArgIndex === -1 || pathArgIndex === args.length - 1) { + console.error('Error: Missing or invalid --path argument'); + process.exit(1); +} + +const path = args[pathArgIndex + 1]!; + +const fileContents = getEnvVariables().map((env) => `${env}=""\n`).join(''); +fs.writeFileSync(path, fileContents); + +console.log(`Successfully wrote chain environment variables to ${path}`); +process.exit(0); + diff --git a/src/chains.js b/src/chains.js deleted file mode 100644 index 3cd38a17..00000000 --- a/src/chains.js +++ /dev/null @@ -1,32 +0,0 @@ -const fs = require("fs"); -const path = require("path"); - -function getChainFilePaths() { - const chainsDirectory = path.resolve(__dirname, "..", "chains"); - const chainFileNames = fs.readdirSync(chainsDirectory); - return chainFileNames.map((chainFileName) => { - return path.join(chainsDirectory, chainFileName); - }); -} - -function getChains() { - return getChainFilePaths().map((chainFilePath) => { - return JSON.parse(fs.readFileSync(chainFilePath, "utf8")); - }); -} - -module.exports = { - getChainFilePaths, - getChains, - getChain: (alias) => { - const chains = getChains().filter((chain) => { - return chain.alias === alias; - }); - if (chains.length === 0) { - throw new Error(`Chain ${alias} not found`); - } else if (chains.length > 1) { - throw new Error(`Multiple instances of chain ${alias} found`); - } - return chains[0]; - }, -}; diff --git a/src/generated/chains.ts b/src/generated/chains.ts new file mode 100644 index 00000000..a59ea59c --- /dev/null +++ b/src/generated/chains.ts @@ -0,0 +1,350 @@ +// =========================================================================== +// DO NOT EDIT THIS FILE MANUALLY! +// +// The contents have been added automatically. +// See: scripts/combine-chains.ts for more information +// =========================================================================== + +import { Chain } from '../types'; + +export const CHAINS: Chain[] = [ + { + name: 'Arbitrum testnet', + alias: 'arbitrum-goerli-testnet', + id: '421613', + symbol: 'testETH', + providerUrl: 'https://goerli-rollup.arbitrum.io/rpc', + explorer: { + api: { + url: 'https://api-goerli.arbiscan.io/api', + key: { required: true, hardhatEtherscanAlias: 'arbitrumGoerli' }, + }, + browserUrl: 'https://testnet.arbiscan.io/', + }, + }, + { + name: 'Arbitrum', + alias: 'arbitrum', + id: '42161', + symbol: 'ETH', + providerUrl: 'https://arb1.arbitrum.io/rpc', + explorer: { + api: { url: 'https://api.arbiscan.io/api', key: { required: true, hardhatEtherscanAlias: 'arbitrumOne' } }, + browserUrl: 'https://arbiscan.io/', + }, + }, + { + name: 'Avalanche testnet', + alias: 'avalanche-testnet', + id: '43113', + symbol: 'testAVAX', + providerUrl: 'https://api.avax-test.network/ext/bc/C/rpc', + explorer: { + api: { + url: 'https://api-testnet.snowtrace.io/api', + key: { required: true, hardhatEtherscanAlias: 'avalancheFujiTestnet' }, + }, + browserUrl: 'https://testnet.snowtrace.io/', + }, + }, + { + name: 'Avalanche', + alias: 'avalanche', + id: '43114', + symbol: 'AVAX', + providerUrl: 'https://api.avax.network/ext/bc/C/rpc', + explorer: { + api: { url: 'https://api.snowtrace.io/api', key: { required: true, hardhatEtherscanAlias: 'avalanche' } }, + browserUrl: 'https://snowtrace.io/', + }, + }, + { + name: 'BSC testnet', + alias: 'bsc-testnet', + id: '97', + symbol: 'testBNB', + providerUrl: 'https://data-seed-prebsc-1-s3.binance.org:8545/', + explorer: { + api: { url: 'https://api-testnet.bscscan.com/api', key: { required: true, hardhatEtherscanAlias: 'bscTestnet' } }, + browserUrl: 'https://testnet.bscscan.com/', + }, + }, + { + name: 'BSC', + alias: 'bsc', + id: '56', + symbol: 'BNB', + providerUrl: 'https://rpc.ankr.com/bsc', + explorer: { + api: { url: 'https://api.bscscan.com/api', key: { required: true, hardhatEtherscanAlias: 'bsc' } }, + browserUrl: 'https://bscscan.com/', + }, + }, + { + name: 'Ethereum Goerli testnet', + alias: 'ethereum-goerli-testnet', + id: '5', + symbol: 'testETH', + providerUrl: 'https://rpc.ankr.com/eth_goerli', + explorer: { + api: { url: 'https://api-goerli.etherscan.io/api', key: { required: true, hardhatEtherscanAlias: 'goerli' } }, + browserUrl: 'https://goerli.etherscan.io/', + }, + }, + { + name: 'Ethereum Sepolia testnet', + alias: 'ethereum-sepolia-testnet', + id: '11155111', + symbol: 'testETH', + providerUrl: 'https://rpc-sepolia.rockx.com', + explorer: { + api: { url: 'https://api-sepolia.etherscan.io/api', key: { required: true, hardhatEtherscanAlias: 'sepolia' } }, + browserUrl: 'https://sepolia.etherscan.io/', + }, + }, + { + name: 'Ethereum', + alias: 'ethereum', + id: '1', + symbol: 'ETH', + providerUrl: 'https://ethereum.publicnode.com', + explorer: { + api: { url: 'https://api.etherscan.io/api', key: { required: true, hardhatEtherscanAlias: 'mainnet' } }, + browserUrl: 'https://etherscan.io/', + }, + }, + { + name: 'Fantom testnet', + alias: 'fantom-testnet', + id: '4002', + symbol: 'testFTM', + providerUrl: 'https://rpc.ankr.com/fantom_testnet', + explorer: { + api: { url: 'https://api-testnet.ftmscan.com/api', key: { required: true, hardhatEtherscanAlias: 'ftmTestnet' } }, + browserUrl: 'https://testnet.ftmscan.com/', + }, + }, + { + name: 'Fantom', + alias: 'fantom', + id: '250', + symbol: 'FTM', + providerUrl: 'https://rpcapi.fantom.network/', + explorer: { + api: { url: 'https://api.ftmscan.com/api', key: { required: true, hardhatEtherscanAlias: 'opera' } }, + browserUrl: 'https://ftmscan.com/', + }, + }, + { + name: 'Gnosis Chain testnet', + alias: 'gnosis-testnet', + id: '10200', + symbol: 'testxDAI', + providerUrl: 'https://rpc.chiadochain.net', + explorer: { + api: { url: 'https://blockscout.chiadochain.net/api', key: { required: false, hardhatEtherscanAlias: 'chiado' } }, + browserUrl: 'https://blockscout.com/gnosis/chiado/', + }, + }, + { + name: 'Gnosis Chain', + alias: 'gnosis', + id: '100', + symbol: 'xDAI', + providerUrl: 'https://rpc.gnosischain.com', + explorer: { + api: { url: 'https://api.gnosisscan.io/api', key: { required: true, hardhatEtherscanAlias: 'gnosis' } }, + browserUrl: 'https://gnosisscan.io/', + }, + }, + { + name: 'Metis testnet', + alias: 'metis-goerli-testnet', + id: '599', + symbol: 'testMETIS', + providerUrl: 'https://goerli.gateway.metisdevops.link', + explorer: { + api: { url: 'https://goerli.explorer.metisdevops.link/api', key: { required: false } }, + browserUrl: 'https://goerli.explorer.metisdevops.link/', + }, + }, + { + name: 'Metis', + alias: 'metis', + id: '1088', + symbol: 'METIS', + providerUrl: 'https://andromeda.metis.io/?owner=1088', + explorer: { + api: { url: 'https://andromeda-explorer.metis.io/api', key: { required: false } }, + browserUrl: 'https://andromeda-explorer.metis.io/', + }, + }, + { + name: 'Milkomeda C1 testnet', + alias: 'milkomeda-c1-testnet', + id: '200101', + symbol: 'testmilkADA', + providerUrl: 'https://rpc-devnet-cardano-evm.c1.milkomeda.com', + explorer: { + api: { url: 'https://explorer-devnet-cardano-evm.c1.milkomeda.com/api', key: { required: false } }, + browserUrl: 'https://explorer-devnet-cardano-evm.c1.milkomeda.com/', + }, + }, + { + name: 'Milkomeda C1', + alias: 'milkomeda-c1', + id: '2001', + symbol: 'milkADA', + providerUrl: 'https://rpc-mainnet-cardano-evm.c1.milkomeda.com', + explorer: { + api: { url: 'https://explorer-mainnet-cardano-evm.c1.milkomeda.com/api', key: { required: false } }, + browserUrl: 'https://explorer-mainnet-cardano-evm.c1.milkomeda.com/', + }, + }, + { + name: 'Moonbeam testnet', + alias: 'moonbeam-testnet', + id: '1287', + symbol: 'testGLMR', + providerUrl: 'https://rpc.api.moonbase.moonbeam.network', + explorer: { + api: { + url: 'https://api-moonbase.moonscan.io/api', + key: { required: true, hardhatEtherscanAlias: 'moonbaseAlpha' }, + }, + browserUrl: 'https://moonbase.moonscan.io/', + }, + }, + { + name: 'Moonbeam', + alias: 'moonbeam', + id: '1284', + symbol: 'GLMR', + providerUrl: 'https://rpc.api.moonbeam.network', + explorer: { + api: { url: 'https://api-moonbeam.moonscan.io/api', key: { required: true, hardhatEtherscanAlias: 'moonbeam' } }, + browserUrl: 'https://moonscan.io/', + }, + }, + { + name: 'Moonriver', + alias: 'moonriver', + id: '1285', + symbol: 'MOVR', + providerUrl: 'https://rpc.api.moonriver.moonbeam.network', + explorer: { + api: { + url: 'https://api-moonriver.moonscan.io/api', + key: { required: true, hardhatEtherscanAlias: 'moonriver' }, + }, + browserUrl: 'https://moonriver.moonscan.io/', + }, + }, + { + name: 'Optimism testnet', + alias: 'optimism-goerli-testnet', + id: '420', + symbol: 'testETH', + providerUrl: 'https://goerli.optimism.io', + explorer: { + api: { + url: 'https://api-goerli-optimism.etherscan.io/api', + key: { required: true, hardhatEtherscanAlias: 'optimisticGoerli' }, + }, + browserUrl: 'https://goerli-optimism.etherscan.io/', + }, + }, + { + name: 'Optimism', + alias: 'optimism', + id: '10', + symbol: 'ETH', + providerUrl: 'https://mainnet.optimism.io', + explorer: { + api: { + url: 'https://api-optimistic.etherscan.io/api', + key: { required: true, hardhatEtherscanAlias: 'optimisticEthereum' }, + }, + browserUrl: 'https://optimistic.etherscan.io/', + }, + }, + { + name: 'Polygon testnet', + alias: 'polygon-testnet', + id: '80001', + symbol: 'testMATIC', + providerUrl: 'https://rpc.ankr.com/polygon_mumbai', + explorer: { + api: { + url: 'https://api-testnet.polygonscan.com/api', + key: { required: true, hardhatEtherscanAlias: 'polygonMumbai' }, + }, + browserUrl: 'https://mumbai.polygonscan.com/', + }, + }, + { + name: 'Polygon zkEVM testnet', + alias: 'polygon-zkevm-goerli-testnet', + id: '1442', + symbol: 'testETH', + providerUrl: 'https://rpc.public.zkevm-test.net', + explorer: { + api: { url: 'https://api-testnet-zkevm.polygonscan.com/api', key: { required: true } }, + browserUrl: 'https://explorer.public.zkevm-test.net', + }, + }, + { + name: 'Polygon zkEVM', + alias: 'polygon-zkevm', + id: '1101', + symbol: 'ETH', + providerUrl: 'https://zkevm-rpc.com', + explorer: { + api: { url: 'https://api-zkevm.polygonscan.com/api', key: { required: true } }, + browserUrl: 'https://zkevm.polygonscan.com/', + }, + }, + { + name: 'Polygon', + alias: 'polygon', + id: '137', + symbol: 'MATIC', + providerUrl: 'https://polygon-bor.publicnode.com', + explorer: { + api: { url: 'https://api.polygonscan.com/api', key: { required: true, hardhatEtherscanAlias: 'polygon' } }, + browserUrl: 'https://polygonscan.com/', + }, + }, + { + name: 'RSK testnet', + alias: 'rsk-testnet', + id: '31', + symbol: 'testRBTC', + providerUrl: 'https://public-node.testnet.rsk.co', + explorer: { browserUrl: 'https://explorer.testnet.rsk.co/' }, + }, + { + name: 'RSK', + alias: 'rsk', + id: '30', + symbol: 'RBTC', + providerUrl: 'https://mainnet.sovryn.app/rpc', + explorer: { browserUrl: 'https://explorer.rsk.co/' }, + }, + { + name: 'zkSync testnet', + alias: 'zksync-goerli-testnet', + id: '280', + symbol: 'testETH', + providerUrl: 'https://testnet.era.zksync.dev', + explorer: { browserUrl: 'https://goerli.explorer.zksync.io/' }, + }, + { + name: 'zkSync', + alias: 'zksync', + id: '324', + symbol: 'ETH', + providerUrl: 'https://mainnet.era.zksync.io', + explorer: { browserUrl: 'https://explorer.zksync.io/' }, + }, +]; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 7bee2ed8..00000000 --- a/src/index.js +++ /dev/null @@ -1,71 +0,0 @@ -const fs = require("fs"); -const { getChains } = require("./chains"); - -module.exports = { - hardhatConfigNetworks: () => { - return getChains().reduce((networks, chain) => { - networks[chain.alias] = { - accounts: { - mnemonic: "", - }, - chainId: Number(chain.id), - url: chain.providerUrl, - }; - return networks; - }, {}); - }, - hardhatEtherscan: () => { - return getChains().reduce( - (etherscan, chain) => { - if (chain.explorer && chain.explorer.api) { - if (chain.explorer.api.key.hardhatEtherscanAlias) { - if (chain.explorer.api.key.required) { - etherscan.apiKey[chain.explorer.api.key.hardhatEtherscanAlias] = - chain.alias; - } else { - etherscan.apiKey[chain.explorer.api.key.hardhatEtherscanAlias] = - "DUMMY_VALUE"; - } - } else { - etherscan.customChains.push({ - network: chain.alias, - chainId: Number(chain.id), - urls: { - apiURL: chain.explorer.api.url, - browserURL: chain.explorer.browserUrl, - }, - }); - if (chain.explorer.api.key.required) { - etherscan.apiKey[chain.alias] = chain.alias; - } else { - etherscan.apiKey[chain.alias] = "DUMMY_VALUE"; - } - } - } - return etherscan; - }, - { - apiKey: {}, - customChains: [], - } - ); - }, - writeEnvFile: (path) => { - const envVariableNames = ["MNEMONIC"]; - Object.values(getChains()).map((chain) => { - if ( - chain.explorer && - chain.explorer.api && - chain.explorer.api.key.required - ) { - envVariableNames.push(`ETHERSCAN_API_KEY_${chain.alias}`); - } - }); - fs.writeFileSync( - path, - envVariableNames.reduce((fileContents, envVariableName) => { - return fileContents + `${envVariableName}=""\n`; - }, "") - ); - }, -}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..0fefd43f --- /dev/null +++ b/src/index.ts @@ -0,0 +1,67 @@ +import { CHAINS } from './generated/chains'; +import { Chain, HardhatConfigNetworks, HardhatEtherscanNetworks } from './types'; + +export * from './types'; + +// NOTE: the following file is generated with the generate-chains.ts script +export { CHAINS } from './generated/chains'; + +export function getChainByAlias(alias: string): Chain { + const chains = CHAINS.filter((c) => c.alias === alias); + if (!chains) { + throw new Error(`Chain with alias:${alias} not found`); + } + if (chains.length > 1) { + throw new Error(`Multiple instances of chain with alias:${alias} found`); + } + return chains[0]!; +} + +export function hardhatConfigNetworks(): HardhatConfigNetworks { + return CHAINS.reduce((networks, chain) => { + networks[chain.alias] = { + accounts: { mnemonic: '' }, + chainId: chain.id, + url: chain.providerUrl, + }; + return networks; + }, {} as HardhatConfigNetworks); +} + +export function hardhatEtherscan(): HardhatEtherscanNetworks { + return CHAINS.reduce((etherscan, chain) => { + if (!chain.explorer || !chain.explorer.api) { + return etherscan; + } + + const apiKey = chain.explorer.api.key; + const explorer = chain.explorer; + const apiKeyValue = apiKey.required ? chain.alias : "DUMMY_VALUE"; + + if (apiKey.hardhatEtherscanAlias) { + etherscan.apiKey[apiKey.hardhatEtherscanAlias] = apiKeyValue; + return etherscan; + } + + etherscan.customChains.push({ + network: chain.alias, + chainId: chain.id, + urls: { + apiURL: explorer.api!.url, + browserURL: explorer.browserUrl, + }, + }); + etherscan.apiKey[chain.alias] = apiKeyValue; + + return etherscan; + }, { apiKey: {}, customChains: [] } as HardhatEtherscanNetworks); +} + +export function getEnvVariables(): string[] { + const keys = CHAINS + .filter((chain) => chain.explorer?.api?.key?.required) + .map((chain) => `ETHERSCAN_API_KEY_${chain.alias}`); + + return ['MNEMONIC', ...keys]; +} + diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..318e197b --- /dev/null +++ b/src/types.ts @@ -0,0 +1,50 @@ +import { z } from 'zod'; + +export const chainExplorerAPIKeySchema = z.object({ + required: z.boolean(), + hardhatEtherscanAlias: z.string().optional(), +}); + +export const chainExplorerAPISchema = z.object({ + key: chainExplorerAPIKeySchema, + url: z.string().url(), +}); + +export const chainExplorerSchema = z.object({ + api: chainExplorerAPISchema.optional(), + browserUrl: z.string().url(), +}); + +export const chainSchema = z.object({ + alias: z.string(), + name: z.string(), + // Most chain IDs are numbers, but to remain flexible this has purposefully been kept as a string + // It can be adjusted if we want to support chains that don't use numbers. + // See: https://github.com/api3dao/chains/pull/1#discussion_r1161102392 + id: z.string().regex(/^\d+$/), + providerUrl: z.string().url(), + symbol: z.string(), + explorer: chainExplorerSchema, +}); + +export type Chain = z.infer; +export type ChainExplorer = z.infer; +export type ChainExplorerAPI = z.infer; +export type ChainExplorerAPIKey = z.infer; + +export interface HardhatConfigNetworks { + [key: string]: { + accounts: { mnemonic: '' }; + chainId: string; + url: string; + } +} + +export interface HardhatEtherscanNetworks { + apiKey: { [etherscanAlias: string]: string; } + customChains: { + network: string; + chainId: string; + urls: { apiURL: string; browserURL: string; } + }[] +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..e89c7085 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "noEmit": false, + "sourceMap": true + }, + "exclude": ["src/**/*.test.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..d8e66ae6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "exactOptionalPropertyTypes": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "noUncheckedIndexedAccess": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "isolatedModules": true, + "noEmit": true, + "outDir": "dist", + "skipLibCheck": true, + "strict": true + }, + "include": ["src"], + "exclude": ["scripts", "chains"] +} diff --git a/yarn.lock b/yarn.lock index 827f600f..894808aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,35 @@ # yarn lockfile v1 -"@adraffy/ens-normalize@1.8.9": - version "1.8.9" - resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.8.9.tgz#67b3acadebbb551669c9a1da15fac951db795b85" - integrity sha512-93OmGCV0vO8+JQ3FHG+gZk/MPHzzMPDRiCiFcCQNTCnHaaxsacO3ScTPGlu2wX2dOtgfalbchPcw1cOYYjHCYQ== +"@adraffy/ens-normalize@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.0.tgz#223572538f6bea336750039bb43a4016dcc8182d" + integrity sha512-iowxq3U30sghZotgl4s/oJRci6WPBfNO5YYgk2cIOMCHr3LeGPcsZjCEr+33Q4N+oV3OABDAtA+pyvWjbvBifQ== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" "@noble/hashes@1.1.2": version "1.1.2" @@ -17,34 +42,299 @@ resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw== +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + +"@types/node@^18.15.11": + version "18.15.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" + integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== + +"@types/prettier@^2.7.2": + version "2.7.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" + integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.4.1: + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + aes-js@4.0.0-beta.3: version "4.0.0-beta.3" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.3.tgz#da2253f0ff03a0b3a9e445c8cbdf78e7fda7d48c" integrity sha512-/xJX0/VTPcbc5xQE2VUP91y1xN8q/rDfhEzLm+vLc3hYvb5+qHCnpJRuFcrKn63zumK/sCwYYzhG8HP78JYSTA== -ethers@^6.0.8: - version "6.0.8" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.0.8.tgz#a02e31f2771b66ecab6c731c1141b30d9de09174" - integrity sha512-j5smdMwn4t4vEARcfUv54mTJ2NMCorYLL51wPjFInEnrRr2SF5Sl9a7Z4DXS8UO1fBJVGHnjDDrF1b7msY3f7Q== +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +ethers@^6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.2.3.tgz#9ddee438b5949e9724ba4c5d2c3b8deb5202ce96" + integrity sha512-l1Z/Yr+HrOk+7LTeYRHGMvYwVLGpTuVrT/kJ7Kagi3nekGISYILIby0f1ipV9BGzgERyy+w4emH+d3PhhcxIfA== dependencies: - "@adraffy/ens-normalize" "1.8.9" + "@adraffy/ens-normalize" "1.9.0" "@noble/hashes" "1.1.2" "@noble/secp256k1" "1.7.1" aes-js "4.0.0-beta.3" tslib "2.4.0" ws "8.5.0" +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^9.2.0: + version "9.3.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.4.tgz#e75dee24891a80c25cc7ee1dd327e126b98679af" + integrity sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA== + dependencies: + fs.realpath "^1.0.0" + minimatch "^8.0.2" + minipass "^4.2.4" + path-scurry "^1.6.1" + +husky@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" + integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +lru-cache@^7.14.1: + version "7.18.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +minimatch@^8.0.2: + version "8.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.3.tgz#0415cb9bb0c1d8ac758c8a673eb1d288e13f5e75" + integrity sha512-tEEvU9TkZgnFDCtpnrEYnPsjT7iUx42aXfs4bzmQ5sMA09/6hZY0jeZcGkXyDagiBOvkUjNo8Viom+Me6+2x7g== + dependencies: + brace-expansion "^2.0.1" + +minipass@^4.0.2, minipass@^4.2.4: + version "4.2.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.5.tgz#9e0e5256f1e3513f8c34691dd68549e85b2c8ceb" + integrity sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +path-scurry@^1.6.1: + version "1.6.3" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.6.3.tgz#4eba7183d64ef88b63c7d330bddc3ba279dc6c40" + integrity sha512-RAmB+n30SlN+HnNx6EbcpoDy9nwdpcGPnEKrJnu6GZoDWBdIjo1UQMVtW2ybtC7LC2oKLcMq8y5g8WnKLiod9g== + dependencies: + lru-cache "^7.14.1" + minipass "^4.0.2" + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + prettier@^2.8.4: version "2.8.4" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3" integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rimraf@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.1.tgz#bd33364f67021c5b79e93d7f4fa0568c7c21b755" + integrity sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og== + dependencies: + glob "^9.2.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-node@^10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + tslib@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +typescript@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.3.tgz#fe976f0c826a88d0a382007681cbb2da44afdedf" + integrity sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + ws@8.5.0: version "8.5.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +zod@^3.21.4: + version "3.21.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" + integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==