From e51546222801bad890e0c19b5b90e19461e82a08 Mon Sep 17 00:00:00 2001 From: Ryan Goulding Date: Fri, 26 Jan 2024 10:32:23 -0800 Subject: [PATCH] chore: follow up from Jan's feedback Signed-off-by: Ryan Goulding --- packages/build-lz-options/README.md | 2 +- packages/build-lz-options/package.json | 1 + .../src/components/config.tsx | 16 +++- .../src/components/outputOptions.tsx | 80 +++++-------------- packages/build-lz-options/src/config.ts | 6 +- packages/build-lz-options/src/index.tsx | 41 +++------- packages/build-lz-options/src/types.ts | 35 +++++--- .../build-lz-options/src/utilities/prompts.ts | 60 ++++++++------ .../build-lz-options/src/utilities/tasks.ts | 68 ---------------- .../src/utilities/terminal.ts | 33 -------- pnpm-lock.yaml | 71 ++++++++++++++-- 11 files changed, 175 insertions(+), 238 deletions(-) delete mode 100644 packages/build-lz-options/src/utilities/tasks.ts delete mode 100644 packages/build-lz-options/src/utilities/terminal.ts diff --git a/packages/build-lz-options/README.md b/packages/build-lz-options/README.md index 15c0ef999..ec98f6f22 100644 --- a/packages/build-lz-options/README.md +++ b/packages/build-lz-options/README.md @@ -18,7 +18,7 @@ ## Create LayerZero OApp Options Static Badge -The easiest way to get started with LayerZero smart contract development. This CLI tool enables you to quickly start building on top of LayerZero omnichain interoperability protocol. To get started, use the following command: +This package provides a convenient way to build and serialize Options for LayerZero OApps. ```bash npx build-lz-options@latest diff --git a/packages/build-lz-options/package.json b/packages/build-lz-options/package.json index 905ecd988..34c19de8d 100644 --- a/packages/build-lz-options/package.json +++ b/packages/build-lz-options/package.json @@ -1,6 +1,7 @@ { "name": "build-lz-options", "version": "0.0.1", + "private": true, "description": "Build LayerZero options with one command", "keywords": [ "LayerZero", diff --git a/packages/build-lz-options/src/components/config.tsx b/packages/build-lz-options/src/components/config.tsx index a8f4a5112..344088879 100644 --- a/packages/build-lz-options/src/components/config.tsx +++ b/packages/build-lz-options/src/components/config.tsx @@ -1,17 +1,21 @@ import React from "react"; import { Box, Text } from "ink"; -import { OptionType1, OptionConfig, OptionType2 } from "@/types"; +import { + OptionType1Summary, + OptionTypeInput, + OptionType2Summary, +} from "@/types"; interface Props { - value: OptionConfig; + value: OptionTypeInput; } interface OptionType1Props { - props: OptionType1; + props: OptionType1Summary; } interface OptionType2Props extends OptionType1Props { - props: OptionType2; + props: OptionType2Summary; } export const ConfigSummary: React.FC = ({ value }) => { @@ -39,7 +43,11 @@ export const Option2Summary: React.FC = ({ props }) => { Gas Limit: {props.gasLimit} + + Native Drop Amount: {props.nativeDropAmount} + + Native Drop Address: {props.nativeDropAddress} diff --git a/packages/build-lz-options/src/components/outputOptions.tsx b/packages/build-lz-options/src/components/outputOptions.tsx index ca841629e..4be5e34d9 100644 --- a/packages/build-lz-options/src/components/outputOptions.tsx +++ b/packages/build-lz-options/src/components/outputOptions.tsx @@ -1,66 +1,24 @@ -import { makeBytes32 } from "@layerzerolabs/devtools-evm"; -import React, { useEffect } from "react"; -import type { OptionType1, OptionType2, OptionType3 } from "@/types"; -import { Box } from "ink"; -import { useTask } from "@/utilities/tasks"; -import { optionsType1, optionsType2 } from "@layerzerolabs/lz-utility-v2"; +import React from "react"; +import type { OptionOutput } from "@/types"; +import { Box, Text } from "ink"; -interface Props { - props: OptionType1; +interface OptionOutputProps { + props: OptionOutput; } -export const outputOptionsType1 = async (gasLimit: OptionType1) => { - console.log(optionsType1(gasLimit.gasLimit)); -}; - -export const OutputOptionsType1: React.FC = ({ props }) => { - const output = useTask(() => outputOptionsType1(props)); - - useEffect(() => { - output.run().catch(() => {}); - }, [output.run]); - - return ; -}; - -interface OptionsType2Props { - props: OptionType2; -} - -const outputOptionsType2 = async (options: OptionType2) => { - console.log( - optionsType2( - options.gasLimit, - options.nativeDropAmount, - makeBytes32(options.nativeDropAddress), - ), +/** + * Render the options output to the user. + * @param {OptionOutputProps} props + * @constructor + */ +export const OutputOptions: React.FC = ({ + props, +}: OptionOutputProps) => { + return ( + + + Result: {props.hex} + + ); }; - -export const OutputOptionsType2: React.FC = ({ props }) => { - const output = useTask(() => outputOptionsType2(props)); - - useEffect(() => { - output.run().catch(() => {}); - }, [output.run]); - - return ; -}; - -interface OptionsType3Props { - props: OptionType3; -} - -const outputOptionsType3 = async (options: OptionType3) => { - console.log(options.output); -}; - -export const OutputOptionsType3: React.FC = ({ props }) => { - const output = useTask(() => outputOptionsType3(props)); - - useEffect(() => { - output.run().catch(() => {}); - }, [output.run]); - - return ; -}; diff --git a/packages/build-lz-options/src/config.ts b/packages/build-lz-options/src/config.ts index 35fd8cc49..a21b2fcd0 100644 --- a/packages/build-lz-options/src/config.ts +++ b/packages/build-lz-options/src/config.ts @@ -6,17 +6,17 @@ import { ExecutorOptionType, WorkerId } from '@layerzerolabs/lz-utility-v2' */ export const OPTION_TYPES: OptionType[] = [ { - // TODO: use OptionType.TYPE_1 once exported from lz-utility-v2 + // TODO: use OptionType.TYPE_1 once exported from lz-v2-utility id: '1', label: '1: gas for remote execution', }, { - // TODO: use OptionType.TYPE_2 once exported from lz-utility-v2 + // TODO: use OptionType.TYPE_2 once exported from lz-v2-utility id: '2', label: '2: gas for remote execution and native drop', }, { - // TODO: use OptionType.TYPE_3 once exported from lz-utility-v2 + // TODO: use OptionType.TYPE_3 once exported from lz-v2-utility id: '3', label: '3: options builder (EndpointV2 only)', }, diff --git a/packages/build-lz-options/src/index.tsx b/packages/build-lz-options/src/index.tsx index 7eb6b4877..c4f994cb7 100644 --- a/packages/build-lz-options/src/index.tsx +++ b/packages/build-lz-options/src/index.tsx @@ -1,3 +1,5 @@ +import { makeBytes32 } from "@layerzerolabs/devtools-evm"; +import { optionsType1, optionsType2 } from "@layerzerolabs/lz-utility-v2"; import React from "react"; import { render } from "ink"; import { Command } from "commander"; @@ -12,11 +14,7 @@ import { Option1Summary, Option2Summary, } from "@/components/config"; -import { - OutputOptionsType1, - OutputOptionsType2, - OutputOptionsType3, -} from "@/components/outputOptions"; +import { OutputOptions } from "@/components/outputOptions"; import { printLogo } from "@layerzerolabs/io-devtools/swag"; new Command("build-lz-options") @@ -24,10 +22,11 @@ new Command("build-lz-options") .action(async () => { printLogo(); - // First we get the config from the user const config = await promptForOptionType(); render().unmount(); + let output: string = ""; + switch (config.type.id) { case "1": { const options = await promptForOptionType1(); @@ -38,13 +37,7 @@ new Command("build-lz-options") }} />, ).unmount(); - render( - , - ); + output = optionsType1(options.gasLimit); break; } case "2": { @@ -58,27 +51,19 @@ new Command("build-lz-options") }} />, ).unmount(); - render( - , + output = optionsType2( + options.gasLimit, + options.nativeDropAmount, + makeBytes32(options.nativeDropAddress), ); break; } case "3": { const options = await promptForOptionType3(); - render( - , - ); + output = options.toHex(); + break; } } + render(); }) .parseAsync(); diff --git a/packages/build-lz-options/src/types.ts b/packages/build-lz-options/src/types.ts index 8beff0b21..46cd6217b 100644 --- a/packages/build-lz-options/src/types.ts +++ b/packages/build-lz-options/src/types.ts @@ -1,21 +1,36 @@ -export interface OptionConfig { - type: OptionType -} - +/** + * Used to render OptionType input from the user. + */ export interface OptionType { id: string label: string } -export interface OptionType1 { +/** + * Input OptionType selection. + */ +export interface OptionTypeInput { + type: OptionType +} + +/** + * The result of building an Option. + */ +export interface OptionOutput { + hex: string +} + +/** + * Summary of OptionType.TYPE_1. + */ +export interface OptionType1Summary { gasLimit: string } -export interface OptionType2 extends OptionType1 { +/** + * Summary of OptionType.TYPE_2. + */ +export interface OptionType2Summary extends OptionType1Summary { nativeDropAmount: string nativeDropAddress: string } - -export interface OptionType3 { - output: string -} diff --git a/packages/build-lz-options/src/utilities/prompts.ts b/packages/build-lz-options/src/utilities/prompts.ts index cf6b3c9ff..2568a48d6 100644 --- a/packages/build-lz-options/src/utilities/prompts.ts +++ b/packages/build-lz-options/src/utilities/prompts.ts @@ -1,12 +1,15 @@ import { EXECUTOR_OPTION_TYPE, OPTION_TYPES, WORKER_TYPE } from '@/config' -import { OptionType1, OptionType2 } from '@/types' +import { OptionType1Summary, OptionType2Summary } from '@/types' import { makeBytes32 } from '@layerzerolabs/devtools-evm' import { ExecutorOptionType, Options, WorkerId } from '@layerzerolabs/lz-utility-v2' import prompts, { PromptObject } from 'prompts' import { handlePromptState, promptToContinue } from '@layerzerolabs/io-devtools' +// Max value of a Uint128 using a BigInt container. const MAX_UINT_128 = BigInt(2) ** BigInt(128) - BigInt(1) +// Max value of a Uint8 using a number container. const MAX_UINT_8 = 0xffff +// Default initial text number. const DEFAULT_INITIAL_TEXT_NUMBER = BigInt('200000') /** @@ -140,23 +143,22 @@ const promptForExecutorOption = async (options: Options): Promise => { const executorOptionType = await promptForExecutorOptionType() switch (executorOptionType.type?.id) { case ExecutorOptionType.LZ_RECEIVE: { - options = await promptExecutorLzReceiveOption(options) - break + return promptExecutorLzReceiveOption(options) } case ExecutorOptionType.NATIVE_DROP: { - options = await promptExecutorNativeDropOption(options) - break + return await promptExecutorNativeDropOption(options) } case ExecutorOptionType.COMPOSE: { - options = await promptExecutorComposeOption(options) - break + return await promptExecutorComposeOption(options) } case ExecutorOptionType.ORDERED: { - options = options.addExecutorOrderedExecutionOption() - break + return options.addExecutorOrderedExecutionOption() + } + default: { + // unreachable in normal operations + throw new Error(`Unsupported executor option type: ${executorOptionType.type?.id}`) } } - return options } const promptVerifierPrecrimeOption = async (options: Options): Promise => { @@ -167,13 +169,33 @@ const promptVerifierPrecrimeOption = async (options: Options): Promise /** * Helper function to prompt for OptionType.TYPE_1. */ -export const promptForOptionType1 = () => prompts([promptForGasLimit]) as never as Promise +export const promptForOptionType1 = () => prompts([promptForGasLimit]) as never as Promise /** * Helper function to prompt for OptionType.TYPE_2. */ -export const promptForOptionType2 = (): Promise => - prompts([promptForGasLimit, promptForNativeDropAmount, promptForNativeDropAddress]) as never as Promise +export const promptForOptionType2 = (): Promise => + prompts([ + promptForGasLimit, + promptForNativeDropAmount, + promptForNativeDropAddress, + ]) as never as Promise + +const determineWorkerType = async (options: Options): Promise => { + const workerType = await promptForWorkerType() + switch (workerType.type?.id) { + case WorkerId.EXECUTOR: { + return promptForExecutorOption(options) + } + case WorkerId.VERIFIER: { + return promptVerifierPrecrimeOption(options) + } + default: { + // unreachable in normal operations + throw new Error(`Unsupported worker type: ${workerType.type?.id}`) + } + } +} /** * Helper function to prompt for OptionType.TYPE_3. @@ -181,17 +203,7 @@ export const promptForOptionType2 = (): Promise => export const promptForOptionType3 = async (): Promise => { let options = Options.newOptions() do { - const workerType = await promptForWorkerType() - switch (workerType.type.id) { - case WorkerId.EXECUTOR: { - options = await promptForExecutorOption(options) - break - } - case WorkerId.VERIFIER: { - options = await promptVerifierPrecrimeOption(options) - break - } - } + options = await determineWorkerType(options) } while (await promptToContinue()) return options } diff --git a/packages/build-lz-options/src/utilities/tasks.ts b/packages/build-lz-options/src/utilities/tasks.ts deleted file mode 100644 index f23ec9a51..000000000 --- a/packages/build-lz-options/src/utilities/tasks.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { useCallback, useRef, useState } from 'react' - -/** - * TaskState holds three different states of a task: - * - * - Pending state - * - Success state - * - Failure state - */ -export type TaskState = - | { - loading: true - success?: never - failure?: never - data?: never - error?: never - } - | { - loading?: false - success: true - failure?: false - data: T - error?: never - } - | { - loading?: false - success?: false - failure: true - data?: never - error: unknown - } - -/** - * Poor person's version of react-query - * - * Handles state management of a promise run just like useMutation() from react-query, - * just with one less package and a bit less sophisticated reference checking mechanism - * (especially the part where it expect the component that uses this not to unmount or change the task - * at runtime) - * - * @param {() => Promise} task - * @returns - */ -export const useTask = (task: () => Promise) => { - const [state, setState] = useState>() - - // We'll keep the task in a reference so that we don't force the users to pass in memoized objects - const taskRef = useRef(task) - taskRef.current = task - - const run = useCallback(() => { - // Set state to loading - setState({ loading: true }) - - return taskRef.current().then( - // Set state to success - (data) => { - return setState({ success: true, data }), Promise.resolve(data) - }, - // Set state to failure - (error) => { - return setState({ failure: true, error }), Promise.reject(error) - } - ) - }, [taskRef]) - - return { run, state } -} diff --git a/packages/build-lz-options/src/utilities/terminal.ts b/packages/build-lz-options/src/utilities/terminal.ts deleted file mode 100644 index 6aa347d36..000000000 --- a/packages/build-lz-options/src/utilities/terminal.ts +++ /dev/null @@ -1,33 +0,0 @@ -const ENTER_ALT_SCREEN_ANSI = '\x1b[?1049h' -const EXIT_ALT_SCREEN_ANSI = '\x1b[?1049l' - -/** - * Helper function that wraps socket writes with a promise - * - * @param socket `WriteStream` - * @returns `(content: string) => Promise` - */ -const createWrite = (socket: NodeJS.WriteStream) => (content: string) => { - return new Promise((resolve, reject) => { - socket.write(content, (error) => { - if (error != null) reject(error) - else resolve() - }) - }) -} - -/** - * Starts an alt screen and returns a callback that exits back to the default screen. - * This makes the app "full screen" - * - * See https://github.com/vadimdemedes/ink/issues/263 for more info - * - * @returns `Promise<() => void>` - */ -export const altScreen = async () => { - const write = createWrite(process.stdout) - - await write(ENTER_ALT_SCREEN_ANSI) - - return () => write(EXIT_ALT_SCREEN_ANSI) -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42d9c9ae0..a2c95922a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -275,7 +275,7 @@ importers: version: link:../io-devtools '@layerzerolabs/lz-utility-v2': specifier: ~2.0.7 - version: 2.0.7 + version: 2.0.9 '@types/jest': specifier: ^29.5.11 version: 29.5.11 @@ -284,7 +284,7 @@ importers: version: 2.4.9 '@types/react': specifier: ^17.0.74 - version: 17.0.74 + version: 17.0.75 commander: specifier: ^11.1.0 version: 11.1.0 @@ -296,7 +296,7 @@ importers: version: 4.6.0(eslint@8.56.0) ink: specifier: ^3.2.0 - version: 3.2.0(@types/react@17.0.74)(react@17.0.2) + version: 3.2.0(@types/react@17.0.75)(react@17.0.2) ink-gradient: specifier: ^2.0.0 version: 2.0.0(ink@3.2.0)(react@17.0.2) @@ -311,13 +311,13 @@ importers: version: 17.0.2 ts-jest: specifier: ^29.1.1 - version: 29.1.1(@babel/core@7.23.7)(esbuild@0.19.11)(jest@29.7.0)(typescript@5.3.3) + version: 29.1.2(@babel/core@7.23.7)(esbuild@0.19.11)(jest@29.7.0)(typescript@5.3.3) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@18.18.14)(typescript@5.3.3) + version: 10.9.2(@swc/core@1.3.105)(@types/node@18.18.14)(typescript@5.3.3) tsup: specifier: ~8.0.1 - version: 8.0.1(ts-node@10.9.2)(typescript@5.3.3) + version: 8.0.1(@swc/core@1.3.105)(ts-node@10.9.2)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -3225,6 +3225,19 @@ packages: hardhat-deploy: 0.11.45 dev: true + /@layerzerolabs/lz-utility-v2@2.0.9: + resolution: {integrity: sha512-W4dTbX4BQjiGBXFi+356XLuwc7lUat00qWbjiT1sE60qsDjpWdVyH7w22jQMq0LDLsMLS0Y/eMk2ezqtS2cMvg==} + dependencies: + '@layerzerolabs/lz-evm-sdk-v2': 2.0.14 + '@solana/web3.js': 1.89.1 + ethers: 5.7.2 + tiny-invariant: 1.3.1 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + dev: true + /@layerzerolabs/lz-v2-utilities@2.0.14: resolution: {integrity: sha512-ypgXd+uJO8FAHIrkWfhuEo1gtPen2LSigLB+76yb6NFg0SxgXo7Z95Xj6Phc8shSJpSXz9R/nqQ1YRC9Vb8rkA==} dependencies: @@ -4934,6 +4947,13 @@ packages: update-browserslist-db: 1.0.13(browserslist@4.22.2) dev: true + /bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + dependencies: + fast-json-stable-stringify: 2.1.0 + dev: true + /bs58@4.0.1: resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} dependencies: @@ -8418,6 +8438,10 @@ packages: /lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -10411,6 +10435,41 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true + /ts-jest@29.1.2(@babel/core@7.23.7)(esbuild@0.19.11)(jest@29.7.0)(typescript@5.3.3): + resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} + engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.23.7 + bs-logger: 0.2.6 + esbuild: 0.19.11 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.5.4 + typescript: 5.3.3 + yargs-parser: 21.1.1 + dev: true + /ts-node@10.9.2(@swc/core@1.3.105)(@types/node@18.18.14)(typescript@5.3.3): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true