diff --git a/.eslintrc b/.eslintrc index 9dcd1e8ba..c1ce429fd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -31,7 +31,7 @@ "@typescript-eslint/no-require-imports": ["warn"], "@typescript-eslint/no-unused-vars": [ "error", - { + { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", "caughtErrorsIgnorePattern": "^_" @@ -48,4 +48,4 @@ } ] } -} +} \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 41ad02d32..fc48baf0b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -22,5 +22,8 @@ typescript/token @yorhodes @jmrossy @tkporter @aroralanuk ## Hello World typescript/helloworld @yorhodes @nambrot +## CLI +typescript/cli @jmrossy @yorhodes + ## Infra typescript/infra @tkporter @nambrot diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index 2bb0aaa20..744fad90d 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -122,6 +122,25 @@ jobs: - name: infra run: yarn workspace @hyperlane-xyz/infra run test + test-cli: + runs-on: ubuntu-latest + needs: [yarn-build] + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + + - uses: actions/cache@v3 + with: + path: ./* + key: ${{ github.sha }} + + - name: test + run: ./typescript/cli/ci-test.sh + test-env: runs-on: ubuntu-latest needs: [yarn-build] diff --git a/Dockerfile b/Dockerfile index a6d4a76bf..3b9b30b71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ COPY typescript/utils/package.json ./typescript/utils/ COPY typescript/sdk/package.json ./typescript/sdk/ COPY typescript/helloworld/package.json ./typescript/helloworld/ COPY typescript/token/package.json ./typescript/token/ +COPY typescript/cli/package.json ./typescript/cli/ COPY typescript/infra/package.json ./typescript/infra/ COPY solidity/package.json ./solidity/ diff --git a/README.md b/README.md index ad2058a9f..d3a70986f 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ [codecov-badge]: https://img.shields.io/codecov/c/github/hyperlane-xyz/hyperlane-monorepo [foundry]: https://getfoundry.sh/ [foundry-badge]: https://img.shields.io/badge/Built%20with-Foundry-FFDB1C.svg -[license]: https://opensource.org/licenses/MIT -[license-badge]: https://img.shields.io/badge/License-MIT-blue.svg +[license]: https://www.apache.org/licenses/LICENSE-2.0 +[license-badge]: https://img.shields.io/badge/License-Apache-blue.svg ## Versioning diff --git a/package.json b/package.json index 4d3e9f541..0c82bedd9 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,14 @@ "description": "A yarn workspace of core Hyperlane packages", "version": "0.0.0", "devDependencies": { - "@trivago/prettier-plugin-sort-imports": "^3.2.0", + "@trivago/prettier-plugin-sort-imports": "^4.2.0", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", - "eslint": "^8.16.0", - "eslint-config-prettier": "^8.5.0", + "eslint": "^8.43.0", + "eslint-config-prettier": "^8.8.0", "husky": "^8.0.0", "lint-staged": "^12.4.3", - "prettier": "^2.4.1" + "prettier": "^2.8.8" }, "packageManager": "yarn@3.2.0", "private": true, @@ -37,6 +37,7 @@ "lodash": "^4.17.21", "recursive-readdir": "^2.2.3", "underscore": "^1.13", - "undici": "^5.11" + "undici": "^5.11", + "@trivago/prettier-plugin-sort-imports/@babel/parser": "^7.22.7" } } diff --git a/solidity/package.json b/solidity/package.json index de06869f3..9dd5bc280 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "1.5.1", + "version": "1.5.4-beta0", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "1.5.1", + "@hyperlane-xyz/utils": "1.5.4-beta0", "@openzeppelin/contracts": "^4.8.0", "@openzeppelin/contracts-upgradeable": "^4.8.0" }, @@ -18,7 +18,7 @@ "ethers": "^5.7.2", "hardhat": "^2.16.1", "hardhat-gas-reporter": "^1.0.9", - "prettier": "^2.4.1", + "prettier": "^2.8.8", "prettier-plugin-solidity": "^1.0.0-beta.5", "solhint": "^3.3.2", "solhint-plugin-prettier": "^0.0.5", @@ -56,5 +56,6 @@ "gas-ci": "yarn gas --check --tolerance 2 || (echo 'Manually update gas snapshot' && exit 1)", "slither": "slither ." }, - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "stableVersion": "1.5.3" } diff --git a/typescript/cli/.eslintignore b/typescript/cli/.eslintignore new file mode 100644 index 000000000..76add878f --- /dev/null +++ b/typescript/cli/.eslintignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/typescript/cli/.eslintrc b/typescript/cli/.eslintrc new file mode 100644 index 000000000..e3f712414 --- /dev/null +++ b/typescript/cli/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "no-console": ["off"] + } +} diff --git a/typescript/cli/.gitignore b/typescript/cli/.gitignore new file mode 100644 index 000000000..8ee178363 --- /dev/null +++ b/typescript/cli/.gitignore @@ -0,0 +1,5 @@ +.env* +/dist +/cache +/configs +/artifacts \ No newline at end of file diff --git a/typescript/cli/README.md b/typescript/cli/README.md new file mode 100644 index 000000000..168c2dba6 --- /dev/null +++ b/typescript/cli/README.md @@ -0,0 +1,57 @@ +# Hyperlane CLI + +The Hyperlane CLI is a command-line tool written in Typescript that facilitates common operations on Hyperlane, such as deploying the core contracts and/or warp routes to new chains. + +## Hyperlane overview + +Hyperlane is an interchain messaging protocol that allows applications to communicate between blockchains. + +Developers can use Hyperlane to share state between blockchains, allowing them to build interchain applications that live natively across multiple chains. + +To read more about interchain applications, how the protocol works, and how to integrate with Hyperlane, please see the [documentation](https://docs.hyperlane.xyz). + +## Setup + +Node 16 or newer is required. + +**Option 1: Global install:** + +```bash +# Install with NPM +npm install -g @hyperlane-xyz/cli +# Or uninstall old versions +npm uninstall -g @hyperlane-xyz/cli +``` + +**Option 2: Temp install:** + +```bash +# Run via NPM's npx command +npx @hyperlane-xyz/cli +# Or via Yarn's dlx command +yarn dlx @hyperlane-xyz/cli +``` + +**Option 3: Run from source:** + +```bash +git clone https://github.com/hyperlane-xyz/hyperlane-monorepo.git +cd hyperlane-monorepo +yarn install && yarn build +cd typescript/cli +yarn hyperlane +``` + +## Common commands + +View help: `hyperlane --help` + +Create a core deployment config: `hyperlane config create` + +Run hyperlane core deployments: `hyperlane deploy core` + +Run warp route deployments: `hyperlane deploy warp` + +View SDK contract addresses: `hyperlane chains addresses` + +Send test message: `hyperlane send message` diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh new file mode 100755 index 000000000..996dd42e8 --- /dev/null +++ b/typescript/cli/ci-test.sh @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +# Optional cleanup for previous runs, useful when running locally +pkill -f anvil +docker ps -aq | xargs docker stop | xargs docker rm +rm -rf /tmp/anvil* +rm -rf /tmp/relayer + +# Setup directories for anvil chains +for CHAIN in anvil1 anvil2 +do + mkdir -p /tmp/$CHAIN /tmp/$CHAIN/state /tmp/$CHAIN/validator /tmp/relayer + chmod -R 777 /tmp/relayer /tmp/$CHAIN +done + +# Optional: remove the --block-time 1 to speedup tests for local runs +anvil --chain-id 31337 -p 8545 --state /tmp/anvil1/state --block-time 1 > /dev/null & +anvil --chain-id 31338 -p 8555 --state /tmp/anvil2/state --block-time 1 > /dev/null & +sleep 1 + +set -e + +echo "{}" > /tmp/empty-artifacts.json + +echo "Deploying contracts to anvil1" +yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ + --chains ./examples/anvil-chains.yaml \ + --artifacts /tmp/empty-artifacts.json \ + --out /tmp \ + --ism ./examples/multisig-ism.yaml \ + --origin anvil1 --remotes anvil2 \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --yes + +CORE_ARTIFACTS_PATH=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` + +echo "Deploying contracts to anvil2" +yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ + --chains ./examples/anvil-chains.yaml \ + --artifacts $CORE_ARTIFACTS_PATH \ + --out /tmp \ + --ism ./examples/multisig-ism.yaml \ + --origin anvil2 --remotes anvil1 \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --yes + +CORE_ARTIFACTS_PATH=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1` +echo "Core artifacts:" +cat $CORE_ARTIFACTS_PATH + +AGENT_CONFIG_FILENAME=`ls -t1 /tmp | grep agent-config | head -1` + +echo "Deploying warp routes" +yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ + --chains ./examples/anvil-chains.yaml \ + --core $CORE_ARTIFACTS_PATH \ + --config ./examples/warp-tokens.yaml \ + --out /tmp \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --yes + +echo "Sending test message" +yarn workspace @hyperlane-xyz/cli run hyperlane send message \ + --origin anvil1 \ + --destination anvil2 \ + --chains ./examples/anvil-chains.yaml \ + --core $CORE_ARTIFACTS_PATH \ + --quick \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + | tee /tmp/message1 + +MESSAGE1_ID=`cat /tmp/message1 | grep "Message ID" | grep -E -o '0x[0-9a-f]+'` +echo "Message 1 ID: $MESSAGE1_ID" + +WARP_ARTIFACTS_FILE=`find /tmp/warp-deployment* -type f -exec ls -t1 {} + | head -1` +ANVIL1_ROUTER=`cat $WARP_ARTIFACTS_FILE | jq -r ".anvil1.router"` + +echo "Sending test warp transfer" +yarn workspace @hyperlane-xyz/cli run hyperlane send transfer \ + --origin anvil1 \ + --destination anvil2 \ + --chains ./examples/anvil-chains.yaml \ + --core $CORE_ARTIFACTS_PATH \ + --router $ANVIL1_ROUTER \ + --type native \ + --quick \ + --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + | tee /tmp/message2 + +MESSAGE2_ID=`cat /tmp/message2 | grep "Message ID" | grep -E -o '0x[0-9a-f]+'` +echo "Message 2 ID: $MESSAGE2_ID" + +if [[ $OSTYPE == 'darwin'* ]]; then + # Required because the -net=host driver only works on linux + DOCKER_CONNECTION_URL="http://host.docker.internal" +else + DOCKER_CONNECTION_URL="http://127.0.0.1" +fi + +for i in "anvil1 8545 ANVIL1" "anvil2 8555 ANVIL2" +do + set -- $i + echo "Running validator on $1" + docker run \ + --mount type=bind,source="/tmp",target=/data --net=host \ + -e CONFIG_FILES=/data/${AGENT_CONFIG_FILENAME} -e HYP_VALIDATOR_ORIGINCHAINNAME=$1 \ + -e HYP_VALIDATOR_REORGPERIOD=0 -e HYP_VALIDATOR_INTERVAL=1 \ + -e HYP_BASE_CHAINS_${3}_CONNECTION_URL=${DOCKER_CONNECTION_URL}:${2} \ + -e HYP_VALIDATOR_VALIDATOR_TYPE=hexKey \ + -e HYP_VALIDATOR_VALIDATOR_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 \ + -e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ + -e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/data/${1}/validator \ + -e HYP_BASE_TRACING_LEVEL=debug -e HYP_BASE_TRACING_FMT=compact \ + gcr.io/abacus-labs-dev/hyperlane-agent:main ./validator > /tmp/${1}/validator-logs.txt & +done + +echo "Validator running, sleeping to let it sync" +sleep 15 +echo "Done sleeping" + +echo "Validator Announcement:" +cat /tmp/anvil1/validator/announcement.json + +echo "Running relayer" +# Won't work on anything but linux due to -net=host +# Replace CONNECTION_URL with host.docker.internal on mac +docker run \ + --mount type=bind,source="/tmp",target=/data --net=host \ + -e CONFIG_FILES=/data/${AGENT_CONFIG_FILENAME} \ + -e HYP_BASE_CHAINS_ANVIL1_CONNECTION_URL=${DOCKER_CONNECTION_URL}:8545 \ + -e HYP_BASE_CHAINS_ANVIL2_CONNECTION_URL=${DOCKER_CONNECTION_URL}:8555 \ + -e HYP_BASE_TRACING_LEVEL=debug -e HYP_BASE_TRACING_FMT=compact \ + -e HYP_RELAYER_RELAYCHAINS=anvil1,anvil2 \ + -e HYP_RELAYER_ALLOWLOCALCHECKPOINTSYNCERS=true -e HYP_RELAYER_DB=/data/relayer \ + -e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ + -e HYP_BASE_CHAINS_ANVIL1_SIGNER_TYPE=hexKey \ + -e HYP_BASE_CHAINS_ANVIL1_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ + -e HYP_BASE_CHAINS_ANVIL2_SIGNER_TYPE=hexKey \ + -e HYP_BASE_CHAINS_ANVIL2_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ + gcr.io/abacus-labs-dev/hyperlane-agent:main ./relayer > /tmp/relayer/relayer-logs.txt & + +sleep 5 +echo "Done running relayer, checking message delivery statuses" + +for i in "1 $MESSAGE1_ID" "2 $MESSAGE2_ID" +do + set -- $i + echo "Checking delivery status of $1: $2" + yarn workspace @hyperlane-xyz/cli run hyperlane status \ + --id $2 \ + --destination anvil2 \ + --chains ./examples/anvil-chains.yaml \ + --core $CORE_ARTIFACTS_PATH \ + | tee /tmp/message-status-$1 + if ! grep -q "$2 was delivered" /tmp/message-status-$1; then + echo "ERROR: Message $1 was not delivered" + exit 1 + else + echo "Message $1 was delivered!" + fi +done + +docker ps -aq | xargs docker stop | xargs docker rm +pkill -f anvil diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts new file mode 100644 index 000000000..a25672e7e --- /dev/null +++ b/typescript/cli/cli.ts @@ -0,0 +1,40 @@ +#! /usr/bin/env node +import chalk from 'chalk'; +import yargs from 'yargs'; + +import { errorRed } from './logger.js'; +import { chainsCommand } from './src/commands/chains.js'; +import { configCommand } from './src/commands/config.js'; +import { deployCommand } from './src/commands/deploy.js'; +import { sendCommand } from './src/commands/send.js'; +import { statusCommand } from './src/commands/status.js'; + +// From yargs code: +const MISSING_PARAMS_ERROR = 'Not enough non-option arguments'; + +console.log(chalk.blue('Hyperlane'), chalk.magentaBright('CLI')); + +try { + await yargs(process.argv.slice(2)) + .scriptName('hyperlane') + // TODO get version num from package.json + .version(false) + .command(chainsCommand) + .command(configCommand) + .command(deployCommand) + .command(sendCommand) + .command(statusCommand) + .demandCommand() + .strict() + .help() + .fail((msg, err, yargs) => { + if (msg && !msg.includes(MISSING_PARAMS_ERROR)) errorRed('Error: ' + msg); + console.log(''); + yargs.showHelp(); + console.log(''); + if (err) errorRed(err.toString()); + process.exit(1); + }).argv; +} catch (error: any) { + errorRed('Error: ' + error.message); +} diff --git a/typescript/cli/examples/anvil-chains.yaml b/typescript/cli/examples/anvil-chains.yaml new file mode 100644 index 000000000..be2f4a9f2 --- /dev/null +++ b/typescript/cli/examples/anvil-chains.yaml @@ -0,0 +1,22 @@ +# Configs for describing chain metadata for use in Hyperlane deployments or apps +# Consists of a map of chain names to metadata +# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts +--- +anvil1: + chainId: 31337 + domainId: 31337 + name: anvil1 + protocol: ethereum + rpcUrls: + - http: http://127.0.0.1:8545 + nativeToken: + name: Ether + symbol: ETH + decimals: 18 +anvil2: + chainId: 31338 + domainId: 31338 + name: anvil2 + protocol: ethereum + rpcUrls: + - http: http://127.0.0.1:8555 diff --git a/typescript/cli/examples/chain-config.yaml b/typescript/cli/examples/chain-config.yaml new file mode 100644 index 000000000..40333a44b --- /dev/null +++ b/typescript/cli/examples/chain-config.yaml @@ -0,0 +1,44 @@ +# Configs for describing chain metadata for use in Hyperlane deployments or apps +# Consists of a map of chain names to metadata +# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts +--- +mychainname: + # Required fields: + chainId: 1234567890 # Number: Use EIP-155 for EVM chains + domainId: 1234567890 # Number: Recommend matching chainId when possible + name: mychainname # String: Unique identifier for the chain, must match key above + protocol: ethereum # ProtocolType: Ethereum, Sealevel, etc. + rpcUrls: # Array: List of RPC configs + # Only http field is required + - http: https://mychain.com/rpc # String: HTTP URL of the RPC endpoint (preferably HTTPS) + # Others here are optional + pagination: + maxBlockRange: 1000 # Number + maxBlockAge: 1000 # Number + minBlockNumber: 1000 # Number + retry: + maxRequests: 5 # Number + baseRetryMs: 1000 # Number + # Optional fields, not required for Hyperlane deployments but useful for apps: + isTestnet: false # Boolean: Whether the chain is considered a testnet or a mainnet + blockExplorers: # Array: List of BlockExplorer configs + # Required fields: + - name: My Chain Explorer # String: Human-readable name for the explorer + url: https://mychain.com/explorer # String: Base URL for the explorer + apiUrl: https://mychain.com/api # String: Base URL for the explorer API + # Optional fields: + apiKey: myapikey # String: API key for the explorer (optional) + family: etherscan # ExplorerFamily: See ExplorerFamily for valid values + nativeToken: + name: Eth # String + symbol: ETH # String + decimals: 18 # Number + displayName: My Chain Name # String: Human-readable name of the chain + displayNameShort: My Chain # String: A shorter human-readable name + logoURI: https://mychain.com/logo.png # String: URI to a logo image for the chain + blocks: + confirmations: 12 # Number: Blocks to wait before considering a transaction confirmed + reorgPeriod: 100 # Number: Blocks before a transaction has a near-zero chance of reverting + estimateBlockTime: 15 # Number: Rough estimate of time per block in seconds + # transactionOverrides: # Object: Properties to include when forming transaction requests + # Any tx fields are allowed diff --git a/typescript/cli/examples/contract-artifacts.yaml b/typescript/cli/examples/contract-artifacts.yaml new file mode 100644 index 000000000..04f690045 --- /dev/null +++ b/typescript/cli/examples/contract-artifacts.yaml @@ -0,0 +1,20 @@ +# Artifacts representing contract addresses +# Typically not written by hand but generated as JSON by the deploy command +# Consists of a map of chain names to contract names to addresses +--- +anvil1: + storageGasOracle: '0xD9A9966E7dA9a7f0032bF449FB12696a638E673C' + validatorAnnounce: '0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524' + proxyAdmin: '0x90f9a2E9eCe93516d65FdaB726a3c62F5960a1b9' + mailbox: '0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70' + interchainGasPaymaster: '0x6cA0B6D22da47f091B7613223cD4BB03a2d77918' + defaultIsmInterchainGasPaymaster: '0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc' + multisigIsm: '0x9bDE63104EE030d9De419EEd6bA7D14b86D6fE3f' + testRecipient: '0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35' + interchainAccountIsm: '0x5e8ee6840caa4f367aff1FF28aA36D5B1b836d35' + aggregationIsmFactory: '0xc864fa3B662613cA5051f41e157d0a997f9a5A87' + routingIsmFactory: '0x1fdfD1486b8339638C6b92f8a96D698D8182D2b1' + interchainQueryRouter: '0xA837e38C3F7D509DF3a7a0fCf65E3814DB6c2618' + interchainAccountRouter: '0x9521291A43ebA3aD3FD24d610F4b7F7543C8d761' + merkleRootMultisigIsmFactory: '0x82140b2ddAd4E4dd7e1D6757Fb5F9485c230B79d' + messageIdMultisigIsmFactory: '0x1079056da3EC7D55521F27e1E094015C0d39Cc65' diff --git a/typescript/cli/examples/multisig-ism.yaml b/typescript/cli/examples/multisig-ism.yaml new file mode 100644 index 000000000..1eca411df --- /dev/null +++ b/typescript/cli/examples/multisig-ism.yaml @@ -0,0 +1,20 @@ +# A config for a multisig Interchain Security Module (ISM) +# Schema: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/ism/types.ts +# +# Valid module types: +# routing +# aggregation +# legacy_multisig +# merkle_root_multisig +# message_id_multisig +--- +anvil1: + type: 'legacy_multisig' # TODO: update for v3 + threshold: 1 # Number: Signatures required to approve a message + validators: # Array: List of validator addresses + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' +anvil2: + type: 'legacy_multisig' # TODO: update for v3 + threshold: 1 + validators: + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' diff --git a/typescript/cli/examples/warp-tokens.yaml b/typescript/cli/examples/warp-tokens.yaml new file mode 100644 index 000000000..24c31a756 --- /dev/null +++ b/typescript/cli/examples/warp-tokens.yaml @@ -0,0 +1,26 @@ +# A config for a Warp Route deployment +# Typically used with the 'hyperlane deploy warp' command +# +# Token Types: +# native +# collateral +# synthetic +# collateralUri +# syntheticUri +# fastCollateral +# fastSynthetic +--- +base: + chainName: anvil1 + type: native + # address: 0x123... # Required for collateral types + # isNft: true # If the token is an NFT (ERC721), set to true + # owner: 0x123 # Optional owner address for synthetic token + # mailbox: 0x123 # Optional mailbox address route + # interchainGasPaymaster: 0x123 # Optional interchainGasPaymaster address +synthetics: + - chainName: anvil2 + # You can optionally set the token metadata, otherwise the base token's will be used + # name: "MySyntheticToken" + # symbol: "MST" + # totalSupply: 10000000 diff --git a/typescript/cli/logger.ts b/typescript/cli/logger.ts new file mode 100644 index 000000000..f40f6822c --- /dev/null +++ b/typescript/cli/logger.ts @@ -0,0 +1,54 @@ +// This file isn't in the src dir so it it's imported before others after import sort +// See bigint hack below and https://github.com/trivago/prettier-plugin-sort-imports/issues/112 +import chalk from 'chalk'; +import debug from 'debug'; + +// Workaround for bug in bigint-buffer which solana-web3.js depends on +// https://github.com/no2chem/bigint-buffer/issues/31#issuecomment-1752134062 +const defaultWarn = console.warn; +console.warn = (...args: any) => { + if ( + args && + typeof args[0] === 'string' && + args[0]?.includes('bigint: Failed to load bindings') + ) + return; + defaultWarn(...args); +}; + +const HYPERLANE_NS = 'hyperlane'; + +// Default root logger for use in utils/scripts +export const logger = debug(HYPERLANE_NS); +export const error = debug(`${HYPERLANE_NS}:ERROR`); + +export function createLogger(namespace: string, isError = false) { + return isError ? error.extend(namespace) : logger.extend(namespace); +} + +// Ensure hyperlane logging is enabled by forcing inclusion of hyperlane namespace +const activeNamespaces = debug.disable(); +const otherNamespaces = activeNamespaces + .split(',') + .filter((ns) => ns.includes(HYPERLANE_NS)); +const hypNamespaces = `${HYPERLANE_NS},${HYPERLANE_NS}:*`; +debug.enable( + otherNamespaces ? `${otherNamespaces},${hypNamespaces}` : `${hypNamespaces}`, +); + +// Change Debug's output format to remove prefixes + postfixes +function formatArgs(this: debug.Debugger, args: any[]) { + args.push(debug.humanize(this.diff)); + args.pop(); +} +debug.formatArgs = formatArgs; + +// Colored logs directly to console +export const logBlue = (...args: any) => console.log(chalk.blue(...args)); +export const logPink = (...args: any) => + console.log(chalk.magentaBright(...args)); +export const logGray = (...args: any) => console.log(chalk.gray(...args)); +export const logGreen = (...args: any) => console.log(chalk.green(...args)); +export const logRed = (...args: any) => console.log(chalk.red(...args)); +export const errorRed = (...args: any) => console.error(chalk.red(...args)); +export const log = (...args: any) => console.log(...args); diff --git a/typescript/cli/package.json b/typescript/cli/package.json new file mode 100644 index 000000000..415d83c87 --- /dev/null +++ b/typescript/cli/package.json @@ -0,0 +1,58 @@ +{ + "name": "@hyperlane-xyz/cli", + "version": "1.5.4-beta0", + "description": "A command-line utility for common Hyperlane operations", + "dependencies": { + "@hyperlane-xyz/hyperlane-token": "1.5.4-beta0", + "@hyperlane-xyz/sdk": "1.5.4-beta0", + "@inquirer/prompts": "^3.0.0", + "chalk": "^5.3.0", + "ethers": "^5.7.2", + "yaml": "^2.3.1", + "yargs": "^17.7.2", + "zod": "^3.21.2" + }, + "devDependencies": { + "@types/node": "^18.14.5", + "@types/yargs": "^17.0.24", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "eslint": "^8.43.0", + "eslint-config-prettier": "^8.8.0", + "prettier": "^2.8.8", + "typescript": "^5.1.6" + }, + "scripts": { + "hyperlane": "node ./dist/cli.js", + "build": "tsc", + "dev": "tsc --watch", + "clean": "rm -rf ./dist", + "lint": "eslint . --ext .ts", + "prettier": "prettier --write ./src ./examples" + }, + "files": [ + "./dist", + "./examples" + ], + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module", + "bin": { + "hyperlane": "./dist/cli.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/hyperlane-xyz/hyperlane-monorepo" + }, + "license": "Apache 2.0", + "homepage": "https://www.hyperlane.xyz", + "keywords": [ + "Hyperlane", + "CLI", + "Permissionless", + "Deployment", + "Typescript" + ], + "packageManager": "yarn@3.2.0", + "stableVersion": "1.5.3" +} diff --git a/typescript/cli/src/commands/chains.ts b/typescript/cli/src/commands/chains.ts new file mode 100644 index 000000000..8565a6d20 --- /dev/null +++ b/typescript/cli/src/commands/chains.ts @@ -0,0 +1,73 @@ +import { CommandModule } from 'yargs'; + +import { + Chains, + CoreChainName, + Mainnets, + Testnets, + chainMetadata, + hyperlaneContractAddresses, +} from '@hyperlane-xyz/sdk'; + +import { log, logBlue, logGray } from '../../logger.js'; + +/** + * Parent command + */ +export const chainsCommand: CommandModule = { + command: 'chains', + describe: 'View information about core Hyperlane chains', + builder: (yargs) => + yargs + .command(listCommand) + .command(addressesCommand) + .version(false) + .demandCommand(), + handler: () => log('Command required'), +}; + +/** + * List command + */ +const listCommand: CommandModule = { + command: 'list', + describe: 'List all core chains included in the Hyperlane SDK', + handler: () => { + logBlue('Hyperlane core mainnet chains:'); + logGray('------------------------------'); + log(Mainnets.map((chain) => chainMetadata[chain].displayName).join(', ')); + log(''); + logBlue('Hyperlane core testnet chains:'); + logGray('------------------------------'); + log(Testnets.map((chain) => chainMetadata[chain].displayName).join(', ')); + }, +}; + +/** + * Addresses command + */ +const addressesCommand: CommandModule = { + command: 'addresses', + describe: 'Display the addresses of core Hyperlane contracts', + builder: (yargs) => + yargs.options({ + name: { + type: 'string', + description: 'Chain to display addresses for', + choices: Object.values(Chains), + alias: 'chain', + }, + }), + handler: (args) => { + const name = args.name as CoreChainName | undefined; + if (name && hyperlaneContractAddresses[name]) { + logBlue('Hyperlane contract addresses for:', name); + logGray('---------------------------------'); + log(JSON.stringify(hyperlaneContractAddresses[name], null, 2)); + } else { + logBlue('Hyperlane core contract addresses:'); + logGray('----------------------------------'); + log(JSON.stringify(hyperlaneContractAddresses, null, 2)); + } + }, +}; diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts new file mode 100644 index 000000000..7e9a76ffb --- /dev/null +++ b/typescript/cli/src/commands/config.ts @@ -0,0 +1,171 @@ +import { CommandModule } from 'yargs'; + +import { log, logGreen } from '../../logger.js'; +import { createChainConfig, readChainConfig } from '../config/chain.js'; +import { + createMultisigConfig, + readMultisigConfig, +} from '../config/multisig.js'; +import { createWarpConfig, readWarpRouteConfig } from '../config/warp.js'; +import { FileFormat } from '../utils/files.js'; + +import { + chainsCommandOption, + fileFormatOption, + outputFileOption, +} from './options.js'; + +/** + * Parent command + */ +export const configCommand: CommandModule = { + command: 'config', + describe: 'Create or validate Hyperlane configs', + builder: (yargs) => + yargs + .command(createCommand) + .command(validateCommand) + .version(false) + .demandCommand(), + handler: () => log('Command required'), +}; + +/** + * Create commands + */ +const createCommand: CommandModule = { + command: 'create', + describe: 'Create a new Hyperlane config', + builder: (yargs) => + yargs + .command(createChainCommand) + .command(createMultisigCommand) + .command(createWarpCommand) + .version(false) + .demandCommand(), + handler: () => log('Command required'), +}; + +const createChainCommand: CommandModule = { + command: 'chain', + describe: 'Create a new, minimal Hyperlane chain config (aka chain metadata)', + builder: (yargs) => + yargs.options({ + output: outputFileOption('./configs/chain-config.yaml'), + format: fileFormatOption, + }), + handler: async (argv: any) => { + const format: FileFormat = argv.format; + const outPath: string = argv.output; + await createChainConfig({ format, outPath }); + process.exit(0); + }, +}; + +const createMultisigCommand: CommandModule = { + command: 'multisig', + describe: 'Create a new Multisig ISM config', + builder: (yargs) => + yargs.options({ + output: outputFileOption('./configs/multisig-ism.yaml'), + format: fileFormatOption, + chains: chainsCommandOption, + }), + handler: async (argv: any) => { + const format: FileFormat = argv.format; + const outPath: string = argv.output; + const chainConfigPath: string = argv.chains; + await createMultisigConfig({ format, outPath, chainConfigPath }); + process.exit(0); + }, +}; + +const createWarpCommand: CommandModule = { + command: 'warp', + describe: 'Create a new Warp Route tokens config', + builder: (yargs) => + yargs.options({ + output: outputFileOption('./configs/warp-tokens.yaml'), + format: fileFormatOption, + chains: chainsCommandOption, + }), + handler: async (argv: any) => { + const format: FileFormat = argv.format; + const outPath: string = argv.output; + const chainConfigPath: string = argv.chains; + await createWarpConfig({ format, outPath, chainConfigPath }); + process.exit(0); + }, +}; + +/** + * Validate commands + */ +const validateCommand: CommandModule = { + command: 'validate', + describe: 'Validate a config in a YAML or JSON file', + builder: (yargs) => + yargs + .command(validateChainCommand) + .command(validateMultisigCommand) + .command(validateWarpCommand) + .version(false) + .demandCommand(), + handler: () => log('Command required'), +}; + +const validateChainCommand: CommandModule = { + command: 'chain', + describe: 'Validate a chain config in a YAML or JSON file', + builder: (yargs) => + yargs.options({ + path: { + type: 'string', + description: 'Input file path', + demandOption: true, + }, + }), + handler: async (argv) => { + const path = argv.path as string; + readChainConfig(path); + process.exit(0); + }, +}; + +const validateMultisigCommand: CommandModule = { + command: 'multisig', + describe: 'Validate a multisig ism config in a YAML or JSON file', + builder: (yargs) => + yargs.options({ + path: { + type: 'string', + description: 'Input file path', + demandOption: true, + }, + }), + handler: async (argv) => { + const path = argv.path as string; + readMultisigConfig(path); + logGreen('Config is valid'); + process.exit(0); + }, +}; + +const validateWarpCommand: CommandModule = { + command: 'warp', + describe: 'Validate a Warp Route config in a YAML or JSON file', + builder: (yargs) => + yargs.options({ + path: { + type: 'string', + description: 'Input file path', + demandOption: true, + }, + }), + handler: async (argv) => { + const path = argv.path as string; + readWarpRouteConfig(path); + logGreen('Config is valid'); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts new file mode 100644 index 000000000..34d591c33 --- /dev/null +++ b/typescript/cli/src/commands/deploy.ts @@ -0,0 +1,120 @@ +import { CommandModule } from 'yargs'; + +import { log, logGray } from '../../logger.js'; +import { runCoreDeploy } from '../deploy/core.js'; +import { runWarpDeploy } from '../deploy/warp.js'; + +import { + chainsCommandOption, + coreArtifactsOption, + keyCommandOption, + outDirCommandOption, + skipConfirmationOption, +} from './options.js'; + +/** + * Parent command + */ +export const deployCommand: CommandModule = { + command: 'deploy', + describe: 'Permisionslessly deploy a Hyperlane contracts or extensions', + builder: (yargs) => + yargs + .command(coreCommand) + .command(warpCommand) + .version(false) + .demandCommand(), + handler: () => log('Command required'), +}; + +/** + * Core command + */ +const coreCommand: CommandModule = { + command: 'core', + describe: 'Deploy core Hyperlane contracts', + builder: (yargs) => + yargs.options({ + key: keyCommandOption, + chains: chainsCommandOption, + out: outDirCommandOption, + artifacts: coreArtifactsOption, + ism: { + type: 'string', + description: + 'A path to a JSON or YAML file with ISM configs (e.g. Multisig)', + }, + origin: { + type: 'string', + description: 'Name of chain to which contracts will be deployed', + }, + remotes: { + type: 'string', + description: + 'Comma separated list of chain names to which origin will be connected', + }, + yes: skipConfirmationOption, + }), + handler: async (argv: any) => { + logGray('Hyperlane permissionless core deployment'); + logGray('----------------------------------------'); + const key: string = argv.key || process.env.HYP_KEY; + const chainConfigPath: string = argv.chains; + const outPath: string = argv.out; + const origin: string | undefined = argv.origin; + const remotes: string[] | undefined = argv.remotes + ? argv.remotes.split(',').map((r: string) => r.trim()) + : undefined; + const artifactsPath: string = argv.artifacts; + const ismConfigPath: string = argv.ism; + const skipConfirmation: boolean = argv.yes; + await runCoreDeploy({ + key, + chainConfigPath, + artifactsPath, + ismConfigPath, + outPath, + origin, + remotes, + skipConfirmation, + }); + process.exit(0); + }, +}; + +/** + * Warp command + */ +const warpCommand: CommandModule = { + command: 'warp', + describe: 'Deploy Warp Route contracts', + builder: (yargs) => + yargs.options({ + key: keyCommandOption, + chains: chainsCommandOption, + out: outDirCommandOption, + core: coreArtifactsOption, + config: { + type: 'string', + description: 'A path to a JSON or YAML file with a warp config.', + }, + yes: skipConfirmationOption, + }), + handler: async (argv: any) => { + const key: string = argv.key || process.env.HYP_KEY; + const chainConfigPath: string = argv.chains; + const warpConfigPath: string | undefined = argv.config; + const coreArtifactsPath: string | undefined = argv.core; + const outPath: string = argv.out; + const skipConfirmation: boolean = argv.yes; + await runWarpDeploy({ + key, + chainConfigPath, + warpConfigPath, + coreArtifactsPath, + outPath, + skipConfirmation, + }); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts new file mode 100644 index 000000000..8b1aad3fa --- /dev/null +++ b/typescript/cli/src/commands/options.ts @@ -0,0 +1,47 @@ +// A set of common options +import { Options } from 'yargs'; + +export const keyCommandOption: Options = { + type: 'string', + description: + 'A hex private key or seed phrase for transaction signing. Or use the HYP_KEY env var', +}; + +export const chainsCommandOption: Options = { + type: 'string', + description: 'A path to a JSON or YAML file with chain configs', + default: './configs/chain-config.yaml', +}; + +export const outDirCommandOption: Options = { + type: 'string', + description: 'A folder name output artifacts into', + default: './artifacts', +}; + +export const coreArtifactsOption: Options = { + type: 'string', + description: 'File path to core deployment output artifacts', +}; + +export const fileFormatOption: Options = { + type: 'string', + alias: 'f', + description: 'Output file format', + choices: ['json', 'yaml'], + default: 'yaml', +}; + +export const outputFileOption = (defaultPath: string): Options => ({ + type: 'string', + alias: 'o', + description: 'Output file path', + default: defaultPath, +}); + +export const skipConfirmationOption: Options = { + type: 'boolean', + alias: 'y', + description: 'Skip confirmation prompts', + default: false, +}; diff --git a/typescript/cli/src/commands/send.ts b/typescript/cli/src/commands/send.ts new file mode 100644 index 000000000..f39fd3681 --- /dev/null +++ b/typescript/cli/src/commands/send.ts @@ -0,0 +1,141 @@ +import { CommandModule, Options } from 'yargs'; + +import { TokenType } from '@hyperlane-xyz/hyperlane-token'; + +import { log } from '../../logger.js'; +import { sendTestMessage } from '../send/message.js'; +import { sendTestTransfer } from '../send/transfer.js'; + +import { + chainsCommandOption, + coreArtifactsOption, + keyCommandOption, +} from './options.js'; + +/** + * Parent command + */ +export const sendCommand: CommandModule = { + command: 'send', + describe: 'Send a test message or transfer', + builder: (yargs) => + yargs + .command(messageCommand) + .command(transferCommand) + .version(false) + .demandCommand(), + handler: () => log('Command required'), +}; + +/** + * Message command + */ +const messageOptions: { [k: string]: Options } = { + key: keyCommandOption, + chains: chainsCommandOption, + core: coreArtifactsOption, + origin: { + type: 'string', + description: 'Origin chain to send message from', + demandOption: true, + }, + destination: { + type: 'string', + description: 'Destination chain to send message to', + demandOption: true, + }, + timeout: { + type: 'number', + description: 'Timeout in seconds', + default: 5 * 60, + }, + quick: { + type: 'boolean', + description: 'Skip wait for message to be delivered', + default: false, + }, +}; + +const messageCommand: CommandModule = { + command: 'message', + describe: 'Send a test message to a remote chain', + builder: (yargs) => yargs.options(messageOptions), + handler: async (argv: any) => { + const key: string = argv.key || process.env.HYP_KEY; + const chainConfigPath: string = argv.chains; + const coreArtifactsPath: string = argv.core; + const origin: string = argv.origin; + const destination: string = argv.destination; + const timeoutSec: number = argv.timeout; + const skipWaitForDelivery: boolean = argv.quick; + await sendTestMessage({ + key, + chainConfigPath, + coreArtifactsPath, + origin, + destination, + timeoutSec, + skipWaitForDelivery, + }); + process.exit(0); + }, +}; + +/** + * Transfer command + */ +const transferCommand: CommandModule = { + command: 'transfer', + describe: 'Send a test token transfer on a warp route', + builder: (yargs) => + yargs.options({ + ...messageOptions, + router: { + type: 'string', + description: 'The address of the token router contract', + demandOption: true, + }, + type: { + type: 'string', + description: 'Warp token type (native of collateral)', + default: TokenType.collateral, + choices: [TokenType.collateral, TokenType.native], + }, + wei: { + type: 'string', + description: 'Amount in wei to send', + default: 1, + }, + recipient: { + type: 'string', + description: 'Token recipient address (defaults to sender)', + }, + }), + handler: async (argv: any) => { + const key: string = argv.key || process.env.HYP_KEY; + const chainConfigPath: string = argv.chains; + const coreArtifactsPath: string = argv.core; + const origin: string = argv.origin; + const destination: string = argv.destination; + const timeoutSec: number = argv.timeout; + const routerAddress: string = argv.router; + const tokenType: TokenType = argv.type; + const wei: string = argv.wei; + const recipient: string | undefined = argv.recipient; + const skipWaitForDelivery: boolean = argv.quick; + await sendTestTransfer({ + key, + chainConfigPath, + coreArtifactsPath, + origin, + destination, + routerAddress, + tokenType, + wei, + recipient, + timeoutSec, + skipWaitForDelivery, + }); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/commands/status.ts b/typescript/cli/src/commands/status.ts new file mode 100644 index 000000000..ab0a39b20 --- /dev/null +++ b/typescript/cli/src/commands/status.ts @@ -0,0 +1,38 @@ +import { CommandModule } from 'yargs'; + +import { checkMessageStatus } from '../status/message.js'; + +import { chainsCommandOption, coreArtifactsOption } from './options.js'; + +export const statusCommand: CommandModule = { + command: 'status', + describe: 'Check status of a message', + builder: (yargs) => + yargs.options({ + chains: chainsCommandOption, + core: coreArtifactsOption, + id: { + type: 'string', + description: 'Message ID', + demandOption: true, + }, + destination: { + type: 'string', + description: 'Destination chain name', + demandOption: true, + }, + }), + handler: async (argv: any) => { + const chainConfigPath: string = argv.chains; + const coreArtifactsPath: string = argv.core; + const messageId: string = argv.id; + const destination: string = argv.destination; + await checkMessageStatus({ + chainConfigPath, + coreArtifactsPath, + messageId, + destination, + }); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/config/artifacts.ts b/typescript/cli/src/config/artifacts.ts new file mode 100644 index 000000000..bed0de298 --- /dev/null +++ b/typescript/cli/src/config/artifacts.ts @@ -0,0 +1,22 @@ +import { z } from 'zod'; + +import { HyperlaneContractsMap } from '@hyperlane-xyz/sdk'; + +import { readYamlOrJson } from '../utils/files.js'; + +const DeploymentArtifactsSchema = z + .object({}) + .catchall(z.object({}).catchall(z.string())); + +export function readDeploymentArtifacts(filePath: string) { + const artifacts = readYamlOrJson>(filePath); + if (!artifacts) throw new Error(`No artifacts found at ${filePath}`); + const result = DeploymentArtifactsSchema.safeParse(artifacts); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid artifacts: ${firstIssue.path} => ${firstIssue.message}`, + ); + } + return artifacts; +} diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts new file mode 100644 index 000000000..e8621e608 --- /dev/null +++ b/typescript/cli/src/config/chain.ts @@ -0,0 +1,108 @@ +import { confirm, input, select } from '@inquirer/prompts'; +import fs from 'fs'; + +import { + ChainMap, + ChainMetadata, + ChainMetadataSchema, +} from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { errorRed, log, logBlue, logGreen } from '../../logger.js'; +import { getMultiProvider } from '../context.js'; +import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; + +export function readChainConfig(filePath: string) { + log(`Reading file configs in ${filePath}`); + const chainToMetadata = readYamlOrJson>(filePath); + + if ( + !chainToMetadata || + typeof chainToMetadata !== 'object' || + !Object.keys(chainToMetadata).length + ) { + errorRed(`No configs found in ${filePath}`); + process.exit(1); + } + + for (const [chain, metadata] of Object.entries(chainToMetadata)) { + const parseResult = ChainMetadataSchema.safeParse(metadata); + if (!parseResult.success) { + errorRed( + `Chain config for ${chain} is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`, + ); + errorRed(JSON.stringify(parseResult.error.errors)); + process.exit(1); + } + if (metadata.name !== chain) { + errorRed(`Chain ${chain} name does not match key`); + process.exit(1); + } + } + + // Ensure multiprovider accepts this metadata + getMultiProvider(chainToMetadata); + + logGreen(`All chain configs in ${filePath} are valid`); + return chainToMetadata; +} + +export function readChainConfigIfExists(filePath: string) { + if (!fs.existsSync(filePath)) { + log('No chain config file provided'); + return {}; + } else { + return readChainConfig(filePath); + } +} + +export async function createChainConfig({ + format, + outPath, +}: { + format: FileFormat; + outPath: string; +}) { + logBlue('Creating a new chain config'); + const name = await input({ + message: 'Enter chain name (one word, lower case)', + }); + const chainId = await input({ message: 'Enter chain id (number)' }); + const skipDomain = await confirm({ + message: 'Will the domainId match the chainId (recommended)?', + }); + let domainId: string; + if (skipDomain) { + domainId = chainId; + } else { + domainId = await input({ + message: 'Enter domain id (number, often matches chainId)', + }); + } + const protocol = await select({ + message: 'Select protocol type', + choices: Object.values(ProtocolType).map((protocol) => ({ + name: protocol, + value: protocol, + })), + }); + const rpcUrl = await input({ message: 'Enter http or https rpc url' }); + const metadata: ChainMetadata = { + name, + chainId: parseInt(chainId, 10), + domainId: parseInt(domainId, 10), + protocol, + rpcUrls: [{ http: rpcUrl }], + }; + const parseResult = ChainMetadataSchema.safeParse(metadata); + if (parseResult.success) { + logGreen(`Chain config is valid, writing to file ${outPath}`); + mergeYamlOrJson(outPath, { [name]: metadata }, format); + } else { + errorRed( + `Chain config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`, + ); + errorRed(JSON.stringify(parseResult.error.errors)); + throw new Error('Invalid chain config'); + } +} diff --git a/typescript/cli/src/config/multisig.ts b/typescript/cli/src/config/multisig.ts new file mode 100644 index 000000000..a65280c40 --- /dev/null +++ b/typescript/cli/src/config/multisig.ts @@ -0,0 +1,120 @@ +import { confirm, input, select } from '@inquirer/prompts'; +import { z } from 'zod'; + +import { ChainMap, ModuleType, MultisigIsmConfig } from '@hyperlane-xyz/sdk'; +import { objMap } from '@hyperlane-xyz/utils'; + +import { errorRed, log, logBlue, logGreen } from '../../logger.js'; +import { runMultiChainSelectionStep } from '../utils/chains.js'; +import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; + +import { readChainConfigIfExists } from './chain.js'; + +const MultisigConfigMapSchema = z.object({}).catchall( + z.object({ + type: z.string(), + threshold: z.number(), + validators: z.array(z.string()), + }), +); +export type MultisigConfigMap = z.infer; + +export function readMultisigConfig(filePath: string) { + const config = readYamlOrJson(filePath); + if (!config) throw new Error(`No multisig config found at ${filePath}`); + const result = MultisigConfigMapSchema.safeParse(config); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid multisig config: ${firstIssue.path} => ${firstIssue.message}`, + ); + } + const parsedConfig = result.data; + const formattedConfig = objMap(parsedConfig, (_, config) => ({ + ...config, + type: humanReadableIsmTypeToEnum(config.type), + })); + + logGreen(`All multisig configs in ${filePath} are valid`); + return formattedConfig as ChainMap; +} + +export function isValidMultisigConfig(config: any) { + return MultisigConfigMapSchema.safeParse(config).success; +} + +function humanReadableIsmTypeToEnum(type: string): ModuleType { + for (const [key, value] of Object.entries(ModuleType)) { + if (key.toLowerCase() === type) return parseInt(value.toString(), 10); + } + throw new Error(`Invalid ISM type ${type}`); +} + +export async function createMultisigConfig({ + format, + outPath, + chainConfigPath, +}: { + format: FileFormat; + outPath: string; + chainConfigPath: string; +}) { + logBlue('Creating a new multisig config'); + const customChains = readChainConfigIfExists(chainConfigPath); + const chains = await runMultiChainSelectionStep(customChains); + + const result: MultisigConfigMap = {}; + let lastConfig: MultisigConfigMap['string'] | undefined = undefined; + let repeat = false; + for (const chain of chains) { + log(`Setting values for chain ${chain}`); + if (lastConfig && repeat) { + result[chain] = lastConfig; + continue; + } + // TODO consider using default and not offering options here + // legacy_multisig is being deprecated in v3 + // Default should probably be aggregation(message_id, merkle_root) + const moduleType = await select({ + message: 'Select multisig type', + choices: [ + // { value: 'routing, name: 'routing' }, // TODO add support + // { value: 'aggregation, name: 'aggregation' }, // TODO add support + { value: 'legacy_multisig', name: 'legacy multisig' }, + { value: 'merkle_root_multisig', name: 'merkle root multisig' }, + { value: 'message_id_multisig', name: 'message id multisig' }, + ], + pageSize: 5, + }); + + const thresholdInput = await input({ + message: 'Enter threshold of signers (number)', + }); + const threshold = parseInt(thresholdInput, 10); + + const validatorsInput = await input({ + message: 'Enter validator addresses (comma separated list)', + }); + const validators = validatorsInput.split(',').map((v) => v.trim()); + lastConfig = { + type: moduleType, + threshold, + validators, + }; + result[chain] = lastConfig; + + repeat = await confirm({ + message: 'Use this same config for remaining chains?', + }); + } + + if (isValidMultisigConfig(result)) { + logGreen(`Multisig config is valid, writing to file ${outPath}`); + mergeYamlOrJson(outPath, result, format); + } else { + errorRed( + `Multisig config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/multisig-ism.yaml for an example`, + ); + throw new Error('Invalid multisig config'); + } +} diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts new file mode 100644 index 000000000..744c0dc21 --- /dev/null +++ b/typescript/cli/src/config/warp.ts @@ -0,0 +1,126 @@ +import { confirm, input } from '@inquirer/prompts'; +import { ethers } from 'ethers'; +import { z } from 'zod'; + +import { TokenType } from '@hyperlane-xyz/hyperlane-token'; + +import { errorRed, logBlue, logGreen } from '../../logger.js'; +import { + runMultiChainSelectionStep, + runSingleChainSelectionStep, +} from '../utils/chains.js'; +import { FileFormat, readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; + +import { readChainConfigIfExists } from './chain.js'; + +const ConnectionConfigSchema = { + mailbox: z.string().optional(), + interchainGasPaymaster: z.string().optional(), + interchainSecurityModule: z.string().optional(), + foreignDeployment: z.string().optional(), +}; + +export const WarpRouteConfigSchema = z.object({ + base: z.object({ + type: z.literal(TokenType.native).or(z.literal(TokenType.collateral)), + chainName: z.string(), + address: z.string().optional(), + isNft: z.boolean().optional(), + name: z.string().optional(), + symbol: z.string().optional(), + decimals: z.number().optional(), + ...ConnectionConfigSchema, + }), + synthetics: z + .array( + z.object({ + chainName: z.string(), + name: z.string().optional(), + symbol: z.string().optional(), + totalSupply: z.number().optional(), + ...ConnectionConfigSchema, + }), + ) + .nonempty(), +}); + +type InferredType = z.infer; +// A workaround for Zod's terrible typing for nonEmpty arrays +export type WarpRouteConfig = { + base: InferredType['base']; + synthetics: Array; +}; + +export function readWarpRouteConfig(filePath: string) { + const config = readYamlOrJson(filePath); + if (!config) throw new Error(`No warp config found at ${filePath}`); + const result = WarpRouteConfigSchema.safeParse(config); + if (!result.success) { + const firstIssue = result.error.issues[0]; + throw new Error( + `Invalid warp config: ${firstIssue.path} => ${firstIssue.message}`, + ); + } + return result.data; +} + +export function isValidWarpRouteConfig(config: any) { + return WarpRouteConfigSchema.safeParse(config).success; +} + +export async function createWarpConfig({ + format, + outPath, + chainConfigPath, +}: { + format: FileFormat; + outPath: string; + chainConfigPath: string; +}) { + logBlue('Creating a new warp route config'); + const customChains = readChainConfigIfExists(chainConfigPath); + const baseChain = await runSingleChainSelectionStep( + customChains, + 'Select base chain with the original token to warp', + ); + + const isNative = await confirm({ + message: + 'Are you creating a route for the native token of the base chain (e.g. Ether on Ethereum)?', + }); + + const baseType = isNative ? TokenType.native : TokenType.collateral; + const baseAddress = isNative + ? ethers.constants.AddressZero + : await input({ message: 'Enter the token address' }); + const isNft = isNative + ? false + : await confirm({ message: 'Is this an NFT (i.e. ERC-721)?' }); + + const syntheticChains = await runMultiChainSelectionStep( + customChains, + 'Select the chains to which the base token will be connected', + ); + + // TODO add more prompts here to support customizing the token metadata + + const result: WarpRouteConfig = { + base: { + chainName: baseChain, + type: baseType, + address: baseAddress, + isNft, + }, + synthetics: syntheticChains.map((chain) => ({ chainName: chain })), + }; + + if (isValidWarpRouteConfig(result)) { + logGreen(`Warp Route config is valid, writing to file ${outPath}`); + writeYamlOrJson(outPath, result, format); + } else { + errorRed( + `Warp config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/warp-tokens.yaml for an example`, + ); + throw new Error('Invalid multisig config'); + } +} diff --git a/typescript/cli/src/consts.ts b/typescript/cli/src/consts.ts new file mode 100644 index 000000000..2b93a4eb5 --- /dev/null +++ b/typescript/cli/src/consts.ts @@ -0,0 +1,4 @@ +// TODO revisit these rough balance requirements with more precise measurements +export const MINIMUM_CORE_DEPLOY_BALANCE = '500000000000000000'; // 0.5 ETH +export const MINIMUM_WARP_DEPLOY_BALANCE = '200000000000000000'; // 0.2 Eth +export const MINIMUM_TEST_SEND_BALANCE = '10000000000000000'; // 0.01 ETH diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts new file mode 100644 index 000000000..710e63b72 --- /dev/null +++ b/typescript/cli/src/context.ts @@ -0,0 +1,51 @@ +import { ethers } from 'ethers'; + +import { + ChainMap, + ChainMetadata, + HyperlaneContractsMap, + MultiProvider, + chainMetadata, + hyperlaneEnvironments, +} from '@hyperlane-xyz/sdk'; +import { objMerge } from '@hyperlane-xyz/utils'; + +import { readChainConfigIfExists } from './config/chain.js'; +import { keyToSigner } from './utils/keys.js'; + +export const sdkContractAddressesMap = { + ...hyperlaneEnvironments.testnet, + ...hyperlaneEnvironments.mainnet, +}; + +export function getMergedContractAddresses( + artifacts?: HyperlaneContractsMap, +) { + return objMerge( + sdkContractAddressesMap, + artifacts || {}, + ) as HyperlaneContractsMap; +} + +export function getContext(chainConfigPath: string) { + const customChains = readChainConfigIfExists(chainConfigPath); + const multiProvider = getMultiProvider(customChains); + return { customChains, multiProvider }; +} + +export function getContextWithSigner(key: string, chainConfigPath: string) { + const signer = keyToSigner(key); + const customChains = readChainConfigIfExists(chainConfigPath); + const multiProvider = getMultiProvider(customChains, signer); + return { signer, customChains, multiProvider }; +} + +export function getMultiProvider( + customChains: ChainMap, + signer?: ethers.Signer, +) { + const chainConfigs = { ...chainMetadata, ...customChains }; + const mp = new MultiProvider(chainConfigs); + if (signer) mp.setSharedSigner(signer); + return mp; +} diff --git a/typescript/cli/src/deploy/TestRecipientDeployer.ts b/typescript/cli/src/deploy/TestRecipientDeployer.ts new file mode 100644 index 000000000..a62505bdd --- /dev/null +++ b/typescript/cli/src/deploy/TestRecipientDeployer.ts @@ -0,0 +1,64 @@ +import debug from 'debug'; + +import { TestRecipient, TestRecipient__factory } from '@hyperlane-xyz/core'; +import { + ChainName, + HyperlaneDeployer, + MultiProvider, +} from '@hyperlane-xyz/sdk'; +import { Address, eqAddress } from '@hyperlane-xyz/utils'; + +export type TestRecipientConfig = { + interchainSecurityModule: Address; +}; + +export type TestRecipientContracts = { + testRecipient: TestRecipient; +}; + +export type TestRecipientAddresses = { + testRecipient: Address; +}; + +export const testRecipientFactories = { + testRecipient: new TestRecipient__factory(), +}; + +// TODO move this and related configs to the SDK +export class TestRecipientDeployer extends HyperlaneDeployer< + TestRecipientConfig, + typeof testRecipientFactories +> { + constructor(multiProvider: MultiProvider) { + super(multiProvider, testRecipientFactories, { + logger: debug('hyperlane:TestRecipientDeployer'), + }); + } + + async deployContracts( + chain: ChainName, + config: TestRecipientConfig, + ): Promise { + const testRecipient = await this.deployContract(chain, 'testRecipient', []); + try { + this.logger(`Checking ISM ${chain}`); + const ism = await testRecipient.interchainSecurityModule(); + this.logger(`Found ISM for on ${chain}: ${ism}`); + if (!eqAddress(ism, config.interchainSecurityModule)) { + this.logger( + `Current ISM does not match config. Updating to ${config.interchainSecurityModule}`, + ); + const tx = testRecipient.setInterchainSecurityModule( + config.interchainSecurityModule, + ); + await this.multiProvider.handleTx(chain, tx); + } + } catch (error) { + this.logger(`Failed to check/update ISM for ${chain}: ${error}`); + this.logger('Leaving ISM as is and continuing.'); + } + return { + testRecipient, + }; + } +} diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts new file mode 100644 index 000000000..01db85278 --- /dev/null +++ b/typescript/cli/src/deploy/core.ts @@ -0,0 +1,444 @@ +import { confirm } from '@inquirer/prompts'; +import { ethers } from 'ethers'; + +import { + ChainMap, + ChainName, + CoreConfig, + DeployedIsm, + GasOracleContractType, + HyperlaneAddresses, + HyperlaneAddressesMap, + HyperlaneContractsMap, + HyperlaneCoreDeployer, + HyperlaneDeploymentArtifacts, + HyperlaneIgpDeployer, + HyperlaneIsmFactory, + HyperlaneIsmFactoryDeployer, + ModuleType, + MultiProvider, + MultisigIsmConfig, + OverheadIgpConfig, + RoutingIsmConfig, + agentStartBlocks, + buildAgentConfig, + defaultMultisigIsmConfigs, + multisigIsmVerificationCost, + serializeContractsMap, +} from '@hyperlane-xyz/sdk'; +import { Address, objFilter, objMerge } from '@hyperlane-xyz/utils'; + +import { log, logBlue, logGray, logGreen, logRed } from '../../logger.js'; +import { readDeploymentArtifacts } from '../config/artifacts.js'; +import { readMultisigConfig } from '../config/multisig.js'; +import { MINIMUM_CORE_DEPLOY_BALANCE } from '../consts.js'; +import { + getContextWithSigner, + getMergedContractAddresses, + sdkContractAddressesMap, +} from '../context.js'; +import { runOriginAndRemotesSelectionStep } from '../utils/chains.js'; +import { + prepNewArtifactsFiles, + runFileSelectionStep, + writeJson, +} from '../utils/files.js'; + +import { + TestRecipientConfig, + TestRecipientDeployer, +} from './TestRecipientDeployer.js'; +import { runPreflightChecks } from './utils.js'; + +export async function runCoreDeploy({ + key, + chainConfigPath, + ismConfigPath, + artifactsPath, + outPath, + origin, + remotes, + skipConfirmation, +}: { + key: string; + chainConfigPath: string; + ismConfigPath: string; + artifactsPath?: string; + outPath: string; + origin?: string; + remotes?: string[]; + skipConfirmation: boolean; +}) { + const { customChains, multiProvider, signer } = getContextWithSigner( + key, + chainConfigPath, + ); + + if (!origin || !remotes?.length) { + ({ origin, remotes } = await runOriginAndRemotesSelectionStep( + customChains, + )); + } + const selectedChains = [origin, ...remotes]; + const artifacts = await runArtifactStep(selectedChains, artifactsPath); + const multisigConfig = await runIsmStep(selectedChains, ismConfigPath); + + const deploymentParams: DeployParams = { + origin, + remotes, + signer, + multiProvider, + artifacts, + multisigConfig, + outPath, + skipConfirmation, + }; + + await runDeployPlanStep(deploymentParams); + await runPreflightChecks({ + ...deploymentParams, + minBalanceWei: MINIMUM_CORE_DEPLOY_BALANCE, + }); + await executeDeploy(deploymentParams); +} + +async function runArtifactStep( + selectedChains: ChainName[], + artifactsPath?: string, +) { + if (!artifactsPath) { + logBlue( + '\n', + 'Deployments can be totally new or can use some existing contract addresses.', + ); + const isResume = await confirm({ + message: 'Do you want use some existing contract addresses?', + }); + if (!isResume) return undefined; + + artifactsPath = await runFileSelectionStep( + './artifacts', + 'contract artifacts', + 'core-deployment', + ); + } + const artifacts = readDeploymentArtifacts(artifactsPath); + const artifactChains = Object.keys(artifacts).filter((c) => + selectedChains.includes(c), + ); + log(`Found existing artifacts for chains: ${artifactChains.join(', ')}`); + return artifacts; +} + +async function runIsmStep(selectedChains: ChainName[], ismConfigPath?: string) { + const defaultConfigChains = Object.keys(defaultMultisigIsmConfigs); + const configRequired = !!selectedChains.find( + (c) => !defaultConfigChains.includes(c), + ); + if (!configRequired) return; + + if (!ismConfigPath) { + logBlue( + '\n', + 'Hyperlane instances requires an Interchain Security Module (ISM).', + ); + logGray( + 'Note, only Multisig ISM configs are currently supported in the CLI', + 'Example config: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/cli/typescript/cli/examples/multisig-ism.yaml', + ); + ismConfigPath = await runFileSelectionStep( + './configs', + 'ISM config', + 'ism', + ); + } + const configs = readMultisigConfig(ismConfigPath); + const multisigConfigChains = Object.keys(configs).filter((c) => + selectedChains.includes(c), + ); + log(`Found configs for chains: ${multisigConfigChains.join(', ')}`); + return configs; +} + +interface DeployParams { + origin: string; + remotes: string[]; + signer: ethers.Signer; + multiProvider: MultiProvider; + artifacts?: HyperlaneContractsMap; + multisigConfig?: ChainMap; + outPath: string; + skipConfirmation: boolean; +} + +async function runDeployPlanStep({ + origin, + remotes, + signer, + artifacts, + skipConfirmation, +}: DeployParams) { + const address = await signer.getAddress(); + logBlue('\nDeployment plan'); + logGray('==============='); + log(`Transaction signer and owner of new contracts will be ${address}`); + log(`Deploying to ${origin} and connecting it to ${remotes.join(', ')}`); + const numContracts = Object.keys( + Object.values(sdkContractAddressesMap)[0], + ).length; + log(`There are ${numContracts} contracts for each chain`); + if (artifacts) + log('But contracts with an address in the artifacts file will be skipped'); + for (const chain of [origin, ...remotes]) { + const chainArtifacts = artifacts?.[chain] || {}; + const numRequired = numContracts - Object.keys(chainArtifacts).length; + log(`${chain} will require ${numRequired} of ${numContracts}`); + } + log('The default interchain security module will be a Multisig.'); + if (skipConfirmation) return; + const isConfirmed = await confirm({ + message: 'Is this deployment plan correct?', + }); + if (!isConfirmed) throw new Error('Deployment cancelled'); +} + +async function executeDeploy({ + origin, + remotes, + signer, + multiProvider, + outPath, + artifacts = {}, + multisigConfig = {}, +}: DeployParams) { + logBlue('All systems ready, captain! Beginning deployment...'); + + const [contractsFilePath, agentFilePath] = prepNewArtifactsFiles(outPath, [ + { filename: 'core-deployment', description: 'Contract addresses' }, + { filename: 'agent-config', description: 'Agent configs' }, + ]); + + const owner = await signer.getAddress(); + const selectedChains = [origin, ...remotes]; + const mergedContractAddrs = getMergedContractAddresses(artifacts); + + // 1. Deploy ISM factories to all deployable chains that don't have them. + log('Deploying ISM factory contracts'); + const ismFactoryDeployer = new HyperlaneIsmFactoryDeployer(multiProvider); + ismFactoryDeployer.cacheAddressesMap(mergedContractAddrs); + const ismFactoryContracts = await ismFactoryDeployer.deploy(selectedChains); + artifacts = writeMergedAddresses( + contractsFilePath, + artifacts, + ismFactoryContracts, + ); + logGreen('ISM factory contracts deployed'); + + // 2. Deploy IGPs to all deployable chains. + log('Deploying IGP contracts'); + const igpConfig = buildIgpConfigMap(owner, selectedChains, multisigConfig); + const igpDeployer = new HyperlaneIgpDeployer(multiProvider); + igpDeployer.cacheAddressesMap(artifacts); + const igpContracts = await igpDeployer.deploy(igpConfig); + artifacts = writeMergedAddresses(contractsFilePath, artifacts, igpContracts); + logGreen('IGP contracts deployed'); + + // Build an IsmFactory that covers all chains so that we can + // use it to deploy ISMs to remote chains. + const ismFactory = HyperlaneIsmFactory.fromAddressesMap( + mergedContractAddrs, + multiProvider, + ); + + // 3. Deploy ISM contracts to remote deployable chains + log('Deploying ISMs'); + const ismContracts: ChainMap<{ multisigIsm: DeployedIsm }> = {}; + const defaultIsms: ChainMap
= {}; + for (const ismOrigin of selectedChains) { + if (artifacts[ismOrigin].multisigIsm) { + log(`ISM contract recovered, skipping ISM deployment to ${ismOrigin}`); + defaultIsms[ismOrigin] = artifacts[ismOrigin].multisigIsm; + continue; + } + log(`Deploying ISM to ${ismOrigin}`); + const ismConfig = buildIsmConfig( + owner, + selectedChains.filter((r) => r !== ismOrigin), + multisigConfig, + ); + ismContracts[ismOrigin] = { + multisigIsm: await ismFactory.deploy(ismOrigin, ismConfig), + }; + defaultIsms[ismOrigin] = ismContracts[ismOrigin].multisigIsm.address; + } + artifacts = writeMergedAddresses(contractsFilePath, artifacts, ismContracts); + logGreen('ISM contracts deployed'); + + // 4. Deploy core contracts to origin chain + log(`Deploying core contracts to ${origin}`); + const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); + coreDeployer.cacheAddressesMap(artifacts); + const coreConfig = buildCoreConfigMap(owner, origin, defaultIsms); + const coreContracts = await coreDeployer.deploy(coreConfig); + artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); + logGreen('Core contracts deployed'); + + // 5. Deploy TestRecipients to all deployable chains + log('Deploying test recipient contracts'); + const testRecipientConfig = buildTestRecipientConfigMap( + selectedChains, + artifacts, + ); + const testRecipientDeployer = new TestRecipientDeployer(multiProvider); + testRecipientDeployer.cacheAddressesMap(artifacts); + const testRecipients = await testRecipientDeployer.deploy( + testRecipientConfig, + ); + artifacts = writeMergedAddresses( + contractsFilePath, + artifacts, + testRecipients, + ); + logGreen('Test recipient contracts deployed'); + + log('Writing agent configs'); + await writeAgentConfig( + agentFilePath, + artifacts, + origin, + remotes, + multiProvider, + ); + logGreen('Agent configs written'); + + logBlue('Deployment is complete!'); + logBlue(`Contract address artifacts are in ${contractsFilePath}`); + logBlue(`Agent configs are in ${agentFilePath}`); +} + +function buildIsmConfig( + owner: Address, + remotes: ChainName[], + multisigIsmConfigs: ChainMap, +): RoutingIsmConfig { + const mergedMultisigIsmConfig: ChainMap = objMerge( + defaultMultisigIsmConfigs, + multisigIsmConfigs, + ); + return { + owner, + type: ModuleType.ROUTING, + domains: Object.fromEntries( + remotes.map((remote) => [remote, mergedMultisigIsmConfig[remote]]), + ), + }; +} + +function buildCoreConfigMap( + owner: Address, + origin: ChainName, + defaultIsms: ChainMap
, +): ChainMap { + const configMap: ChainMap = {}; + configMap[origin] = { + owner, + defaultIsm: defaultIsms[origin], + }; + return configMap; +} + +function buildTestRecipientConfigMap( + chains: ChainName[], + addressesMap: HyperlaneAddressesMap, +): ChainMap { + return chains.reduce>((config, chain) => { + const interchainSecurityModule = + // TODO revisit assumption that multisigIsm is always the ISM + addressesMap[chain].multisigIsm ?? + addressesMap[chain].interchainSecurityModule ?? + ethers.constants.AddressZero; + if (interchainSecurityModule === ethers.constants.AddressZero) { + logRed('Error: No ISM for TestRecipient, deploying with zero address'); + } + config[chain] = { interchainSecurityModule }; + return config; + }, {}); +} + +function buildIgpConfigMap( + owner: Address, + selectedChains: ChainName[], + multisigIsmConfigs: ChainMap, +): ChainMap { + const mergedMultisigIsmConfig: ChainMap = objMerge( + defaultMultisigIsmConfigs, + multisigIsmConfigs, + ); + const configMap: ChainMap = {}; + for (const origin of selectedChains) { + const overhead: ChainMap = {}; + const gasOracleType: ChainMap = {}; + for (const remote of selectedChains) { + if (origin === remote) continue; + overhead[remote] = multisigIsmVerificationCost( + mergedMultisigIsmConfig[remote].threshold, + mergedMultisigIsmConfig[remote].validators.length, + ); + gasOracleType[remote] = GasOracleContractType.StorageGasOracle; + } + configMap[origin] = { + owner, + beneficiary: owner, + gasOracleType, + overhead, + oracleKey: owner, + }; + } + return configMap; +} + +function writeMergedAddresses( + filePath: string, + aAddresses: HyperlaneAddressesMap, + bContracts: HyperlaneContractsMap, +): HyperlaneAddressesMap { + const bAddresses = serializeContractsMap(bContracts); + const mergedAddresses = objMerge(aAddresses, bAddresses); + writeJson(filePath, mergedAddresses); + return mergedAddresses; +} + +async function writeAgentConfig( + filePath: string, + artifacts: HyperlaneAddressesMap, + origin: ChainName, + remotes: ChainName[], + multiProvider: MultiProvider, +) { + const selectedChains = [origin, ...remotes]; + const startBlocks: ChainMap = { ...agentStartBlocks }; + startBlocks[origin] = await multiProvider + .getProvider(origin) + .getBlockNumber(); + + const mergedAddressesMap: HyperlaneAddressesMap = objMerge( + sdkContractAddressesMap, + artifacts, + ); + const filteredAddressesMap = objFilter( + mergedAddressesMap, + (chain, v): v is HyperlaneAddresses => + selectedChains.includes(chain) && + !!v.mailbox && + !!v.interchainGasPaymaster && + !!v.validatorAnnounce, + ) as ChainMap; + + const agentConfig = buildAgentConfig( + Object.keys(filteredAddressesMap), + multiProvider, + filteredAddressesMap, + startBlocks, + ); + writeJson(filePath, agentConfig); +} diff --git a/typescript/cli/src/deploy/types.ts b/typescript/cli/src/deploy/types.ts new file mode 100644 index 000000000..85e6e4645 --- /dev/null +++ b/typescript/cli/src/deploy/types.ts @@ -0,0 +1,27 @@ +import type { ERC20Metadata, TokenType } from '@hyperlane-xyz/hyperlane-token'; +import type { Address } from '@hyperlane-xyz/utils'; + +export type MinimalTokenMetadata = Omit; + +// Types below must match the Warp UI token config schema +// It is used to generate the configs for the Warp UI +// https://github.com/hyperlane-xyz/hyperlane-warp-ui-template/blob/main/src/features/tokens/types.ts +interface BaseWarpUITokenConfig extends MinimalTokenMetadata { + type: TokenType.collateral | TokenType.native; + chainId: number; + logoURI?: string; + isNft?: boolean; +} + +interface CollateralTokenConfig extends BaseWarpUITokenConfig { + type: TokenType.collateral; + address: Address; + hypCollateralAddress: Address; +} + +interface NativeTokenConfig extends BaseWarpUITokenConfig { + type: TokenType.native; + hypNativeAddress: Address; +} + +export type WarpUITokenConfig = CollateralTokenConfig | NativeTokenConfig; diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts new file mode 100644 index 000000000..ac5b56585 --- /dev/null +++ b/typescript/cli/src/deploy/utils.ts @@ -0,0 +1,46 @@ +import { ethers } from 'ethers'; + +import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { log, logGreen } from '../../logger.js'; +import { assertNativeBalances } from '../utils/balances.js'; +import { assertSigner } from '../utils/keys.js'; + +export async function runPreflightChecks({ + origin, + remotes, + signer, + multiProvider, + minBalanceWei, +}: { + origin: ChainName; + remotes: ChainName[]; + signer: ethers.Signer; + multiProvider: MultiProvider; + minBalanceWei: string; +}) { + log('Running pre-flight checks...'); + + if (!origin || !remotes?.length) throw new Error('Invalid chain selection'); + if (remotes.includes(origin)) + throw new Error('Origin and remotes must be distinct'); + for (const chain of [origin, ...remotes]) { + const metadata = multiProvider.tryGetChainMetadata(chain); + if (!metadata) throw new Error(`No chain config found for ${chain}`); + if (metadata.protocol !== ProtocolType.Ethereum) + throw new Error('Only Ethereum chains are supported for now'); + } + logGreen('Chains are valid ✅'); + + assertSigner(signer); + logGreen('Signer is valid ✅'); + + await assertNativeBalances( + multiProvider, + signer, + [origin, ...remotes], + minBalanceWei, + ); + logGreen('Balances are sufficient ✅'); +} diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts new file mode 100644 index 000000000..e2ffc81d1 --- /dev/null +++ b/typescript/cli/src/deploy/warp.ts @@ -0,0 +1,361 @@ +import { confirm, input } from '@inquirer/prompts'; +import { ethers } from 'ethers'; + +import { + ERC20__factory, + ERC721__factory, + HypERC20Deployer, + HypERC721Deployer, + TokenConfig, + TokenFactories, + TokenType, +} from '@hyperlane-xyz/hyperlane-token'; +import { + ChainMap, + ChainName, + ConnectionClientConfig, + HyperlaneContractsMap, + MultiProvider, + RouterConfig, + chainMetadata as defaultChainMetadata, +} from '@hyperlane-xyz/sdk'; +import { Address, objMap } from '@hyperlane-xyz/utils'; + +import { log, logBlue, logGray, logGreen } from '../../logger.js'; +import { readDeploymentArtifacts } from '../config/artifacts.js'; +import { WarpRouteConfig, readWarpRouteConfig } from '../config/warp.js'; +import { MINIMUM_WARP_DEPLOY_BALANCE } from '../consts.js'; +import { + getContextWithSigner, + getMergedContractAddresses, +} from '../context.js'; +import { + prepNewArtifactsFiles, + runFileSelectionStep, + writeJson, +} from '../utils/files.js'; + +import { MinimalTokenMetadata, WarpUITokenConfig } from './types.js'; +import { runPreflightChecks } from './utils.js'; + +export async function runWarpDeploy({ + key, + chainConfigPath, + warpConfigPath, + coreArtifactsPath, + outPath, + skipConfirmation, +}: { + key: string; + chainConfigPath: string; + warpConfigPath?: string; + coreArtifactsPath?: string; + outPath: string; + skipConfirmation: boolean; +}) { + const { multiProvider, signer } = getContextWithSigner(key, chainConfigPath); + + if (!warpConfigPath) { + warpConfigPath = await runFileSelectionStep( + './configs', + 'Warp config', + 'warp', + ); + } + const warpRouteConfig = readWarpRouteConfig(warpConfigPath); + + const artifacts = coreArtifactsPath + ? readDeploymentArtifacts(coreArtifactsPath) + : undefined; + + const configs = await runBuildConfigStep({ + warpRouteConfig, + artifacts, + multiProvider, + signer, + }); + + const deploymentParams = { + ...configs, + signer, + multiProvider, + outPath, + skipConfirmation, + }; + + await runDeployPlanStep(deploymentParams); + await runPreflightChecks({ + ...deploymentParams, + minBalanceWei: MINIMUM_WARP_DEPLOY_BALANCE, + }); + await executeDeploy(deploymentParams); +} + +async function runBuildConfigStep({ + warpRouteConfig, + multiProvider, + signer, + artifacts, +}: { + warpRouteConfig: WarpRouteConfig; + multiProvider: MultiProvider; + signer: ethers.Signer; + artifacts?: HyperlaneContractsMap; +}) { + log('Assembling token configs'); + const { base, synthetics } = warpRouteConfig; + const { type: baseType, chainName: baseChainName, isNft } = base; + + const owner = await signer.getAddress(); + + const baseMetadata = await fetchBaseTokenMetadata(base, multiProvider); + log( + `Using base token metadata: Name: ${baseMetadata.name}, Symbol: ${baseMetadata.symbol}, Decimals: ${baseMetadata.decimals}`, + ); + + const mergedContractAddrs = getMergedContractAddresses(artifacts); + + // Create configs that coalesce together values from the config file, + // the artifacts, and the SDK as a fallback + const configMap: ChainMap = { + [baseChainName]: { + type: baseType, + token: + baseType === TokenType.collateral + ? base.address! + : ethers.constants.AddressZero, + owner, + mailbox: base.mailbox || mergedContractAddrs[baseChainName].mailbox, + interchainSecurityModule: + base.interchainSecurityModule || + mergedContractAddrs[baseChainName].interchainSecurityModule || + mergedContractAddrs[baseChainName].multisigIsm, + interchainGasPaymaster: + base.interchainGasPaymaster || + mergedContractAddrs[baseChainName].defaultIsmInterchainGasPaymaster, + foreignDeployment: base.foreignDeployment, + name: baseMetadata.name, + symbol: baseMetadata.symbol, + decimals: baseMetadata.decimals, + }, + }; + + for (const synthetic of synthetics) { + const sChainName = synthetic.chainName; + configMap[sChainName] = { + type: TokenType.synthetic, + name: synthetic.name || baseMetadata.name, + symbol: synthetic.symbol || baseMetadata.symbol, + totalSupply: synthetic.totalSupply || 0, + owner, + mailbox: synthetic.mailbox || mergedContractAddrs[sChainName].mailbox, + interchainSecurityModule: + synthetic.interchainSecurityModule || + mergedContractAddrs[sChainName].interchainSecurityModule || + mergedContractAddrs[sChainName].multisigIsm, + interchainGasPaymaster: + synthetic.interchainGasPaymaster || + mergedContractAddrs[sChainName].defaultIsmInterchainGasPaymaster, + foreignDeployment: synthetic.foreignDeployment, + }; + } + + // Request input for any address fields that are missing + const requiredRouterFields: Array = [ + 'mailbox', + 'interchainSecurityModule', + 'interchainGasPaymaster', + ]; + let hasShownInfo = false; + for (const [chain, token] of Object.entries(configMap)) { + for (const field of requiredRouterFields) { + if (token[field]) continue; + if (!hasShownInfo) { + logBlue( + 'Some router fields are missing. Please enter them now, add them to your warp config, or use the --core flag to use deployment artifacts.', + ); + hasShownInfo = true; + } + const value = await input({ + message: `Enter ${field} for ${getTokenName(token)} token on ${chain}`, + }); + if (!value) throw new Error(`Field ${field} required`); + token[field] = value.trim(); + } + } + + log('Token configs ready'); + return { + configMap, + metadata: baseMetadata, + origin: baseChainName, + remotes: synthetics.map(({ chainName }) => chainName), + isNft: !!isNft, + }; +} + +interface DeployParams { + configMap: ChainMap; + isNft: boolean; + metadata: MinimalTokenMetadata; + origin: ChainName; + remotes: ChainName[]; + signer: ethers.Signer; + multiProvider: MultiProvider; + outPath: string; + skipConfirmation: boolean; +} + +async function runDeployPlanStep({ + configMap, + isNft, + origin, + remotes, + signer, + skipConfirmation, +}: DeployParams) { + const address = await signer.getAddress(); + const baseToken = configMap[origin]; + const baseName = getTokenName(baseToken); + logBlue('\nDeployment plan'); + logGray('==============='); + log(`Transaction signer and owner of new contracts will be ${address}`); + log(`Deploying a warp route with a base of ${baseName} token on ${origin}`); + log(`Connecting it to new synthetic tokens on ${remotes.join(', ')}`); + log(`Using token standard ${isNft ? 'ERC721' : 'ERC20'}`); + + if (skipConfirmation) return; + + const isConfirmed = await confirm({ + message: 'Is this deployment plan correct?', + }); + if (!isConfirmed) throw new Error('Deployment cancelled'); +} + +async function executeDeploy(params: DeployParams) { + logBlue('All systems ready, captain! Beginning deployment...'); + + const { configMap, isNft, multiProvider, outPath } = params; + + const [contractsFilePath, tokenConfigPath] = prepNewArtifactsFiles(outPath, [ + { filename: 'warp-deployment', description: 'Contract addresses' }, + { filename: 'warp-ui-token-config', description: 'Warp UI token config' }, + ]); + + const deployer = isNft + ? new HypERC721Deployer(multiProvider) + : new HypERC20Deployer(multiProvider); + + const deployedContracts = await deployer.deploy(configMap); + logGreen('Hyp token deployments complete'); + + log('Writing deployment artifacts'); + writeTokenDeploymentArtifacts(contractsFilePath, deployedContracts, params); + writeWarpUiTokenConfig(tokenConfigPath, deployedContracts, params); + + logBlue('Deployment is complete!'); + logBlue(`Contract address artifacts are in ${contractsFilePath}`); + logBlue(`Warp UI token config is in ${tokenConfigPath}`); +} + +// TODO move into token classes in the SDK +async function fetchBaseTokenMetadata( + base: WarpRouteConfig['base'], + multiProvider: MultiProvider, +): Promise { + const { type, name, symbol, chainName, address, decimals, isNft } = base; + + // Skip fetching metadata if it's already provided in the config + if (name && symbol && decimals) { + return { name, symbol, decimals }; + } + + if (type === TokenType.native) { + return ( + multiProvider.getChainMetadata(base.chainName).nativeToken || + defaultChainMetadata.ethereum.nativeToken! + ); + } else if (base.type === TokenType.collateral && address) { + log(`Fetching token metadata for ${address} on ${chainName}}`); + const provider = multiProvider.getProvider(chainName); + if (isNft) { + const erc721Contract = ERC721__factory.connect(address, provider); + const [name, symbol] = await Promise.all([ + erc721Contract.name(), + erc721Contract.symbol(), + ]); + return { name, symbol, decimals: 0 }; + } else { + const erc20Contract = ERC20__factory.connect(address, provider); + const [name, symbol, decimals] = await Promise.all([ + erc20Contract.name(), + erc20Contract.symbol(), + erc20Contract.decimals(), + ]); + return { name, symbol, decimals }; + } + } else { + throw new Error(`Unsupported token: ${base}`); + } +} + +function getTokenName(token: TokenConfig) { + return token.type === TokenType.native ? 'native' : token.name; +} +function writeTokenDeploymentArtifacts( + filePath: string, + contracts: HyperlaneContractsMap, + { configMap }: DeployParams, +) { + const artifacts: ChainMap<{ + router: Address; + tokenType: TokenType; + }> = objMap(contracts, (chain, contract) => { + return { + router: contract.router.address, + tokenType: configMap[chain].type, + }; + }); + writeJson(filePath, artifacts); +} + +function writeWarpUiTokenConfig( + filePath: string, + contracts: HyperlaneContractsMap, + { configMap, isNft, metadata, origin, multiProvider }: DeployParams, +) { + const baseConfig = configMap[origin]; + const hypTokenAddr = + contracts[origin]?.router?.address || configMap[origin]?.foreignDeployment; + if (!hypTokenAddr) { + throw Error( + 'No base Hyperlane token address deployed and no foreign deployment specified', + ); + } + const commonFields = { + chainId: multiProvider.getChainId(origin), + name: metadata.name, + symbol: metadata.symbol, + decimals: metadata.decimals, + }; + let tokenConfig: WarpUITokenConfig; + if (baseConfig.type === TokenType.collateral) { + tokenConfig = { + ...commonFields, + type: TokenType.collateral, + address: baseConfig.token, + hypCollateralAddress: hypTokenAddr, + isNft, + }; + } else if (baseConfig.type === TokenType.native) { + tokenConfig = { + ...commonFields, + type: TokenType.native, + hypNativeAddress: hypTokenAddr, + }; + } else { + throw new Error(`Unsupported token type: ${baseConfig.type}`); + } + + writeJson(filePath, tokenConfig); +} diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts new file mode 100644 index 000000000..b37291f7f --- /dev/null +++ b/typescript/cli/src/send/message.ts @@ -0,0 +1,142 @@ +import { BigNumber, ethers } from 'ethers'; + +import { + ChainName, + HyperlaneContractsMap, + HyperlaneCore, + HyperlaneIgp, + MultiProvider, +} from '@hyperlane-xyz/sdk'; +import { addressToBytes32, timeout } from '@hyperlane-xyz/utils'; + +import { errorRed, log, logBlue, logGreen } from '../../logger.js'; +import { readDeploymentArtifacts } from '../config/artifacts.js'; +import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; +import { + getContextWithSigner, + getMergedContractAddresses, +} from '../context.js'; +import { runPreflightChecks } from '../deploy/utils.js'; + +const GAS_AMOUNT = 300_000; + +// TODO improve the UX here by making params optional and +// prompting for missing values +export async function sendTestMessage({ + key, + chainConfigPath, + coreArtifactsPath, + origin, + destination, + timeoutSec, + skipWaitForDelivery, +}: { + key: string; + chainConfigPath: string; + coreArtifactsPath: string; + origin: ChainName; + destination: ChainName; + timeoutSec: number; + skipWaitForDelivery: boolean; +}) { + const { signer, multiProvider } = getContextWithSigner(key, chainConfigPath); + const coreArtifacts = coreArtifactsPath + ? readDeploymentArtifacts(coreArtifactsPath) + : undefined; + + await runPreflightChecks({ + origin, + remotes: [destination], + multiProvider, + signer, + minBalanceWei: MINIMUM_TEST_SEND_BALANCE, + }); + + await timeout( + executeDelivery({ + origin, + destination, + multiProvider, + signer, + coreArtifacts, + skipWaitForDelivery, + }), + timeoutSec * 1000, + 'Timed out waiting for messages to be delivered', + ); +} + +async function executeDelivery({ + origin, + destination, + multiProvider, + signer, + coreArtifacts, + skipWaitForDelivery, +}: { + origin: ChainName; + destination: ChainName; + multiProvider: MultiProvider; + signer: ethers.Signer; + coreArtifacts?: HyperlaneContractsMap; + skipWaitForDelivery: boolean; +}) { + const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); + const core = HyperlaneCore.fromAddressesMap( + mergedContractAddrs, + multiProvider, + ); + const mailbox = core.getContracts(origin).mailbox; + const igp = HyperlaneIgp.fromAddressesMap(mergedContractAddrs, multiProvider); + const igpContract = igp.getContracts(origin).defaultIsmInterchainGasPaymaster; + + const destinationDomain = multiProvider.getDomainId(destination); + const signerAddress = await signer.getAddress(); + + let txReceipt: ethers.ContractReceipt; + try { + const recipient = mergedContractAddrs[destination].testRecipient; + if (!recipient) { + throw new Error(`Unable to find TestRecipient for ${destination}`); + } + + log('Dispatching message'); + const messageTx = await mailbox.dispatch( + destinationDomain, + addressToBytes32(recipient), + '0x48656c6c6f21', // Hello! + ); + txReceipt = await multiProvider.handleTx(origin, messageTx); + const message = core.getDispatchedMessages(txReceipt)[0]; + logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); + logBlue(`Message ID: ${message.id}`); + + // TODO requires update for v3 + const value = await igp.quoteGasPaymentForDefaultIsmIgp( + origin, + destination, + BigNumber.from(GAS_AMOUNT), + ); + log(`Paying for gas with ${value} wei`); + const paymentTx = await igpContract.payForGas( + message.id, + destinationDomain, + GAS_AMOUNT, + signerAddress, + { value }, + ); + await paymentTx.wait(); + } catch (e) { + errorRed( + `Encountered error sending message from ${origin} to ${destination}`, + ); + throw e; + } + + if (skipWaitForDelivery) return; + + log('Waiting for message delivery on destination chain...'); + // Max wait 10 minutes + await core.waitForMessageProcessed(txReceipt, 10000, 60); + logGreen('Message was delivered!'); +} diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts new file mode 100644 index 000000000..c699d57a7 --- /dev/null +++ b/typescript/cli/src/send/transfer.ts @@ -0,0 +1,196 @@ +import { BigNumber, ethers } from 'ethers'; + +import { + ERC20__factory, + EvmHypCollateralAdapter, + HypERC20Collateral__factory, + TokenType, +} from '@hyperlane-xyz/hyperlane-token'; +import { + ChainName, + HyperlaneContractsMap, + HyperlaneCore, + MultiProtocolProvider, + MultiProvider, +} from '@hyperlane-xyz/sdk'; +import { Address, timeout } from '@hyperlane-xyz/utils'; + +import { log, logBlue, logGreen } from '../../logger.js'; +import { readDeploymentArtifacts } from '../config/artifacts.js'; +import { MINIMUM_TEST_SEND_BALANCE } from '../consts.js'; +import { + getContextWithSigner, + getMergedContractAddresses, +} from '../context.js'; +import { runPreflightChecks } from '../deploy/utils.js'; +import { assertNativeBalances, assertTokenBalance } from '../utils/balances.js'; + +// TODO improve the UX here by making params optional and +// prompting for missing values +export async function sendTestTransfer({ + key, + chainConfigPath, + coreArtifactsPath, + origin, + destination, + routerAddress, + tokenType, + wei, + recipient, + timeoutSec, + skipWaitForDelivery, +}: { + key: string; + chainConfigPath: string; + coreArtifactsPath: string; + origin: ChainName; + destination: ChainName; + routerAddress: Address; + tokenType: TokenType; + wei: string; + recipient?: string; + timeoutSec: number; + skipWaitForDelivery: boolean; +}) { + const { signer, multiProvider } = getContextWithSigner(key, chainConfigPath); + const artifacts = coreArtifactsPath + ? readDeploymentArtifacts(coreArtifactsPath) + : undefined; + + if (tokenType === TokenType.collateral) { + await assertTokenBalance( + multiProvider, + signer, + origin, + routerAddress, + wei.toString(), + ); + } else if (tokenType === TokenType.native) { + await assertNativeBalances(multiProvider, signer, [origin], wei.toString()); + } else { + throw new Error( + 'Only collateral and native token types are currently supported in the CLI. For synthetic transfers, try the Warp UI.', + ); + } + + await runPreflightChecks({ + origin, + remotes: [destination], + multiProvider, + signer, + minBalanceWei: MINIMUM_TEST_SEND_BALANCE, + }); + + await timeout( + executeDelivery({ + origin, + destination, + routerAddress, + tokenType, + wei, + recipient, + signer, + multiProvider, + artifacts, + skipWaitForDelivery, + }), + timeoutSec * 1000, + 'Timed out waiting for messages to be delivered', + ); +} + +async function executeDelivery({ + origin, + destination, + routerAddress, + tokenType, + wei, + recipient, + multiProvider, + signer, + artifacts, + skipWaitForDelivery, +}: { + origin: ChainName; + destination: ChainName; + routerAddress: Address; + tokenType: TokenType; + wei: string; + recipient?: string; + multiProvider: MultiProvider; + signer: ethers.Signer; + artifacts?: HyperlaneContractsMap; + skipWaitForDelivery: boolean; +}) { + const signerAddress = await signer.getAddress(); + recipient ||= signerAddress; + + const mergedContractAddrs = getMergedContractAddresses(artifacts); + + const core = HyperlaneCore.fromAddressesMap( + mergedContractAddrs, + multiProvider, + ); + + const provider = multiProvider.getProvider(origin); + const connectedSigner = signer.connect(provider); + + if (tokenType === TokenType.collateral) { + const wrappedToken = await getWrappedToken(routerAddress, provider); + const token = ERC20__factory.connect(wrappedToken, connectedSigner); + const approval = await token.allowance(signerAddress, routerAddress); + if (approval.lt(wei)) { + const approveTx = await token.approve(routerAddress, wei); + await approveTx.wait(); + } + } + + // TODO move next section into MultiProtocolTokenApp when it exists + const adapter = new EvmHypCollateralAdapter( + origin, + MultiProtocolProvider.fromMultiProvider(multiProvider), + { token: routerAddress }, + ); + const destinationDomain = multiProvider.getDomainId(destination); + const gasPayment = await adapter.quoteGasPayment(destinationDomain); + const txValue = + tokenType === TokenType.native + ? BigNumber.from(gasPayment).add(wei).toString() + : gasPayment; + const transferTx = await adapter.populateTransferRemoteTx({ + weiAmountOrId: wei, + destination: destinationDomain, + recipient, + txValue, + }); + + const txResponse = await connectedSigner.sendTransaction(transferTx); + const txReceipt = await multiProvider.handleTx(origin, txResponse); + + const message = core.getDispatchedMessages(txReceipt)[0]; + logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); + logBlue(`Message ID: ${message.id}`); + + if (skipWaitForDelivery) return; + + // Max wait 10 minutes + await core.waitForMessageProcessed(txReceipt, 10000, 60); + logGreen(`Transfer sent to destination chain!`); +} + +async function getWrappedToken( + address: Address, + provider: ethers.providers.Provider, +): Promise
{ + try { + const contract = HypERC20Collateral__factory.connect(address, provider); + const wrappedToken = await contract.wrappedToken(); + if (ethers.utils.isAddress(wrappedToken)) return wrappedToken; + else throw new Error('Invalid wrapped token address'); + } catch (error) { + log('Error getting wrapped token', error); + throw new Error( + `Could not get wrapped token from router address ${address}`, + ); + } +} diff --git a/typescript/cli/src/status/message.ts b/typescript/cli/src/status/message.ts new file mode 100644 index 000000000..933b75cea --- /dev/null +++ b/typescript/cli/src/status/message.ts @@ -0,0 +1,36 @@ +import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk'; + +import { log, logBlue, logGreen } from '../../logger.js'; +import { readDeploymentArtifacts } from '../config/artifacts.js'; +import { getContext, getMergedContractAddresses } from '../context.js'; + +export async function checkMessageStatus({ + chainConfigPath, + coreArtifactsPath, + messageId, + destination, +}: { + chainConfigPath: string; + coreArtifactsPath: string; + messageId: string; + destination: ChainName; +}) { + const { multiProvider } = getContext(chainConfigPath); + const coreArtifacts = coreArtifactsPath + ? readDeploymentArtifacts(coreArtifactsPath) + : undefined; + + const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); + const core = HyperlaneCore.fromAddressesMap( + mergedContractAddrs, + multiProvider, + ); + const mailbox = core.getContracts(destination).mailbox; + log(`Checking status of message ${messageId} on ${destination}`); + const delivered = await mailbox.delivered(messageId); + if (delivered) { + logGreen(`Message ${messageId} was delivered`); + } else { + logBlue(`Message ${messageId} was not yet delivered`); + } +} diff --git a/typescript/cli/src/utils/balances.ts b/typescript/cli/src/utils/balances.ts new file mode 100644 index 000000000..02ee9e836 --- /dev/null +++ b/typescript/cli/src/utils/balances.ts @@ -0,0 +1,44 @@ +import { ethers } from 'ethers'; + +import { ERC20__factory } from '@hyperlane-xyz/hyperlane-token'; +import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +export async function assertNativeBalances( + multiProvider: MultiProvider, + signer: ethers.Signer, + chains: ChainName[], + minBalanceWei: string, +) { + const address = await signer.getAddress(); + const minBalance = ethers.utils.formatEther(minBalanceWei.toString()); + await Promise.all( + chains.map(async (chain) => { + const balanceWei = await multiProvider + .getProvider(chain) + .getBalance(address); + const balance = ethers.utils.formatEther(balanceWei); + if (balanceWei.lte(minBalanceWei)) + throw new Error( + `${address} has insufficient balance on ${chain}. At least ${minBalance} required but found ${balance.toString()} ETH`, + ); + }), + ); +} + +export async function assertTokenBalance( + multiProvider: MultiProvider, + signer: ethers.Signer, + chain: ChainName, + token: Address, + minBalanceWei: string, +) { + const address = await signer.getAddress(); + const provider = multiProvider.getProvider(chain); + const tokenContract = ERC20__factory.connect(token, provider); + const balanceWei = await tokenContract.balanceOf(address); + if (balanceWei.lte(minBalanceWei)) + throw new Error( + `${address} has insufficient balance on ${chain} for token ${token}. At least ${minBalanceWei} wei required but found ${balanceWei.toString()} wei`, + ); +} diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts new file mode 100644 index 000000000..50143bb63 --- /dev/null +++ b/typescript/cli/src/utils/chains.ts @@ -0,0 +1,88 @@ +import { Separator, checkbox } from '@inquirer/prompts'; +import select from '@inquirer/select'; +import chalk from 'chalk'; + +import { + ChainMap, + ChainMetadata, + mainnetChainsMetadata, + testnetChainsMetadata, +} from '@hyperlane-xyz/sdk'; + +import { log, logBlue } from '../../logger.js'; + +// A special value marker to indicate user selected +// a new chain in the list +const NEW_CHAIN_MARKER = '__new__'; + +export async function runOriginAndRemotesSelectionStep( + customChains: ChainMap, +) { + const origin = await runSingleChainSelectionStep( + customChains, + 'Select origin chain (the chain to which you will deploy now)', + ); + const remotes = await runMultiChainSelectionStep( + customChains, + 'Select remote chains the origin will send messages to', + ); + return { origin, remotes }; +} + +export async function runSingleChainSelectionStep( + customChains: ChainMap, + message = 'Select chain', +) { + const choices = getChainChoices(customChains); + const origin = (await select({ + message, + choices, + pageSize: 20, + })) as string; + handleNewChain([origin]); + return origin; +} + +export async function runMultiChainSelectionStep( + customChains: ChainMap, + message = 'Select chains', +) { + const choices = getChainChoices(customChains); + const remotes = (await checkbox({ + message, + choices, + pageSize: 20, + })) as string[]; + handleNewChain(remotes); + if (!remotes?.length) throw new Error('No remote chains selected'); + return remotes; +} + +function getChainChoices(customChains: ChainMap) { + const chainsToChoices = (chains: ChainMetadata[]) => + chains.map((c) => ({ name: c.name, value: c.name })); + const choices: Parameters['0']['choices'] = [ + new Separator('--Custom Chains--'), + ...chainsToChoices(Object.values(customChains)), + { name: '(New custom chain)', value: NEW_CHAIN_MARKER }, + new Separator('--Mainnet Chains--'), + ...chainsToChoices(mainnetChainsMetadata), + new Separator('--Testnet Chains--'), + ...chainsToChoices(testnetChainsMetadata), + ]; + return choices; +} + +function handleNewChain(chainNames: string[]) { + if (chainNames.includes(NEW_CHAIN_MARKER)) { + logBlue( + 'To use a new chain, use the --config argument add them to that file', + ); + log( + chalk.blue('Use the'), + chalk.magentaBright('hyperlane config create'), + chalk.blue('command to create new configs'), + ); + process.exit(0); + } +} diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts new file mode 100644 index 000000000..7d57dec27 --- /dev/null +++ b/typescript/cli/src/utils/files.ts @@ -0,0 +1,178 @@ +import { input } from '@inquirer/prompts'; +import select from '@inquirer/select'; +import fs from 'fs'; +import path from 'path'; +import { parse as yamlParse, stringify as yamlStringify } from 'yaml'; + +import { objMerge } from '@hyperlane-xyz/utils'; + +import { logBlue } from '../../logger.js'; + +import { getTimestampForFilename } from './time.js'; + +export type FileFormat = 'yaml' | 'json'; + +export function readFileAtPath(filepath: string) { + if (!fs.existsSync(filepath)) { + throw Error(`File doesn't exist at ${filepath}`); + } + return fs.readFileSync(filepath, 'utf8'); +} + +export function writeFileAtPath(filepath: string, value: string) { + const dirname = path.dirname(filepath); + if (!fs.existsSync(dirname)) { + fs.mkdirSync(dirname, { recursive: true }); + } + fs.writeFileSync(filepath, value); +} + +export function readJson(filepath: string): T { + return JSON.parse(readFileAtPath(filepath)) as T; +} + +export function tryReadJson(filepath: string): T | null { + try { + return readJson(filepath) as T; + } catch (error) { + return null; + } +} + +export function writeJson(filepath: string, obj: any) { + writeFileAtPath(filepath, JSON.stringify(obj, null, 2) + '\n'); +} + +export function mergeJson>( + filepath: string, + obj: T, +) { + if (fs.existsSync(filepath)) { + const previous = readJson(filepath); + writeJson(filepath, objMerge(previous, obj)); + } else { + writeJson(filepath, obj); + } +} + +export function readYaml(filepath: string): T { + return yamlParse(readFileAtPath(filepath)) as T; +} + +export function tryReadYamlAtPath(filepath: string): T | null { + try { + return readYaml(filepath); + } catch (error) { + return null; + } +} + +export function writeYaml(filepath: string, obj: any) { + writeFileAtPath(filepath, yamlStringify(obj, null, 2) + '\n'); +} + +export function mergeYaml>( + filepath: string, + obj: T, +) { + if (fs.existsSync(filepath)) { + const previous = readYaml(filepath); + writeYaml(filepath, objMerge(previous, obj)); + } else { + writeYaml(filepath, obj); + } +} + +export function readYamlOrJson(filepath: string, format?: FileFormat): T { + return resolveYamlOrJson(filepath, readJson, readYaml, format); +} + +export function writeYamlOrJson( + filepath: string, + obj: Record, + format?: FileFormat, +) { + return resolveYamlOrJson( + filepath, + (f: string) => writeJson(f, obj), + (f: string) => writeYaml(f, obj), + format, + ); +} + +export function mergeYamlOrJson( + filepath: string, + obj: Record, + format?: FileFormat, +) { + return resolveYamlOrJson( + filepath, + (f: string) => mergeJson(f, obj), + (f: string) => mergeYaml(f, obj), + format, + ); +} + +function resolveYamlOrJson( + filepath: string, + jsonFn: any, + yamlFn: any, + format?: FileFormat, +) { + if (format === 'json' || filepath.endsWith('.json')) { + return jsonFn(filepath); + } else if ( + format === 'yaml' || + filepath.endsWith('.yaml') || + filepath.endsWith('.yml') + ) { + return yamlFn(filepath); + } else { + throw new Error(`Invalid file format for ${filepath}`); + } +} + +export function prepNewArtifactsFiles( + outPath: string, + files: Array<{ filename: string; description: string }>, +) { + const timestamp = getTimestampForFilename(); + const newPaths: string[] = []; + for (const file of files) { + const filePath = path.join(outPath, `${file.filename}-${timestamp}.json`); + // Write empty object to ensure permissions are okay + writeJson(filePath, {}); + newPaths.push(filePath); + logBlue(`${file.description} will be written to ${filePath}`); + } + return newPaths; +} + +export async function runFileSelectionStep( + folderPath: string, + description: string, + pattern?: string, +) { + let filenames = fs.readdirSync(folderPath); + if (pattern) { + filenames = filenames.filter((f) => f.includes(pattern)); + } + + let filename = (await select({ + message: `Select ${description} file`, + choices: [ + ...filenames.map((f) => ({ name: f, value: f })), + { name: '(Other file)', value: null }, + ], + pageSize: 20, + })) as string; + + if (filename) return path.join(folderPath, filename); + + filename = await input({ + message: `Enter ${description} filepath`, + }); + + if (filename) return filename; + else throw new Error(`No filepath entered ${description}`); +} diff --git a/typescript/cli/src/utils/keys.ts b/typescript/cli/src/utils/keys.ts new file mode 100644 index 000000000..39d01b1c1 --- /dev/null +++ b/typescript/cli/src/utils/keys.ts @@ -0,0 +1,18 @@ +import { ethers } from 'ethers'; + +import { ensure0x } from '@hyperlane-xyz/utils'; + +export function keyToSigner(key: string) { + if (!key) throw new Error('No key provided'); + const formattedKey = key.trim().toLowerCase(); + if (ethers.utils.isHexString(ensure0x(formattedKey))) + return new ethers.Wallet(ensure0x(formattedKey)); + else if (formattedKey.split(' ').length >= 6) + return ethers.Wallet.fromMnemonic(formattedKey); + else throw new Error('Invalid key format'); +} + +export function assertSigner(signer: ethers.Signer) { + if (!signer || !ethers.Signer.isSigner(signer)) + throw new Error('Signer is invalid'); +} diff --git a/typescript/cli/src/utils/time.ts b/typescript/cli/src/utils/time.ts new file mode 100644 index 000000000..a490684cc --- /dev/null +++ b/typescript/cli/src/utils/time.ts @@ -0,0 +1,6 @@ +export function getTimestampForFilename() { + const now = new Date(); + return `${now.getFullYear()}-${ + now.getMonth() + 1 + }-${now.getDate()}-${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}`; +} diff --git a/typescript/cli/tsconfig.json b/typescript/cli/tsconfig.json new file mode 100644 index 000000000..cfeac3bad --- /dev/null +++ b/typescript/cli/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "Node16", + "moduleResolution": "Node16", + "outDir": "./dist/", + "rootDir": "." + }, + "include": ["./cli.ts", "./logger.ts", "./src/**/*.ts", "./src/*.d.ts", "./examples/**/*.ts"], +} diff --git a/typescript/helloworld/.eslintrc b/typescript/helloworld/.eslintrc index d2b44668c..446616f52 100644 --- a/typescript/helloworld/.eslintrc +++ b/typescript/helloworld/.eslintrc @@ -29,7 +29,7 @@ "@typescript-eslint/no-require-imports": ["warn"], "@typescript-eslint/no-unused-vars": [ "error", - { + { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", "caughtErrorsIgnorePattern": "^_" diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index b66c9d89f..9bcbed5ec 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,33 +1,33 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "1.5.1", + "version": "1.5.4-beta0", "dependencies": { - "@hyperlane-xyz/sdk": "1.5.1", + "@hyperlane-xyz/sdk": "1.5.4-beta0", "@openzeppelin/contracts-upgradeable": "^4.8.0", "ethers": "^5.7.2" }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.2.1", "@nomiclabs/hardhat-waffle": "^2.0.3", - "@trivago/prettier-plugin-sort-imports": "^3.2.0", + "@trivago/prettier-plugin-sort-imports": "^4.2.0", "@typechain/ethers-v5": "10.0.0", "@typechain/hardhat": "^6.0.0", "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "chai": "^4.3.0", - "eslint": "^8.16.0", - "eslint-config-prettier": "^8.5.0", + "eslint": "^8.43.0", + "eslint-config-prettier": "^8.8.0", "ethereum-waffle": "^3.4.4", "hardhat": "^2.16.1", "hardhat-gas-reporter": "^1.0.9", - "prettier": "^2.4.1", + "prettier": "^2.8.8", "prettier-plugin-solidity": "^1.0.0-beta.5", "solhint": "^3.3.2", "solhint-plugin-prettier": "^0.0.5", "solidity-coverage": "^0.8.3", - "ts-node": "^10.8.0", + "ts-node": "^10.9.1", "typechain": "8.0.0", "typescript": "^5.1.6" }, @@ -47,7 +47,7 @@ "packageManager": "yarn@3.2.0", "repository": { "type": "git", - "url": "https://github.com/hyperlane-xyz/hyperlane-app-template" + "url": "https://github.com/hyperlane-xyz/hyperlane-monorepo" }, "scripts": { "build": "hardhat compile && tsc", @@ -65,5 +65,6 @@ "lodash": "^4.17.21", "async": "^2.6.4", "undici": "^5.11" - } + }, + "stableVersion": "1.5.3" } diff --git a/typescript/infra/package.json b/typescript/infra/package.json index efe23c50f..a03b8af04 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -11,10 +11,10 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "1.5.1", - "@hyperlane-xyz/hyperlane-token": "1.5.1", - "@hyperlane-xyz/sdk": "1.5.1", - "@hyperlane-xyz/utils": "1.5.1", + "@hyperlane-xyz/helloworld": "1.5.4-beta0", + "@hyperlane-xyz/hyperlane-token": "1.5.4-beta0", + "@hyperlane-xyz/sdk": "1.5.4-beta0", + "@hyperlane-xyz/utils": "1.5.4-beta0", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "^1.3.0", "@safe-global/protocol-kit": "^1.2.0", @@ -24,7 +24,7 @@ "dotenv": "^10.0.0", "prom-client": "^14.0.1", "prompts": "^2.4.2", - "yargs": "^17.4.1" + "yargs": "^17.7.2" }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.2.1", @@ -33,13 +33,13 @@ "@types/mocha": "^9.1.0", "@types/node": "^16.9.1", "@types/prompts": "^2.0.14", - "@types/yargs": "^17.0.10", + "@types/yargs": "^17.0.24", "chai": "^4.3.4", "ethereum-waffle": "^3.4.4", "ethers": "^5.7.2", "hardhat": "^2.16.1", - "prettier": "^2.4.1", - "ts-node": "^10.8.0", + "prettier": "^2.8.8", + "ts-node": "^10.9.1", "typescript": "^5.1.6" }, "private": true, diff --git a/typescript/infra/src/config/environment.ts b/typescript/infra/src/config/environment.ts index bdaf9d1da..2a53ba74d 100644 --- a/typescript/infra/src/config/environment.ts +++ b/typescript/infra/src/config/environment.ts @@ -27,7 +27,7 @@ import { LiquidityLayerRelayerConfig } from './middleware'; export const EnvironmentNames = Object.keys(environments); export type DeployEnvironment = keyof typeof environments; export type EnvironmentChain = Extract< - keyof typeof environments[E], + keyof (typeof environments)[E], ChainName >; diff --git a/typescript/infra/src/roles.ts b/typescript/infra/src/roles.ts index b1f425ba7..4da4bf3c0 100644 --- a/typescript/infra/src/roles.ts +++ b/typescript/infra/src/roles.ts @@ -21,5 +21,5 @@ export const ALL_AGENT_ROLES = [ Role.Relayer, Role.Scraper, ] as const; -export type AgentRole = typeof ALL_AGENT_ROLES[number]; +export type AgentRole = (typeof ALL_AGENT_ROLES)[number]; export type AgentChainNames = Record; diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 8a5f7972c..c2bca6b4d 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "1.5.1", + "version": "1.5.4-beta0", "dependencies": { - "@hyperlane-xyz/core": "1.5.1", - "@hyperlane-xyz/utils": "1.5.1", + "@hyperlane-xyz/core": "1.5.4-beta0", + "@hyperlane-xyz/utils": "1.5.4-beta0", "@solana/web3.js": "^1.78.0", "@types/coingecko-api": "^1.0.10", "@types/debug": "^4.1.7", @@ -28,9 +28,9 @@ "fs": "0.0.1-security", "hardhat": "^2.16.1", "mocha": "^9.2.2", - "prettier": "^2.4.1", + "prettier": "^2.8.8", "sinon": "^13.0.2", - "ts-node": "^10.8.0", + "ts-node": "^10.9.1", "typescript": "^5.1.6" }, "files": [ @@ -57,5 +57,6 @@ "test:unit": "mocha --config .mocharc.json './src/**/*.test.ts'", "test:hardhat": "hardhat test $(find ./src -name \"*.hardhat-test.ts\")" }, - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "stableVersion": "1.5.3" } diff --git a/typescript/sdk/src/app/HyperlaneApp.ts b/typescript/sdk/src/app/HyperlaneApp.ts index 66fb2c070..99329db1a 100644 --- a/typescript/sdk/src/app/HyperlaneApp.ts +++ b/typescript/sdk/src/app/HyperlaneApp.ts @@ -16,8 +16,10 @@ import { MultiGeneric } from '../utils/MultiGeneric'; export class HyperlaneApp< Factories extends HyperlaneFactories, > extends MultiGeneric> { + public readonly contractsMap: HyperlaneContractsMap; + constructor( - public readonly contractsMap: HyperlaneContractsMap, + contractsMap: HyperlaneContractsMap, public readonly multiProvider: MultiProvider, public readonly logger = debug('hyperlane:App'), ) { @@ -25,6 +27,7 @@ export class HyperlaneApp< connectContracts(contracts, multiProvider.getSignerOrProvider(chain)), ); super(connectedContractsMap); + this.contractsMap = connectedContractsMap; } getContracts(chain: ChainName): HyperlaneContracts { diff --git a/typescript/sdk/src/consts/agentStartBlocks.ts b/typescript/sdk/src/consts/agentStartBlocks.ts new file mode 100644 index 000000000..6ac42ad51 --- /dev/null +++ b/typescript/sdk/src/consts/agentStartBlocks.ts @@ -0,0 +1,26 @@ +import { ChainMap } from '../types'; + +// TODO this was previously in hyp-deploy, but ideally should be integrated +// into the ChainMetadata type +export const agentStartBlocks: ChainMap = { + // --------------- Mainnets --------------------- + celo: 16884144, + ethereum: 16271503, + avalanche: 24145479, + polygon: 37313389, + bsc: 25063295, + arbitrum: 49073182, + optimism: 55698988, + moonbeam: 2595747, + gnosis: 25900000, + // --------------- Testnets --------------------- + alfajores: 14863532, + fuji: 16330615, + mumbai: 29390033, + bsctestnet: 25001629, + goerli: 8039005, + sepolia: 3082913, + moonbasealpha: 3310405, + optimismgoerli: 3055263, + arbitrumgoerli: 1941997, +}; diff --git a/typescript/sdk/src/consts/environments/index.ts b/typescript/sdk/src/consts/environments/index.ts index 55e49c1bb..c5628c4cb 100644 --- a/typescript/sdk/src/consts/environments/index.ts +++ b/typescript/sdk/src/consts/environments/index.ts @@ -11,7 +11,7 @@ export const hyperlaneEnvironments = { test, testnet, mainnet }; export type HyperlaneEnvironment = keyof typeof hyperlaneEnvironments; export type HyperlaneEnvironmentChain = Extract< - keyof typeof hyperlaneEnvironments[E], + keyof (typeof hyperlaneEnvironments)[E], ChainName >; @@ -19,4 +19,7 @@ export type HyperlaneEnvironmentChain = Extract< export const hyperlaneContractAddresses = objMerge( hyperlaneEnvironments.testnet, hyperlaneEnvironments.mainnet, -) as Record; +) as Record< + CoreChainName, + (typeof hyperlaneEnvironments)['mainnet']['ethereum'] +>; diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index 8f123bb88..771e7c312 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -69,7 +69,7 @@ export class HyperlaneCore extends HyperlaneApp { destination: ChainName, delayMs?: number, maxAttempts?: number, - ): Promise { + ): Promise { await pollAsync( async () => { this.logger(`Checking if message ${messageId} was processed`); @@ -77,7 +77,7 @@ export class HyperlaneCore extends HyperlaneApp { const delivered = await mailbox.delivered(messageId); if (delivered) { this.logger(`Message ${messageId} was processed`); - return; + return true; } else { throw new Error(`Message ${messageId} not yet processed`); } @@ -85,7 +85,7 @@ export class HyperlaneCore extends HyperlaneApp { delayMs, maxAttempts, ); - return; + return true; } waitForMessageProcessing( @@ -112,6 +112,7 @@ export class HyperlaneCore extends HyperlaneApp { ), ), ); + this.logger(`All messages processed for tx ${sourceTx.transactionHash}`); } // Redundant with static method but keeping for backwards compatibility diff --git a/typescript/sdk/src/core/MultiProtocolCore.test.ts b/typescript/sdk/src/core/MultiProtocolCore.test.ts index 5c21e842d..ece1aedfd 100644 --- a/typescript/sdk/src/core/MultiProtocolCore.test.ts +++ b/typescript/sdk/src/core/MultiProtocolCore.test.ts @@ -62,7 +62,7 @@ describe('MultiProtocolCore', () => { type: ProviderType.EthersV5, receipt, }); - }); + }).timeout(10000); it('to Sealevel', async () => { const multiProvider = new MultiProtocolProvider(); @@ -88,6 +88,6 @@ describe('MultiProtocolCore', () => { type: ProviderType.EthersV5, receipt, }); - }); + }).timeout(10000); }); }); diff --git a/typescript/sdk/src/core/MultiProtocolCore.ts b/typescript/sdk/src/core/MultiProtocolCore.ts index 971394f54..ddcdc3a3e 100644 --- a/typescript/sdk/src/core/MultiProtocolCore.ts +++ b/typescript/sdk/src/core/MultiProtocolCore.ts @@ -57,15 +57,15 @@ export class MultiProtocolCore extends MultiProtocolApp< throw new Error(`No adapter for protocol ${protocol}`); } - waitForMessagesProcessed( + async waitForMessagesProcessed( origin: ChainName, destination: ChainName, sourceTx: TypedTransactionReceipt, delayMs?: number, maxAttempts?: number, - ): Promise { + ): Promise { const messages = this.adapter(origin).extractMessageIds(sourceTx); - return Promise.all( + await Promise.all( messages.map((msg) => this.adapter(destination).waitForMessageProcessed( msg.messageId, @@ -75,5 +75,6 @@ export class MultiProtocolCore extends MultiProtocolApp< ), ), ); + return true; } } diff --git a/typescript/sdk/src/core/adapters/EvmCoreAdapter.ts b/typescript/sdk/src/core/adapters/EvmCoreAdapter.ts index 4eb00a380..a89da035b 100644 --- a/typescript/sdk/src/core/adapters/EvmCoreAdapter.ts +++ b/typescript/sdk/src/core/adapters/EvmCoreAdapter.ts @@ -60,7 +60,7 @@ export class EvmCoreAdapter extends BaseEvmAdapter implements ICoreAdapter { destination: ChainName, delayMs?: number, maxAttempts?: number, - ): Promise { + ): Promise { return this.core.waitForMessageIdProcessed( messageId, destination, diff --git a/typescript/sdk/src/core/adapters/SealevelCoreAdapter.ts b/typescript/sdk/src/core/adapters/SealevelCoreAdapter.ts index 1709d9f0f..bd5adce44 100644 --- a/typescript/sdk/src/core/adapters/SealevelCoreAdapter.ts +++ b/typescript/sdk/src/core/adapters/SealevelCoreAdapter.ts @@ -57,7 +57,7 @@ export class SealevelCoreAdapter destination: ChainName, delayMs?: number, maxAttempts?: number, - ): Promise { + ): Promise { const pda = SealevelCoreAdapter.deriveMailboxMessageProcessedPda( this.addresses.mailbox, messageId, @@ -75,6 +75,8 @@ export class SealevelCoreAdapter delayMs, maxAttempts, ); + + return true; } static parseMessageDispatchLogs( diff --git a/typescript/sdk/src/core/adapters/types.ts b/typescript/sdk/src/core/adapters/types.ts index da97bc36d..7d2268922 100644 --- a/typescript/sdk/src/core/adapters/types.ts +++ b/typescript/sdk/src/core/adapters/types.ts @@ -13,5 +13,5 @@ export interface ICoreAdapter extends BaseAppAdapter { destination: ChainName, delayMs?: number, maxAttempts?: number, - ): Promise; + ): Promise; } diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 451bea786..744dc7b47 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -6,6 +6,7 @@ export { BaseSealevelAdapter, MultiProtocolApp, } from './app/MultiProtocolApp'; +export { agentStartBlocks } from './consts/agentStartBlocks'; export { chainIdToMetadata, chainMetadata, diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts index c712354e7..f32880bc0 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts @@ -33,6 +33,7 @@ import { RoutingIsmConfig, } from './types'; +// TODO this should handle cached addresses like the other deployers export class HyperlaneIsmFactory extends HyperlaneApp { static fromEnvironment( env: Env, diff --git a/typescript/sdk/src/metadata/ChainMetadataManager.ts b/typescript/sdk/src/metadata/ChainMetadataManager.ts index 973148268..4c9e57466 100644 --- a/typescript/sdk/src/metadata/ChainMetadataManager.ts +++ b/typescript/sdk/src/metadata/ChainMetadataManager.ts @@ -165,6 +165,21 @@ export class ChainMetadataManager { return getDomainId(metadata); } + /** + * Get the protocol type for a given chain name, chain id, or domain id + */ + tryGetProtocol(chainNameOrId: ChainName | number): ProtocolType | null { + return this.tryGetChainMetadata(chainNameOrId)?.protocol ?? null; + } + + /** + * Get the protocol type for a given chain name, chain id, or domain id + * @throws if chain's metadata or protocol has not been set + */ + getProtocol(chainNameOrId: ChainName | number): ProtocolType { + return this.getChainMetadata(chainNameOrId).protocol; + } + /** * Get the domain ids for a list of chain names, chain ids, or domain ids * @throws if any chain's metadata has not been set diff --git a/typescript/token/package.json b/typescript/token/package.json index 48481563d..092ffcca5 100644 --- a/typescript/token/package.json +++ b/typescript/token/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/hyperlane-token", "description": "A template for interchain ERC20 and ERC721 tokens using Hyperlane", - "version": "1.5.1", + "version": "1.5.4-beta0", "dependencies": { - "@hyperlane-xyz/core": "1.5.1", - "@hyperlane-xyz/sdk": "1.5.1", - "@hyperlane-xyz/utils": "1.5.1", + "@hyperlane-xyz/core": "1.5.4-beta0", + "@hyperlane-xyz/sdk": "1.5.4-beta0", + "@hyperlane-xyz/utils": "1.5.4-beta0", "@openzeppelin/contracts-upgradeable": "^4.8.0", "@solana/spl-token": "^0.3.8", "ethers": "^5.7.2" @@ -13,24 +13,24 @@ "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.2.1", "@nomiclabs/hardhat-waffle": "^2.0.3", - "@trivago/prettier-plugin-sort-imports": "^3.2.0", + "@trivago/prettier-plugin-sort-imports": "^4.2.0", "@typechain/ethers-v5": "10.0.0", "@typechain/hardhat": "^6.0.0", "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "chai": "^4.3.0", - "eslint": "^8.16.0", - "eslint-config-prettier": "^8.5.0", + "eslint": "^8.43.0", + "eslint-config-prettier": "^8.8.0", "ethereum-waffle": "^3.4.4", "hardhat": "^2.16.1", "hardhat-gas-reporter": "^1.0.9", - "prettier": "^2.4.1", + "prettier": "^2.8.8", "prettier-plugin-solidity": "^1.0.0-beta.5", "solhint": "^3.3.2", "solhint-plugin-prettier": "^0.0.5", "solidity-coverage": "^0.8.3", - "ts-node": "^10.8.0", + "ts-node": "^10.9.1", "typechain": "8.0.0", "typescript": "^5.1.6" }, @@ -62,5 +62,6 @@ "test": "hardhat test ./test/*.test.ts", "deploy-warp-route": "DEBUG=* ts-node scripts/deploy" }, - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "stableVersion": "1.5.3" } diff --git a/typescript/token/tsconfig.json b/typescript/token/tsconfig.json index 18f5bb5bc..3df5408c8 100644 --- a/typescript/token/tsconfig.json +++ b/typescript/token/tsconfig.json @@ -12,4 +12,4 @@ "./src/types/hardhat.d.ts", "hardhat.config.ts" ], -} +} \ No newline at end of file diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 25d4172fb..baaa9ec84 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities and types for the Hyperlane network", - "version": "1.5.1", + "version": "1.5.4-beta0", "dependencies": { "@solana/web3.js": "^1.78.0", "bignumber.js": "^9.1.1", @@ -9,7 +9,7 @@ }, "devDependencies": { "chai": "^4.3.0", - "prettier": "^2.4.1", + "prettier": "^2.8.8", "typescript": "^5.1.6" }, "homepage": "https://www.hyperlane.xyz", @@ -32,5 +32,6 @@ "types": "dist/index.d.ts", "files": [ "/dist" - ] + ], + "stableVersion": "1.5.3" } diff --git a/typescript/utils/src/types.ts b/typescript/utils/src/types.ts index 525f26feb..7135114bf 100644 --- a/typescript/utils/src/types.ts +++ b/typescript/utils/src/types.ts @@ -17,7 +17,7 @@ export const ProtocolSmallestUnit = { export type Domain = number; export type Address = string; export type AddressBytes32 = string; -export type ChainCaip2Id = `${string}:${string}`; // e.g. ethereum:1 or solana:mainnet-beta +export type ChainCaip2Id = `${string}:${string}`; // e.g. ethereum:1 or sealevel:1399811149 export type TokenCaip19Id = `${string}:${string}/${string}:${string}`; // e.g. ethereum:1/erc20:0x6b175474e89094c44da98b954eedeac495271d0f export type HexString = string; diff --git a/yarn.lock b/yarn.lock index 04fa0dddc..69f772de4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2419,7 +2419,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.7": version: 7.16.7 resolution: "@babel/code-frame@npm:7.16.7" dependencies: @@ -2428,87 +2428,53 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.17.10": - version: 7.18.5 - resolution: "@babel/compat-data@npm:7.18.5" - checksum: 1baee39fcf0992402ed12d6be43739f3bfb7f0cacddee8959236692ae926bcc3f4fe5abdd907870f4fc8b9fd798c1e6e2999ae97c9b8aedbd834fe03f2765e73 - languageName: node - linkType: hard - -"@babel/core@npm:7.13.10": - version: 7.13.10 - resolution: "@babel/core@npm:7.13.10" +"@babel/code-frame@npm:^7.22.13": + version: 7.22.13 + resolution: "@babel/code-frame@npm:7.22.13" dependencies: - "@babel/code-frame": ^7.12.13 - "@babel/generator": ^7.13.9 - "@babel/helper-compilation-targets": ^7.13.10 - "@babel/helper-module-transforms": ^7.13.0 - "@babel/helpers": ^7.13.10 - "@babel/parser": ^7.13.10 - "@babel/template": ^7.12.13 - "@babel/traverse": ^7.13.0 - "@babel/types": ^7.13.0 - convert-source-map: ^1.7.0 - debug: ^4.1.0 - gensync: ^1.0.0-beta.2 - json5: ^2.1.2 - lodash: ^4.17.19 - semver: ^6.3.0 - source-map: ^0.5.0 - checksum: 9b3362fd02e6a4f3ad642893312ec3d22713c4eeb2571c994d49c31f38d24893a6a18f4b49abb8d56b510e116278608eaddde2ca72ccb39ab29350efa5af39de + "@babel/highlight": ^7.22.13 + chalk: ^2.4.2 + checksum: 22e342c8077c8b77eeb11f554ecca2ba14153f707b85294fcf6070b6f6150aae88a7b7436dd88d8c9289970585f3fe5b9b941c5aa3aa26a6d5a8ef3f292da058 languageName: node linkType: hard -"@babel/generator@npm:7.13.9": - version: 7.13.9 - resolution: "@babel/generator@npm:7.13.9" +"@babel/generator@npm:7.17.7": + version: 7.17.7 + resolution: "@babel/generator@npm:7.17.7" dependencies: - "@babel/types": ^7.13.0 + "@babel/types": ^7.17.0 jsesc: ^2.5.1 source-map: ^0.5.0 - checksum: 1b0e9fa1b5ea6656f0abeeedc99ff7bffa455d7bf118f4d17a75d80c439206b4ba3e1071c104b486b7447689512969286cbde505e6169ab38cf437e13ca54225 + checksum: e7344b9b4559115f2754ecc2ae9508412ea6a8f617544cd3d3f17cabc727bd30630765f96c8a4ebc8901ded1492a3a6c23d695a4f1e8f3042f860b30c891985c languageName: node linkType: hard -"@babel/generator@npm:^7.13.0, @babel/generator@npm:^7.13.9, @babel/generator@npm:^7.18.2": - version: 7.18.2 - resolution: "@babel/generator@npm:7.18.2" +"@babel/generator@npm:^7.17.3": + version: 7.22.9 + resolution: "@babel/generator@npm:7.22.9" dependencies: - "@babel/types": ^7.18.2 - "@jridgewell/gen-mapping": ^0.3.0 + "@babel/types": ^7.22.5 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 jsesc: ^2.5.1 - checksum: d0661e95532ddd97566d41fec26355a7b28d1cbc4df95fe80cc084c413342935911b48db20910708db39714844ddd614f61c2ec4cca3fb10181418bdcaa2e7a3 + checksum: 7c9d2c58b8d5ac5e047421a6ab03ec2ff5d9a5ff2c2212130a0055e063ac349e0b19d435537d6886c999771aef394832e4f54cd9fc810100a7f23d982f6af06b languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.13.10": - version: 7.18.2 - resolution: "@babel/helper-compilation-targets@npm:7.18.2" - dependencies: - "@babel/compat-data": ^7.17.10 - "@babel/helper-validator-option": ^7.16.7 - browserslist: ^4.20.2 - semver: ^6.3.0 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 4f02e79f20c0b3f8db5049ba8c35027c41ccb3fc7884835d04e49886538e0f55702959db1bb75213c94a5708fec2dc81a443047559a4f184abb884c72c0059b4 - languageName: node - linkType: hard - -"@babel/helper-environment-visitor@npm:^7.16.7, @babel/helper-environment-visitor@npm:^7.18.2": +"@babel/helper-environment-visitor@npm:^7.16.7": version: 7.18.2 resolution: "@babel/helper-environment-visitor@npm:7.18.2" checksum: 1a9c8726fad454a082d077952a90f17188e92eabb3de236cb4782c49b39e3f69c327e272b965e9a20ff8abf37d30d03ffa6fd7974625a6c23946f70f7527f5e9 languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.12.13, @babel/helper-function-name@npm:^7.17.9": - version: 7.17.9 - resolution: "@babel/helper-function-name@npm:7.17.9" +"@babel/helper-function-name@npm:^7.16.7": + version: 7.22.5 + resolution: "@babel/helper-function-name@npm:7.22.5" dependencies: - "@babel/template": ^7.16.7 - "@babel/types": ^7.17.0 - checksum: a59b2e5af56d8f43b9b0019939a43774754beb7cb01a211809ca8031c71890999d07739e955343135ec566c4d8ff725435f1f60fb0af3bb546837c1f9f84f496 + "@babel/template": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: 6b1f6ce1b1f4e513bf2c8385a557ea0dd7fa37971b9002ad19268ca4384bbe90c09681fe4c076013f33deabc63a53b341ed91e792de741b4b35e01c00238177a languageName: node linkType: hard @@ -2521,71 +2487,40 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.16.7": +"@babel/helper-split-export-declaration@npm:^7.16.7": version: 7.16.7 - resolution: "@babel/helper-module-imports@npm:7.16.7" + resolution: "@babel/helper-split-export-declaration@npm:7.16.7" dependencies: "@babel/types": ^7.16.7 - checksum: ddd2c4a600a2e9a4fee192ab92bf35a627c5461dbab4af31b903d9ba4d6b6e59e0ff3499fde4e2e9a0eebe24906f00b636f8b4d9bd72ff24d50e6618215c3212 - languageName: node - linkType: hard - -"@babel/helper-module-transforms@npm:^7.13.0": - version: 7.18.0 - resolution: "@babel/helper-module-transforms@npm:7.18.0" - dependencies: - "@babel/helper-environment-visitor": ^7.16.7 - "@babel/helper-module-imports": ^7.16.7 - "@babel/helper-simple-access": ^7.17.7 - "@babel/helper-split-export-declaration": ^7.16.7 - "@babel/helper-validator-identifier": ^7.16.7 - "@babel/template": ^7.16.7 - "@babel/traverse": ^7.18.0 - "@babel/types": ^7.18.0 - checksum: 824c3967c08d75bb36adc18c31dcafebcd495b75b723e2e17c6185e88daf5c6db62a6a75d9f791b5f38618a349e7cb32503e715a1b9a4e8bad4d0f43e3e6b523 - languageName: node - linkType: hard - -"@babel/helper-simple-access@npm:^7.17.7": - version: 7.18.2 - resolution: "@babel/helper-simple-access@npm:7.18.2" - dependencies: - "@babel/types": ^7.18.2 - checksum: c0862b56db7e120754d89273a039b128c27517389f6a4425ff24e49779791e8fe10061579171fb986be81fa076778acb847c709f6f5e396278d9c5e01360c375 + checksum: e10aaf135465c55114627951b79115f24bc7af72ecbb58d541d66daf1edaee5dde7cae3ec8c3639afaf74526c03ae3ce723444e3b5b3dc77140c456cd84bcaa1 languageName: node linkType: hard -"@babel/helper-split-export-declaration@npm:^7.12.13, @babel/helper-split-export-declaration@npm:^7.16.7": - version: 7.16.7 - resolution: "@babel/helper-split-export-declaration@npm:7.16.7" - dependencies: - "@babel/types": ^7.16.7 - checksum: e10aaf135465c55114627951b79115f24bc7af72ecbb58d541d66daf1edaee5dde7cae3ec8c3639afaf74526c03ae3ce723444e3b5b3dc77140c456cd84bcaa1 +"@babel/helper-string-parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-string-parser@npm:7.22.5" + checksum: 836851ca5ec813077bbb303acc992d75a360267aa3b5de7134d220411c852a6f17de7c0d0b8c8dcc0f567f67874c00f4528672b2a4f1bc978a3ada64c8c78467 languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.12.11, @babel/helper-validator-identifier@npm:^7.16.7": +"@babel/helper-validator-identifier@npm:^7.16.7": version: 7.16.7 resolution: "@babel/helper-validator-identifier@npm:7.16.7" checksum: dbb3db9d184343152520a209b5684f5e0ed416109cde82b428ca9c759c29b10c7450657785a8b5c5256aa74acc6da491c1f0cf6b784939f7931ef82982051b69 languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.16.7": - version: 7.16.7 - resolution: "@babel/helper-validator-option@npm:7.16.7" - checksum: c5ccc451911883cc9f12125d47be69434f28094475c1b9d2ada7c3452e6ac98a1ee8ddd364ca9e3f9855fcdee96cdeafa32543ebd9d17fee7a1062c202e80570 +"@babel/helper-validator-identifier@npm:^7.22.19, @babel/helper-validator-identifier@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-validator-identifier@npm:7.22.20" + checksum: 136412784d9428266bcdd4d91c32bcf9ff0e8d25534a9d94b044f77fe76bc50f941a90319b05aafd1ec04f7d127cd57a179a3716009ff7f3412ef835ada95bdc languageName: node linkType: hard -"@babel/helpers@npm:^7.13.10": - version: 7.18.2 - resolution: "@babel/helpers@npm:7.18.2" - dependencies: - "@babel/template": ^7.16.7 - "@babel/traverse": ^7.18.2 - "@babel/types": ^7.18.2 - checksum: 94620242f23f6d5f9b83a02b1aa1632ffb05b0815e1bb53d3b46d64aa8e771066bba1db8bd267d9091fb00134cfaeda6a8d69d1d4cc2c89658631adfa077ae70 +"@babel/helper-validator-identifier@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-validator-identifier@npm:7.22.5" + checksum: 7f0f30113474a28298c12161763b49de5018732290ca4de13cdaefd4fd0d635a6fe3f6686c37a02905fb1e64f21a5ee2b55140cf7b070e729f1bd66866506aea languageName: node linkType: hard @@ -2600,21 +2535,32 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:7.14.6": - version: 7.14.6 - resolution: "@babel/parser@npm:7.14.6" +"@babel/highlight@npm:^7.22.13": + version: 7.22.20 + resolution: "@babel/highlight@npm:7.22.20" + dependencies: + "@babel/helper-validator-identifier": ^7.22.20 + chalk: ^2.4.2 + js-tokens: ^4.0.0 + checksum: 84bd034dca309a5e680083cd827a766780ca63cef37308404f17653d32366ea76262bd2364b2d38776232f2d01b649f26721417d507e8b4b6da3e4e739f6d134 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.17.3, @babel/parser@npm:^7.22.7": + version: 7.22.7 + resolution: "@babel/parser@npm:7.22.7" bin: parser: ./bin/babel-parser.js - checksum: 104482e07971a78a3d68a0c329b1303981a272f55a510d39c93dac3c293f207ec863329046abc5d8bb86db58c49670cc607654793470a87ccd386544c2ccf298 + checksum: 02209ddbd445831ee8bf966fdf7c29d189ed4b14343a68eb2479d940e7e3846340d7cc6bd654a5f3d87d19dc84f49f50a58cf9363bee249dc5409ff3ba3dab54 languageName: node linkType: hard -"@babel/parser@npm:^7.13.0, @babel/parser@npm:^7.13.10, @babel/parser@npm:^7.16.7, @babel/parser@npm:^7.18.5": - version: 7.18.5 - resolution: "@babel/parser@npm:7.18.5" +"@babel/parser@npm:^7.22.15": + version: 7.22.16 + resolution: "@babel/parser@npm:7.22.16" bin: parser: ./bin/babel-parser.js - checksum: 4976349d8681af215fd5771bd5b74568cc95a2e8bf2afcf354bf46f73f3d6f08d54705f354b1d0012f914dd02a524b7d37c5c1204ccaafccb9db3c37dba96a9b + checksum: 944c756b5bdeb07b9fec16ecef6b3c61aff9d4c4b924abadcf01afa1840a740b8e2357ae00482b5b37daad6d2bfd848c947f27ad65138d687b6fdc924bc59edd languageName: node linkType: hard @@ -2645,64 +2591,46 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.12.13, @babel/template@npm:^7.16.7": - version: 7.16.7 - resolution: "@babel/template@npm:7.16.7" +"@babel/template@npm:^7.22.5": + version: 7.22.15 + resolution: "@babel/template@npm:7.22.15" dependencies: - "@babel/code-frame": ^7.16.7 - "@babel/parser": ^7.16.7 - "@babel/types": ^7.16.7 - checksum: 10cd112e89276e00f8b11b55a51c8b2f1262c318283a980f4d6cdb0286dc05734b9aaeeb9f3ad3311900b09bc913e02343fcaa9d4a4f413964aaab04eb84ac4a + "@babel/code-frame": ^7.22.13 + "@babel/parser": ^7.22.15 + "@babel/types": ^7.22.15 + checksum: 1f3e7dcd6c44f5904c184b3f7fe280394b191f2fed819919ffa1e529c259d5b197da8981b6ca491c235aee8dbad4a50b7e31304aa531271cb823a4a24a0dd8fd languageName: node linkType: hard -"@babel/traverse@npm:7.13.0": - version: 7.13.0 - resolution: "@babel/traverse@npm:7.13.0" - dependencies: - "@babel/code-frame": ^7.12.13 - "@babel/generator": ^7.13.0 - "@babel/helper-function-name": ^7.12.13 - "@babel/helper-split-export-declaration": ^7.12.13 - "@babel/parser": ^7.13.0 - "@babel/types": ^7.13.0 - debug: ^4.1.0 - globals: ^11.1.0 - lodash: ^4.17.19 - checksum: 7d584b5541396b02f6973ba8ec8a067f2a6c2fd2e894c663dfae36e86e65a004a6865fbffbfc89ca040c894f9c12134bb971d31f09e7ec32c28e9b18bf737f2a - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.18.0, @babel/traverse@npm:^7.18.2": - version: 7.18.5 - resolution: "@babel/traverse@npm:7.18.5" +"@babel/traverse@npm:7.17.3": + version: 7.17.3 + resolution: "@babel/traverse@npm:7.17.3" dependencies: "@babel/code-frame": ^7.16.7 - "@babel/generator": ^7.18.2 - "@babel/helper-environment-visitor": ^7.18.2 - "@babel/helper-function-name": ^7.17.9 + "@babel/generator": ^7.17.3 + "@babel/helper-environment-visitor": ^7.16.7 + "@babel/helper-function-name": ^7.16.7 "@babel/helper-hoist-variables": ^7.16.7 "@babel/helper-split-export-declaration": ^7.16.7 - "@babel/parser": ^7.18.5 - "@babel/types": ^7.18.4 + "@babel/parser": ^7.17.3 + "@babel/types": ^7.17.0 debug: ^4.1.0 globals: ^11.1.0 - checksum: cc0470c880e15a748ca3424665c65836dba450fd0331fb28f9d30aa42acd06387b6321996517ab1761213f781fe8d657e2c3ad67c34afcb766d50653b393810f + checksum: 780d7ecf711758174989794891af08d378f81febdb8932056c0d9979524bf0298e28f8e7708a872d7781151506c28f56c85c63ea3f1f654662c2fcb8a3eb9fdc languageName: node linkType: hard -"@babel/types@npm:7.13.0": - version: 7.13.0 - resolution: "@babel/types@npm:7.13.0" +"@babel/types@npm:7.17.0": + version: 7.17.0 + resolution: "@babel/types@npm:7.17.0" dependencies: - "@babel/helper-validator-identifier": ^7.12.11 - lodash: ^4.17.19 + "@babel/helper-validator-identifier": ^7.16.7 to-fast-properties: ^2.0.0 - checksum: 3dbb08add345325a49e1deebefa8d3774a8ab055c4be675c339a389358f4b3443652ded4bfdb230b342c6af12593a6fd3fb95156564e7ec84081018815896821 + checksum: 12e5a287986fe557188e87b2c5202223f1dc83d9239a196ab936fdb9f8c1eb0be717ff19f934b5fad4e29a75586d5798f74bed209bccea1c20376b9952056f0e languageName: node linkType: hard -"@babel/types@npm:^7.13.0, @babel/types@npm:^7.16.7, @babel/types@npm:^7.17.0, @babel/types@npm:^7.18.0, @babel/types@npm:^7.18.2, @babel/types@npm:^7.18.4, @babel/types@npm:^7.8.3": +"@babel/types@npm:^7.16.7, @babel/types@npm:^7.17.0, @babel/types@npm:^7.8.3": version: 7.18.4 resolution: "@babel/types@npm:7.18.4" dependencies: @@ -2712,6 +2640,28 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.22.15": + version: 7.22.19 + resolution: "@babel/types@npm:7.22.19" + dependencies: + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.19 + to-fast-properties: ^2.0.0 + checksum: 2d69740e69b55ba36ece0c17d5afb7b7213b34297157df39ef9ba24965aff677c56f014413052ecc5b2fbbf26910c63e5bb24a969df84d7a17153750cf75915e + languageName: node + linkType: hard + +"@babel/types@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/types@npm:7.22.5" + dependencies: + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.5 + to-fast-properties: ^2.0.0 + checksum: c13a9c1dc7d2d1a241a2f8363540cb9af1d66e978e8984b400a20c4f38ba38ca29f06e26a0f2d49a70bad9e57615dac09c35accfddf1bb90d23cd3e0a0bab892 + languageName: node + linkType: hard + "@chainsafe/as-sha256@npm:^0.3.1": version: 0.3.1 resolution: "@chainsafe/as-sha256@npm:0.3.1" @@ -2805,23 +2755,6 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^1.3.0": - version: 1.3.0 - resolution: "@eslint/eslintrc@npm:1.3.0" - dependencies: - ajv: ^6.12.4 - debug: ^4.3.2 - espree: ^9.3.2 - globals: ^13.15.0 - ignore: ^5.2.0 - import-fresh: ^3.2.1 - js-yaml: ^4.1.0 - minimatch: ^3.1.2 - strip-json-comments: ^3.1.1 - checksum: a1e734ad31a8b5328dce9f479f185fd4fc83dd7f06c538e1fa457fd8226b89602a55cc6458cd52b29573b01cdfaf42331be8cfc1fec732570086b591f4ed6515 - languageName: node - linkType: hard - "@eslint/eslintrc@npm:^2.0.3": version: 2.0.3 resolution: "@eslint/eslintrc@npm:2.0.3" @@ -3905,17 +3838,6 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.9.2": - version: 0.9.5 - resolution: "@humanwhocodes/config-array@npm:0.9.5" - dependencies: - "@humanwhocodes/object-schema": ^1.2.1 - debug: ^4.1.1 - minimatch: ^3.0.4 - checksum: 8ba6281bc0590f6c6eadeefc14244b5a3e3f5903445aadd1a32099ed80e753037674026ce1b3c945ab93561bea5eb29e3c5bff67060e230c295595ba517a3492 - languageName: node - linkType: hard - "@humanwhocodes/module-importer@npm:^1.0.1": version: 1.0.1 resolution: "@humanwhocodes/module-importer@npm:1.0.1" @@ -3930,12 +3852,37 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/core@1.5.1, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/cli@workspace:typescript/cli": + version: 0.0.0-use.local + resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" + dependencies: + "@hyperlane-xyz/hyperlane-token": 1.5.4-beta0 + "@hyperlane-xyz/sdk": 1.5.4-beta0 + "@inquirer/prompts": ^3.0.0 + "@types/node": ^18.14.5 + "@types/yargs": ^17.0.24 + "@typescript-eslint/eslint-plugin": ^5.62.0 + "@typescript-eslint/parser": ^5.62.0 + chalk: ^5.3.0 + eslint: ^8.43.0 + eslint-config-prettier: ^8.8.0 + ethers: ^5.7.2 + prettier: ^2.8.8 + typescript: ^5.1.6 + yaml: ^2.3.1 + yargs: ^17.7.2 + zod: ^3.21.2 + bin: + hyperlane: ./dist/cli.js + languageName: unknown + linkType: soft + +"@hyperlane-xyz/core@1.5.4-beta0, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@eth-optimism/contracts": ^0.6.0 - "@hyperlane-xyz/utils": 1.5.1 + "@hyperlane-xyz/utils": 1.5.4-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts": ^4.8.0 @@ -3947,7 +3894,7 @@ __metadata: ethers: ^5.7.2 hardhat: ^2.16.1 hardhat-gas-reporter: ^1.0.9 - prettier: ^2.4.1 + prettier: ^2.8.8 prettier-plugin-solidity: ^1.0.0-beta.5 solhint: ^3.3.2 solhint-plugin-prettier: ^0.0.5 @@ -3958,68 +3905,68 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@1.5.1, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@1.5.4-beta0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/sdk": 1.5.1 + "@hyperlane-xyz/sdk": 1.5.4-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 - "@trivago/prettier-plugin-sort-imports": ^3.2.0 + "@trivago/prettier-plugin-sort-imports": ^4.2.0 "@typechain/ethers-v5": 10.0.0 "@typechain/hardhat": ^6.0.0 "@types/mocha": ^9.1.0 "@typescript-eslint/eslint-plugin": ^5.62.0 "@typescript-eslint/parser": ^5.62.0 chai: ^4.3.0 - eslint: ^8.16.0 - eslint-config-prettier: ^8.5.0 + eslint: ^8.43.0 + eslint-config-prettier: ^8.8.0 ethereum-waffle: ^3.4.4 ethers: ^5.7.2 hardhat: ^2.16.1 hardhat-gas-reporter: ^1.0.9 - prettier: ^2.4.1 + prettier: ^2.8.8 prettier-plugin-solidity: ^1.0.0-beta.5 solhint: ^3.3.2 solhint-plugin-prettier: ^0.0.5 solidity-coverage: ^0.8.3 - ts-node: ^10.8.0 + ts-node: ^10.9.1 typechain: 8.0.0 typescript: ^5.1.6 languageName: unknown linkType: soft -"@hyperlane-xyz/hyperlane-token@1.5.1, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": +"@hyperlane-xyz/hyperlane-token@1.5.4-beta0, @hyperlane-xyz/hyperlane-token@workspace:typescript/token": version: 0.0.0-use.local resolution: "@hyperlane-xyz/hyperlane-token@workspace:typescript/token" dependencies: - "@hyperlane-xyz/core": 1.5.1 - "@hyperlane-xyz/sdk": 1.5.1 - "@hyperlane-xyz/utils": 1.5.1 + "@hyperlane-xyz/core": 1.5.4-beta0 + "@hyperlane-xyz/sdk": 1.5.4-beta0 + "@hyperlane-xyz/utils": 1.5.4-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 "@solana/spl-token": ^0.3.8 - "@trivago/prettier-plugin-sort-imports": ^3.2.0 + "@trivago/prettier-plugin-sort-imports": ^4.2.0 "@typechain/ethers-v5": 10.0.0 "@typechain/hardhat": ^6.0.0 "@types/mocha": ^9.1.0 "@typescript-eslint/eslint-plugin": ^5.62.0 "@typescript-eslint/parser": ^5.62.0 chai: ^4.3.0 - eslint: ^8.16.0 - eslint-config-prettier: ^8.5.0 + eslint: ^8.43.0 + eslint-config-prettier: ^8.8.0 ethereum-waffle: ^3.4.4 ethers: ^5.7.2 hardhat: ^2.16.1 hardhat-gas-reporter: ^1.0.9 - prettier: ^2.4.1 + prettier: ^2.8.8 prettier-plugin-solidity: ^1.0.0-beta.5 solhint: ^3.3.2 solhint-plugin-prettier: ^0.0.5 solidity-coverage: ^0.8.3 - ts-node: ^10.8.0 + ts-node: ^10.9.1 typechain: 8.0.0 typescript: ^5.1.6 languageName: unknown @@ -4037,10 +3984,10 @@ __metadata: "@ethersproject/experimental": ^5.7.0 "@ethersproject/hardware-wallets": ^5.7.0 "@ethersproject/providers": ^5.7.2 - "@hyperlane-xyz/helloworld": 1.5.1 - "@hyperlane-xyz/hyperlane-token": 1.5.1 - "@hyperlane-xyz/sdk": 1.5.1 - "@hyperlane-xyz/utils": 1.5.1 + "@hyperlane-xyz/helloworld": 1.5.4-beta0 + "@hyperlane-xyz/hyperlane-token": 1.5.4-beta0 + "@hyperlane-xyz/sdk": 1.5.4-beta0 + "@hyperlane-xyz/utils": 1.5.4-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-etherscan": ^3.0.3 "@nomiclabs/hardhat-waffle": ^2.0.3 @@ -4051,7 +3998,7 @@ __metadata: "@types/mocha": ^9.1.0 "@types/node": ^16.9.1 "@types/prompts": ^2.0.14 - "@types/yargs": ^17.0.10 + "@types/yargs": ^17.0.24 asn1.js: 5.4.1 aws-kms-ethers-signer: ^0.1.3 chai: ^4.3.4 @@ -4059,12 +4006,12 @@ __metadata: ethereum-waffle: ^3.4.4 ethers: ^5.7.2 hardhat: ^2.16.1 - prettier: ^2.4.1 + prettier: ^2.8.8 prom-client: ^14.0.1 prompts: ^2.4.2 - ts-node: ^10.8.0 + ts-node: ^10.9.1 typescript: ^5.1.6 - yargs: ^17.4.1 + yargs: ^17.7.2 languageName: unknown linkType: soft @@ -4072,23 +4019,23 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/monorepo@workspace:." dependencies: - "@trivago/prettier-plugin-sort-imports": ^3.2.0 + "@trivago/prettier-plugin-sort-imports": ^4.2.0 "@typescript-eslint/eslint-plugin": ^5.62.0 "@typescript-eslint/parser": ^5.62.0 - eslint: ^8.16.0 - eslint-config-prettier: ^8.5.0 + eslint: ^8.43.0 + eslint-config-prettier: ^8.8.0 husky: ^8.0.0 lint-staged: ^12.4.3 - prettier: ^2.4.1 + prettier: ^2.8.8 languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@1.5.1, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@1.5.4-beta0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: - "@hyperlane-xyz/core": 1.5.1 - "@hyperlane-xyz/utils": 1.5.1 + "@hyperlane-xyz/core": 1.5.4-beta0 + "@hyperlane-xyz/utils": 1.5.4-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@solana/web3.js": ^1.78.0 @@ -4108,16 +4055,16 @@ __metadata: fs: 0.0.1-security hardhat: ^2.16.1 mocha: ^9.2.2 - prettier: ^2.4.1 + prettier: ^2.8.8 sinon: ^13.0.2 - ts-node: ^10.8.0 + ts-node: ^10.9.1 typescript: ^5.1.6 viem: ^1.3.1 zod: ^3.21.2 languageName: unknown linkType: soft -"@hyperlane-xyz/utils@1.5.1, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@1.5.4-beta0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: @@ -4125,19 +4072,166 @@ __metadata: bignumber.js: ^9.1.1 chai: ^4.3.0 ethers: ^5.7.2 - prettier: ^2.4.1 + prettier: ^2.8.8 typescript: ^5.1.6 languageName: unknown linkType: soft -"@jridgewell/gen-mapping@npm:^0.3.0": - version: 0.3.1 - resolution: "@jridgewell/gen-mapping@npm:0.3.1" +"@inquirer/checkbox@npm:^1.3.5": + version: 1.3.5 + resolution: "@inquirer/checkbox@npm:1.3.5" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + ansi-escapes: ^4.3.2 + chalk: ^4.1.2 + figures: ^3.2.0 + checksum: 9359a697d2a98e3110291ae7503d8e817449bef00158624d4827f783d29f2738dc2c33721311de814fe76b642989941b9718c440e828973b12803facd4a9ea09 + languageName: node + linkType: hard + +"@inquirer/confirm@npm:^2.0.6": + version: 2.0.6 + resolution: "@inquirer/confirm@npm:2.0.6" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + chalk: ^4.1.2 + checksum: cbffd0d652ccc25942e12b670c20abaa77c69eaec09ac2d5cc0ae32125abae06ab2d30cd4a49f3041efcce6eab65c77a60c64b9334ed1df2b6acad196779827b + languageName: node + linkType: hard + +"@inquirer/core@npm:^3.0.0": + version: 3.0.0 + resolution: "@inquirer/core@npm:3.0.0" + dependencies: + "@inquirer/type": ^1.1.1 + "@types/mute-stream": ^0.0.1 + "@types/node": ^20.4.2 + "@types/wrap-ansi": ^3.0.0 + ansi-escapes: ^4.3.2 + chalk: ^4.1.2 + cli-spinners: ^2.8.0 + cli-width: ^4.0.0 + figures: ^3.2.0 + mute-stream: ^1.0.0 + run-async: ^3.0.0 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + wrap-ansi: ^6.0.1 + checksum: d8a47c43c7ae1678cb669b66440ada287563fa6a5c47e4a2f686db3fa4d7e14a277432b137acdc4c49ecd4dc57f0c03352bf7973e3c2ae8e49f02498665372a8 + languageName: node + linkType: hard + +"@inquirer/editor@npm:^1.2.4": + version: 1.2.4 + resolution: "@inquirer/editor@npm:1.2.4" dependencies: - "@jridgewell/set-array": ^1.0.0 + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + chalk: ^4.1.2 + external-editor: ^3.0.3 + checksum: 3355ddbf91baab86d5016b35fd09905bfe6f5daee3f8dc844d8f3904161547eb93efd2a8845e172bbf9159148abd68bcf20c1d811c74330064b783c9ab8be67d + languageName: node + linkType: hard + +"@inquirer/expand@npm:^1.1.5": + version: 1.1.5 + resolution: "@inquirer/expand@npm:1.1.5" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + chalk: ^4.1.2 + figures: ^3.2.0 + checksum: 04df9f1713864a965bfaff12358cbd4de97f58363dfb0b625ba4c4b4736bdb1cef38b5a726024ea41b0a55cdbdd1d271fbb6659a8e7fce8927383a8d1bf77562 + languageName: node + linkType: hard + +"@inquirer/input@npm:^1.2.5": + version: 1.2.5 + resolution: "@inquirer/input@npm:1.2.5" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + chalk: ^4.1.2 + checksum: a39b7e4ea9140efd1ff41ff2661fbb9b1bbc4dc08e4e645d028eb50925ebc0c9d3d5e31f969ffef4139e5b170e2e6ff536624ec2bbe22aae9bc7e8712f182087 + languageName: node + linkType: hard + +"@inquirer/password@npm:^1.1.5": + version: 1.1.5 + resolution: "@inquirer/password@npm:1.1.5" + dependencies: + "@inquirer/input": ^1.2.5 + "@inquirer/type": ^1.1.1 + chalk: ^4.1.2 + checksum: 81aa1a101cfffdcaab6e12487ffc672e5a5cad0c4e2c504f0c56b7bf3119077826d6df5b0a35a2a938c1ff888618d68e656f7acc5103abd5d7b2b56e97aa5ff3 + languageName: node + linkType: hard + +"@inquirer/prompts@npm:^3.0.0": + version: 3.0.0 + resolution: "@inquirer/prompts@npm:3.0.0" + dependencies: + "@inquirer/checkbox": ^1.3.5 + "@inquirer/confirm": ^2.0.6 + "@inquirer/core": ^3.0.0 + "@inquirer/editor": ^1.2.4 + "@inquirer/expand": ^1.1.5 + "@inquirer/input": ^1.2.5 + "@inquirer/password": ^1.1.5 + "@inquirer/rawlist": ^1.2.5 + "@inquirer/select": ^1.2.5 + checksum: f4bfa4da8f8be09ed25c2c13cf7006dfcbbad9ea895562a3de0a3d29a3cac4d9fadb9180c9334de9e41bb2572f73c78e802df383fd5a0d02a7d90d6330e990f0 + languageName: node + linkType: hard + +"@inquirer/rawlist@npm:^1.2.5": + version: 1.2.5 + resolution: "@inquirer/rawlist@npm:1.2.5" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + chalk: ^4.1.2 + checksum: e94388228305b0d1b49b4f4c6b744c89614c7f413cae2098ff5f7d685ae485b9ad3af9f93796061fad2c8bfa816e7daa9e349ee2499398011744d7c03f6718c3 + languageName: node + linkType: hard + +"@inquirer/select@npm:^1.2.5": + version: 1.2.5 + resolution: "@inquirer/select@npm:1.2.5" + dependencies: + "@inquirer/core": ^3.0.0 + "@inquirer/type": ^1.1.1 + ansi-escapes: ^4.3.2 + chalk: ^4.1.2 + figures: ^3.2.0 + checksum: 752b97cc3189e2d119c0a431f2c61ac25029f5edcdf5495da42edabec698c53b08275df6649dbf8678368ffef61e5fe1f9ad8e0e9b3c0f925a48c1eeb549ddd9 + languageName: node + linkType: hard + +"@inquirer/type@npm:^1.1.1": + version: 1.1.1 + resolution: "@inquirer/type@npm:1.1.1" + checksum: 1fcce0bd6c92611ed67ee9252deebba0fa9a54aeb4e37fc349ec736c8e1ffaa58f3a02dbe84489ff9a90bebc6b1080a19169ef30c16a18128cf8f42d06a49f51 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.2": + version: 0.3.3 + resolution: "@jridgewell/gen-mapping@npm:0.3.3" + dependencies: + "@jridgewell/set-array": ^1.0.1 "@jridgewell/sourcemap-codec": ^1.4.10 "@jridgewell/trace-mapping": ^0.3.9 - checksum: e9e7bb3335dea9e60872089761d4e8e089597360cdb1af90370e9d53b7d67232c1e0a3ab65fbfef4fc785745193fbc56bff9f3a6cab6c6ce3f15e12b4191f86b + checksum: 4a74944bd31f22354fc01c3da32e83c19e519e3bbadafa114f6da4522ea77dd0c2842607e923a591d60a76699d819a2fbb6f3552e277efdb9b58b081390b60ab + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:3.1.0": + version: 3.1.0 + resolution: "@jridgewell/resolve-uri@npm:3.1.0" + checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267 languageName: node linkType: hard @@ -4148,10 +4242,17 @@ __metadata: languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.0.0": - version: 1.1.1 - resolution: "@jridgewell/set-array@npm:1.1.1" - checksum: cc5d91e0381c347e3edee4ca90b3c292df9e6e55f29acbe0dd97de8651b4730e9ab761406fd572effa79972a0edc55647b627f8c72315e276d959508853d9bf2 +"@jridgewell/set-array@npm:^1.0.1": + version: 1.1.2 + resolution: "@jridgewell/set-array@npm:1.1.2" + checksum: 69a84d5980385f396ff60a175f7177af0b8da4ddb81824cb7016a9ef914eee9806c72b6b65942003c63f7983d4f39a5c6c27185bbca88eb4690b62075602e28e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:1.4.14": + version: 1.4.14 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" + checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 languageName: node linkType: hard @@ -4172,6 +4273,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:^0.3.17": + version: 0.3.18 + resolution: "@jridgewell/trace-mapping@npm:0.3.18" + dependencies: + "@jridgewell/resolve-uri": 3.1.0 + "@jridgewell/sourcemap-codec": 1.4.14 + checksum: 0572669f855260808c16fe8f78f5f1b4356463b11d3f2c7c0b5580c8ba1cbf4ae53efe9f627595830856e57dbac2325ac17eb0c3dd0ec42102e6f227cc289c02 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.13 resolution: "@jridgewell/trace-mapping@npm:0.3.13" @@ -5292,20 +5403,23 @@ __metadata: languageName: node linkType: hard -"@trivago/prettier-plugin-sort-imports@npm:^3.2.0": - version: 3.2.0 - resolution: "@trivago/prettier-plugin-sort-imports@npm:3.2.0" +"@trivago/prettier-plugin-sort-imports@npm:^4.2.0": + version: 4.2.0 + resolution: "@trivago/prettier-plugin-sort-imports@npm:4.2.0" dependencies: - "@babel/core": 7.13.10 - "@babel/generator": 7.13.9 - "@babel/parser": 7.14.6 - "@babel/traverse": 7.13.0 - "@babel/types": 7.13.0 + "@babel/generator": 7.17.7 + "@babel/parser": ^7.20.5 + "@babel/traverse": 7.17.3 + "@babel/types": 7.17.0 javascript-natural-sort: 0.7.1 - lodash: 4.17.21 + lodash: ^4.17.21 peerDependencies: - prettier: 2.x - checksum: 22461433fa0dc82621713cdfb88f8f527c6c41729e9859bb7f0106ef23c35b0da591ee7fed63516be7e8df5604dc8055f0c7e200fed1ef97f44000c9fe25a890 + "@vue/compiler-sfc": 3.x + prettier: 2.x - 3.x + peerDependenciesMeta: + "@vue/compiler-sfc": + optional: true + checksum: 2081ba9f1a2d33b9a3eeadeb3e713d404ee3d1a5cff3b20a23d94d6d915f0a8ff549616c1e77cd728f1b33733e0d7ab8e4c2512f344a612d81ece40025351160 languageName: node linkType: hard @@ -5549,6 +5663,15 @@ __metadata: languageName: node linkType: hard +"@types/mute-stream@npm:^0.0.1": + version: 0.0.1 + resolution: "@types/mute-stream@npm:0.0.1" + dependencies: + "@types/node": "*" + checksum: 01bb9f45a77b691538cba7f0c89166a5bfb112993056ae06a8218cd47d417a5f6a5cfc31f0d31293790c647d27564fe6aa39aa9cb2ef08792d42ed40f18de8d5 + languageName: node + linkType: hard + "@types/node-fetch@npm:^2.5.5": version: 2.6.1 resolution: "@types/node-fetch@npm:2.6.1" @@ -5587,6 +5710,20 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^18.14.5": + version: 18.17.1 + resolution: "@types/node@npm:18.17.1" + checksum: 56201bda9a2d05d68602df63b4e67b0545ac8c6d0280bd5fb31701350a978a577a027501fbf49db99bf177f2242ebd1244896bfd35e89042d5bd7dfebff28d4e + languageName: node + linkType: hard + +"@types/node@npm:^20.4.2": + version: 20.4.5 + resolution: "@types/node@npm:20.4.5" + checksum: 36a0304a8dc346a1b2d2edac4c4633eecf70875793d61a5274d0df052d7a7af7a8e34f29884eac4fbd094c4f0201477dcb39c0ecd3307ca141688806538d1138 + languageName: node + linkType: hard + "@types/node@npm:^8.0.0": version: 8.10.66 resolution: "@types/node@npm:8.10.66" @@ -5711,6 +5848,13 @@ __metadata: languageName: node linkType: hard +"@types/wrap-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/wrap-ansi@npm:3.0.0" + checksum: 492f0610093b5802f45ca292777679bb9b381f1f32ae939956dd9e00bf81dba7cc99979687620a2817d9a7d8b59928207698166c47a0861c6a2e5c30d4aaf1e9 + languageName: node + linkType: hard + "@types/ws@npm:^7.4.4": version: 7.4.7 resolution: "@types/ws@npm:7.4.7" @@ -5736,12 +5880,12 @@ __metadata: languageName: node linkType: hard -"@types/yargs@npm:^17.0.10": - version: 17.0.10 - resolution: "@types/yargs@npm:17.0.10" +"@types/yargs@npm:^17.0.24": + version: 17.0.24 + resolution: "@types/yargs@npm:17.0.24" dependencies: "@types/yargs-parser": "*" - checksum: f0673cbfc08e17239dc58952a88350d6c4db04a027a28a06fbad27d87b670e909f9cd9e66f9c64cebdd5071d1096261e33454a55868395f125297e5c50992ca8 + checksum: 5f3ac4dc4f6e211c1627340160fbe2fd247ceba002190da6cf9155af1798450501d628c9165a183f30a224fc68fa5e700490d740ff4c73e2cdef95bc4e8ba7bf languageName: node linkType: hard @@ -6047,7 +6191,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.4.1, acorn@npm:^8.7.1": +"acorn@npm:^8.4.1": version: 8.7.1 resolution: "acorn@npm:8.7.1" bin: @@ -6191,7 +6335,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.3.0": +"ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -7627,21 +7771,6 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.20.2": - version: 4.20.4 - resolution: "browserslist@npm:4.20.4" - dependencies: - caniuse-lite: ^1.0.30001349 - electron-to-chromium: ^1.4.147 - escalade: ^3.1.1 - node-releases: ^2.0.5 - picocolors: ^1.0.0 - bin: - browserslist: cli.js - checksum: 0e56c42da765524e5c31bc9a1f08afaa8d5dba085071137cf21e56dc78d0cf0283764143df4c7d1c0cd18c3187fc9494e1d93fa0255004f0be493251a28635f9 - languageName: node - linkType: hard - "bs58@npm:^4.0.0, bs58@npm:^4.0.1": version: 4.0.1 resolution: "bs58@npm:4.0.1" @@ -7924,7 +8053,7 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30000844, caniuse-lite@npm:^1.0.30001349": +"caniuse-lite@npm:^1.0.30000844": version: 1.0.30001352 resolution: "caniuse-lite@npm:1.0.30001352" checksum: 575ad031349e56224471859decd100d0f90c804325bf1b543789b212d6126f6e18925766b325b1d96f75e48df0036e68f92af26d1fb175803fd6ad935bc807ac @@ -8001,7 +8130,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.1.0": +"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -8011,6 +8140,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.3.0": + version: 5.3.0 + resolution: "chalk@npm:5.3.0" + checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 + languageName: node + linkType: hard + "chardet@npm:^0.7.0": version: 0.7.0 resolution: "chardet@npm:0.7.0" @@ -8181,6 +8317,13 @@ __metadata: languageName: node linkType: hard +"cli-spinners@npm:^2.8.0": + version: 2.9.0 + resolution: "cli-spinners@npm:2.9.0" + checksum: a9c56e1f44457d4a9f4f535364e729cb8726198efa9e98990cfd9eda9e220dfa4ba12f92808d1be5e29029cdfead781db82dc8549b97b31c907d55f96aa9b0e2 + languageName: node + linkType: hard + "cli-table3@npm:^0.5.0": version: 0.5.1 resolution: "cli-table3@npm:0.5.1" @@ -8222,6 +8365,13 @@ __metadata: languageName: node linkType: hard +"cli-width@npm:^4.0.0": + version: 4.0.0 + resolution: "cli-width@npm:4.0.0" + checksum: 1ec12311217cc8b2d018646a58b61424d2348def598fb58ba2c32e28f0bcb59a35cef168110311cefe3340abf00e5171b351de6c3e2c084bd1642e6e2a9e144e + languageName: node + linkType: hard + "cliui@npm:^3.2.0": version: 3.2.0 resolution: "cliui@npm:3.2.0" @@ -8255,6 +8405,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 + languageName: node + linkType: hard + "clone-response@npm:^1.0.2": version: 1.0.2 resolution: "clone-response@npm:1.0.2" @@ -8491,7 +8652,7 @@ __metadata: languageName: node linkType: hard -"convert-source-map@npm:^1.5.1, convert-source-map@npm:^1.7.0": +"convert-source-map@npm:^1.5.1": version: 1.8.0 resolution: "convert-source-map@npm:1.8.0" dependencies: @@ -9159,7 +9320,7 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.3.47, electron-to-chromium@npm:^1.4.147": +"electron-to-chromium@npm:^1.3.47": version: 1.4.152 resolution: "electron-to-chromium@npm:1.4.152" checksum: d1e3405adc8a8ddbcf5a91f33739f2f3f5fa7612a4e2b6cc6a85d9ebccc87f59cf9e99d2de93c7959b34aa7c633c6c162d912bf895a0e4b79d0c2ce35594948f @@ -9441,14 +9602,14 @@ __metadata: languageName: node linkType: hard -"eslint-config-prettier@npm:^8.5.0": - version: 8.5.0 - resolution: "eslint-config-prettier@npm:8.5.0" +"eslint-config-prettier@npm:^8.8.0": + version: 8.8.0 + resolution: "eslint-config-prettier@npm:8.8.0" peerDependencies: eslint: ">=7.0.0" bin: eslint-config-prettier: bin/cli.js - checksum: 0d0f5c32e7a0ad91249467ce71ca92394ccd343178277d318baf32063b79ea90216f4c81d1065d60f96366fdc60f151d4d68ae7811a58bd37228b84c2083f893 + checksum: 1e94c3882c4d5e41e1dcfa2c368dbccbfe3134f6ac7d40101644d3bfbe3eb2f2ffac757f3145910b5eacf20c0e85e02b91293d3126d770cbf3dc390b3564681c languageName: node linkType: hard @@ -9472,16 +9633,6 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^7.1.1": - version: 7.1.1 - resolution: "eslint-scope@npm:7.1.1" - dependencies: - esrecurse: ^4.3.0 - estraverse: ^5.2.0 - checksum: 9f6e974ab2db641ca8ab13508c405b7b859e72afe9f254e8131ff154d2f40c99ad4545ce326fd9fde3212ff29707102562a4834f1c48617b35d98c71a97fbf3e - languageName: node - linkType: hard - "eslint-scope@npm:^7.2.0": version: 7.2.0 resolution: "eslint-scope@npm:7.2.0" @@ -9501,17 +9652,6 @@ __metadata: languageName: node linkType: hard -"eslint-utils@npm:^3.0.0": - version: 3.0.0 - resolution: "eslint-utils@npm:3.0.0" - dependencies: - eslint-visitor-keys: ^2.0.0 - peerDependencies: - eslint: ">=5" - checksum: 0668fe02f5adab2e5a367eee5089f4c39033af20499df88fe4e6aba2015c20720404d8c3d6349b6f716b08fdf91b9da4e5d5481f265049278099c4c836ccb619 - languageName: node - linkType: hard - "eslint-visitor-keys@npm:^1.0.0, eslint-visitor-keys@npm:^1.1.0": version: 1.3.0 resolution: "eslint-visitor-keys@npm:1.3.0" @@ -9519,13 +9659,6 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^2.0.0": - version: 2.1.0 - resolution: "eslint-visitor-keys@npm:2.1.0" - checksum: e3081d7dd2611a35f0388bbdc2f5da60b3a3c5b8b6e928daffff7391146b434d691577aa95064c8b7faad0b8a680266bcda0a42439c18c717b80e6718d7e267d - languageName: node - linkType: hard - "eslint-visitor-keys@npm:^3.3.0": version: 3.3.0 resolution: "eslint-visitor-keys@npm:3.3.0" @@ -9586,51 +9719,6 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.16.0": - version: 8.17.0 - resolution: "eslint@npm:8.17.0" - dependencies: - "@eslint/eslintrc": ^1.3.0 - "@humanwhocodes/config-array": ^0.9.2 - ajv: ^6.10.0 - chalk: ^4.0.0 - cross-spawn: ^7.0.2 - debug: ^4.3.2 - doctrine: ^3.0.0 - escape-string-regexp: ^4.0.0 - eslint-scope: ^7.1.1 - eslint-utils: ^3.0.0 - eslint-visitor-keys: ^3.3.0 - espree: ^9.3.2 - esquery: ^1.4.0 - esutils: ^2.0.2 - fast-deep-equal: ^3.1.3 - file-entry-cache: ^6.0.1 - functional-red-black-tree: ^1.0.1 - glob-parent: ^6.0.1 - globals: ^13.15.0 - ignore: ^5.2.0 - import-fresh: ^3.0.0 - imurmurhash: ^0.1.4 - is-glob: ^4.0.0 - js-yaml: ^4.1.0 - json-stable-stringify-without-jsonify: ^1.0.1 - levn: ^0.4.1 - lodash.merge: ^4.6.2 - minimatch: ^3.1.2 - natural-compare: ^1.4.0 - optionator: ^0.9.1 - regexpp: ^3.2.0 - strip-ansi: ^6.0.1 - strip-json-comments: ^3.1.0 - text-table: ^0.2.0 - v8-compile-cache: ^2.0.3 - bin: - eslint: bin/eslint.js - checksum: b484c96681c6b19f5b437f664623f1cd310d3ee9be88400d8450e086e664cd968a9dc202f0b0678578fd50e7a445b92586efe8c787de5073ff2f83213b00bb7b - languageName: node - linkType: hard - "eslint@npm:^8.43.0": version: 8.43.0 resolution: "eslint@npm:8.43.0" @@ -9691,17 +9779,6 @@ __metadata: languageName: node linkType: hard -"espree@npm:^9.3.2": - version: 9.3.2 - resolution: "espree@npm:9.3.2" - dependencies: - acorn: ^8.7.1 - acorn-jsx: ^5.3.2 - eslint-visitor-keys: ^3.3.0 - checksum: 9a790d6779847051e87f70d720a0f6981899a722419e80c92ab6dee01e1ab83b8ce52d11b4dc96c2c490182efb5a4c138b8b0d569205bfe1cd4629e658e58c30 - languageName: node - linkType: hard - "espree@npm:^9.5.2": version: 9.5.2 resolution: "espree@npm:9.5.2" @@ -9733,7 +9810,7 @@ __metadata: languageName: node linkType: hard -"esquery@npm:^1.0.1, esquery@npm:^1.4.0": +"esquery@npm:^1.0.1": version: 1.4.0 resolution: "esquery@npm:1.4.0" dependencies: @@ -10694,6 +10771,15 @@ __metadata: languageName: node linkType: hard +"figures@npm:^3.2.0": + version: 3.2.0 + resolution: "figures@npm:3.2.0" + dependencies: + escape-string-regexp: ^1.0.5 + checksum: 85a6ad29e9aca80b49b817e7c89ecc4716ff14e3779d9835af554db91bac41c0f289c418923519392a1e582b4d10482ad282021330cd045bb7b80c84152f2a2b + languageName: node + linkType: hard + "file-entry-cache@npm:^5.0.1": version: 5.0.1 resolution: "file-entry-cache@npm:5.0.1" @@ -11242,13 +11328,6 @@ __metadata: languageName: node linkType: hard -"gensync@npm:^1.0.0-beta.2": - version: 1.0.0-beta.2 - resolution: "gensync@npm:1.0.0-beta.2" - checksum: a7437e58c6be12aa6c90f7730eac7fa9833dc78872b4ad2963d2031b00a3367a93f98aec75f9aaac7220848e4026d67a8655e870b24f20a543d103c0d65952ec - languageName: node - linkType: hard - "get-caller-file@npm:^1.0.1": version: 1.0.3 resolution: "get-caller-file@npm:1.0.3" @@ -11374,7 +11453,7 @@ __metadata: languageName: node linkType: hard -"glob-parent@npm:^6.0.1, glob-parent@npm:^6.0.2": +"glob-parent@npm:^6.0.2": version: 6.0.2 resolution: "glob-parent@npm:6.0.2" dependencies: @@ -11502,15 +11581,6 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.15.0": - version: 13.15.0 - resolution: "globals@npm:13.15.0" - dependencies: - type-fest: ^0.20.2 - checksum: 383ade0873b2ab29ce6d143466c203ed960491575bc97406395e5c8434026fb02472ab2dfff5bc16689b8460269b18fda1047975295cd0183904385c51258bae - languageName: node - linkType: hard - "globals@npm:^13.19.0": version: 13.20.0 resolution: "globals@npm:13.20.0" @@ -13142,15 +13212,6 @@ __metadata: languageName: node linkType: hard -"json5@npm:^2.1.2": - version: 2.2.1 - resolution: "json5@npm:2.2.1" - bin: - json5: lib/cli.js - checksum: 74b8a23b102a6f2bf2d224797ae553a75488b5adbaee9c9b6e5ab8b510a2fc6e38f876d4c77dea672d4014a44b2399e15f2051ac2b37b87f74c0c7602003543b - languageName: node - linkType: hard - "jsonfile@npm:^2.1.0": version: 2.4.0 resolution: "jsonfile@npm:2.4.0" @@ -14600,6 +14661,13 @@ __metadata: languageName: node linkType: hard +"mute-stream@npm:^1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 + languageName: node + linkType: hard + "nan@npm:^2.14.0": version: 2.16.0 resolution: "nan@npm:2.16.0" @@ -14879,13 +14947,6 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.5": - version: 2.0.5 - resolution: "node-releases@npm:2.0.5" - checksum: e85d949addd19f8827f32569d2be5751e7812ccf6cc47879d49f79b5234ff4982225e39a3929315f96370823b070640fb04d79fc0ddec8b515a969a03493a42f - languageName: node - linkType: hard - "nofilter@npm:^1.0.4": version: 1.0.4 resolution: "nofilter@npm:1.0.4" @@ -15591,13 +15652,6 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0": - version: 1.0.0 - resolution: "picocolors@npm:1.0.0" - checksum: a2e8092dd86c8396bdba9f2b5481032848525b3dc295ce9b57896f931e63fc16f79805144321f72976383fc249584672a75cc18d6777c6b757603f372f745981 - languageName: node - linkType: hard - "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -15775,7 +15829,7 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^2.1.2, prettier@npm:^2.3.1, prettier@npm:^2.4.1": +"prettier@npm:^2.1.2, prettier@npm:^2.3.1": version: 2.6.2 resolution: "prettier@npm:2.6.2" bin: @@ -15784,6 +15838,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:^2.8.8": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: b49e409431bf129dd89238d64299ba80717b57ff5a6d1c1a8b1a28b590d998a34e083fa13573bc732bb8d2305becb4c9a4407f8486c81fa7d55100eb08263cf8 + languageName: node + linkType: hard + "private@npm:^0.1.6, private@npm:^0.1.8": version: 0.1.8 resolution: "private@npm:0.1.8" @@ -16311,13 +16374,6 @@ __metadata: languageName: node linkType: hard -"regexpp@npm:^3.2.0": - version: 3.2.0 - resolution: "regexpp@npm:3.2.0" - checksum: a78dc5c7158ad9ddcfe01aa9144f46e192ddbfa7b263895a70a5c6c73edd9ce85faf7c0430e59ac38839e1734e275b9c3de5c57ee3ab6edc0e0b1bdebefccef8 - languageName: node - linkType: hard - "regexpu-core@npm:^2.0.0": version: 2.0.0 resolution: "regexpu-core@npm:2.0.0" @@ -16716,6 +16772,13 @@ __metadata: languageName: node linkType: hard +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 280c03d5a88603f48103fc6fd69f07fb0c392a1e0d319c34ec96a2516030e07ba06f79231a563c78698b882649c2fc1fda601bc84705f57d50efcd1fa506cfc0 + languageName: node + linkType: hard + "run-parallel-limit@npm:^1.1.0": version: 1.1.0 resolution: "run-parallel-limit@npm:1.1.0" @@ -18321,9 +18384,9 @@ __metadata: languageName: node linkType: hard -"ts-node@npm:^10.8.0": - version: 10.8.1 - resolution: "ts-node@npm:10.8.1" +"ts-node@npm:^10.9.1": + version: 10.9.1 + resolution: "ts-node@npm:10.9.1" dependencies: "@cspotcode/source-map-support": ^0.8.0 "@tsconfig/node10": ^1.0.7 @@ -18355,7 +18418,7 @@ __metadata: ts-node-script: dist/bin-script.js ts-node-transpile-only: dist/bin-transpile.js ts-script: dist/bin-script-deprecated.js - checksum: 7d1aa7aa3ae1c0459c4922ed0dbfbade442cfe0c25aebaf620cdf1774f112c8d7a9b14934cb6719274917f35b2c503ba87bcaf5e16a0d39ba0f68ce3e7728363 + checksum: 090adff1302ab20bd3486e6b4799e90f97726ed39e02b39e566f8ab674fd5bd5f727f43615debbfc580d33c6d9d1c6b1b3ce7d8e3cca3e20530a145ffa232c35 languageName: node linkType: hard @@ -18948,13 +19011,6 @@ __metadata: languageName: node linkType: hard -"v8-compile-cache@npm:^2.0.3": - version: 2.3.0 - resolution: "v8-compile-cache@npm:2.3.0" - checksum: adb0a271eaa2297f2f4c536acbfee872d0dd26ec2d76f66921aa7fc437319132773483344207bdbeee169225f4739016d8d2dbf0553913a52bb34da6d0334f8e - languageName: node - linkType: hard - "validate-npm-package-license@npm:^3.0.1": version: 3.0.4 resolution: "validate-npm-package-license@npm:3.0.4" @@ -19943,7 +19999,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^6.2.0": +"wrap-ansi@npm:^6.0.1, wrap-ansi@npm:^6.2.0": version: 6.2.0 resolution: "wrap-ansi@npm:6.2.0" dependencies: @@ -20193,6 +20249,13 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.3.1": + version: 2.3.1 + resolution: "yaml@npm:2.3.1" + checksum: 2c7bc9a7cd4c9f40d3b0b0a98e370781b68b8b7c4515720869aced2b00d92f5da1762b4ffa947f9e795d6cd6b19f410bd4d15fdd38aca7bd96df59bd9486fb54 + languageName: node + linkType: hard + "yargs-parser@npm:13.1.2, yargs-parser@npm:^13.1.2": version: 13.1.2 resolution: "yargs-parser@npm:13.1.2" @@ -20227,10 +20290,10 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^21.0.0": - version: 21.0.1 - resolution: "yargs-parser@npm:21.0.1" - checksum: c3ea2ed12cad0377ce3096b3f138df8267edf7b1aa7d710cd502fe16af417bafe4443dd71b28158c22fcd1be5dfd0e86319597e47badf42ff83815485887323a +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c languageName: node linkType: hard @@ -20290,18 +20353,18 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.4.1": - version: 17.5.1 - resolution: "yargs@npm:17.5.1" +"yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" dependencies: - cliui: ^7.0.2 + cliui: ^8.0.1 escalade: ^3.1.1 get-caller-file: ^2.0.5 require-directory: ^2.1.1 string-width: ^4.2.3 y18n: ^5.0.5 - yargs-parser: ^21.0.0 - checksum: 00d58a2c052937fa044834313f07910fd0a115dec5ee35919e857eeee3736b21a4eafa8264535800ba8bac312991ce785ecb8a51f4d2cc8c4676d865af1cfbde + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a languageName: node linkType: hard