-
Notifications
You must be signed in to change notification settings - Fork 1
feat: evm multi chain signer #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 12 commits
dedaa34
4f541e3
f10e542
36e2b00
ba0d6ea
2bf349b
9c43d1c
3602abd
ce81b48
ff2b4c9
af97d7f
e4839a1
4abd806
188dced
0b92c68
ea205dc
ac6d5c1
925621c
a0ea88b
f030f23
52efa81
12679af
b7d439b
24b9d7a
60a2ae2
884db14
2cc7ca4
d7326a6
6561504
e532b1a
afb3b21
fc1188a
366aab5
0b793c7
24b0c55
2076ef6
e61baff
2d08d1e
92115c0
c6cc7e9
0c8dc01
ccc7df6
bdf0107
e13e08a
0546e10
2ee37d4
7770bd7
21758af
38310f8
6e437c7
4820623
3146e93
fa2b0e8
7d6a156
b90d0f7
80533e1
4e3b65c
e5a63c9
c33d90f
7bf044f
96322c5
1eb69e9
676f22a
bae0f81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| { | ||
| // Use IntelliSense to learn about possible attributes. | ||
| // Hover to view descriptions of existing attributes. | ||
| // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
| "version": "0.2.0", | ||
| "configurations": [ | ||
| { | ||
| "type": "node", | ||
| "request": "launch", | ||
| "name": "Launch Program", | ||
| "skipFiles": ["<node_internals>/**"], | ||
| "program": "$${workspaceFolder}/**/*.ts", | ||
| "outFiles": ["${workspaceFolder}/**/*.js"] | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| // import { input, select } from '@inquirer/prompts'; | ||
| import { input, select } from '@inquirer/prompts'; | ||
| import { ethers } from 'ethers'; | ||
| import { stringify as yamlStringify } from 'yaml'; | ||
| import { CommandModule } from 'yargs'; | ||
|
|
||
| import { | ||
| ChainSubmissionStrategy, | ||
| ChainSubmissionStrategySchema, | ||
| TxSubmitterType, | ||
| } from '@hyperlane-xyz/sdk'; | ||
| import { ProtocolType, assert } from '@hyperlane-xyz/utils'; | ||
|
|
||
| import { CommandModuleWithWriteContext } from '../context/types.js'; | ||
| import { | ||
| errorRed, | ||
| log, | ||
| logBlue, | ||
| logCommandHeader, | ||
| logGreen, | ||
| } from '../logger.js'; | ||
| import { runSingleChainSelectionStep } from '../utils/chains.js'; | ||
| import { | ||
| indentYamlOrJson, | ||
| readYamlOrJson, | ||
| writeYamlOrJson, | ||
| } from '../utils/files.js'; | ||
|
|
||
| import { | ||
| DEFAULT_STRATEGY_CONFIG_PATH, | ||
| outputFileCommandOption, | ||
| } from './options.js'; | ||
|
|
||
| /** | ||
| * Parent command | ||
| */ | ||
| export const strategyCommand: CommandModule = { | ||
| command: 'strategy', | ||
| describe: 'Manage Hyperlane deployment strategies', | ||
| builder: (yargs) => yargs.command(init).version(false).demandCommand(), | ||
| handler: () => log('Command required'), | ||
| }; | ||
|
|
||
| export const init: CommandModuleWithWriteContext<{ | ||
| chain: string; | ||
| config: string; | ||
| }> = { | ||
| command: 'init', | ||
| describe: 'Initiates strategy', | ||
| builder: { | ||
| config: outputFileCommandOption( | ||
| DEFAULT_STRATEGY_CONFIG_PATH, | ||
| false, | ||
| 'The path to output a Strategy Config JSON or YAML file.', | ||
| ), | ||
| type: { | ||
|
||
| type: 'string', | ||
| description: | ||
| 'Type of submitter (jsonRpc, impersonatedAccount, gnosisSafe, gnosisSafeTxBuilder)', | ||
| }, | ||
| safeAddress: { | ||
|
||
| type: 'string', | ||
| description: | ||
| 'Safe address (required for gnosisSafe and gnosisSafeTxBuilder types)', | ||
| }, | ||
| userAddress: { | ||
| type: 'string', | ||
| description: 'User address (required for impersonatedAccount type)', | ||
| }, | ||
| }, | ||
| handler: async ({ | ||
| context, | ||
| type: inputType, | ||
| safeAddress: inputSafeAddress, | ||
| userAddress: inputUserAddress, | ||
| }) => { | ||
| logCommandHeader(`Hyperlane Key Init`); | ||
| let defaultStrategy; | ||
| try { | ||
| defaultStrategy = await readYamlOrJson(DEFAULT_STRATEGY_CONFIG_PATH); | ||
| } catch (e) { | ||
| defaultStrategy = writeYamlOrJson( | ||
| DEFAULT_STRATEGY_CONFIG_PATH, | ||
| {}, | ||
| 'yaml', | ||
| ); | ||
| } | ||
|
|
||
| const chain = await runSingleChainSelectionStep(context.chainMetadata); | ||
| const chainProtocol = context.chainMetadata[chain].protocol; | ||
| assert(chainProtocol === ProtocolType.Ethereum, 'Incompatible protocol'); | ||
|
||
|
|
||
| // If type wasn't provided via command line, prompt for it | ||
| const type = | ||
| inputType || | ||
| (await select({ | ||
| message: 'Enter the type of submitter', | ||
| choices: Object.values(TxSubmitterType).map((value) => ({ | ||
| name: value, | ||
| value: value, | ||
| })), | ||
| })); | ||
|
|
||
| const submitter: any = { | ||
| type: type, | ||
| }; | ||
|
|
||
| // Configure submitter based on type | ||
| switch (type) { | ||
| case TxSubmitterType.JSON_RPC: | ||
| submitter.privateKey = await input({ | ||
| message: 'Enter your private key', | ||
| validate: (pk) => isValidPrivateKey(pk), | ||
| }); | ||
| submitter.chain = chain; | ||
| break; | ||
|
|
||
| case TxSubmitterType.IMPERSONATED_ACCOUNT: | ||
| submitter.userAddress = | ||
| inputUserAddress || | ||
| (await input({ | ||
| message: 'Enter the user address to impersonate', | ||
| validate: (address) => { | ||
| try { | ||
| return ethers.utils.isAddress(address) | ||
|
||
| ? true | ||
| : 'Invalid Ethereum address'; | ||
| } catch { | ||
| return 'Invalid Ethereum address'; | ||
| } | ||
| }, | ||
| })); | ||
| assert( | ||
| submitter.userAddress, | ||
| 'User address is required for impersonated account', | ||
| ); | ||
| break; | ||
|
|
||
| case TxSubmitterType.GNOSIS_SAFE: | ||
| case TxSubmitterType.GNOSIS_TX_BUILDER: | ||
| submitter.safeAddress = | ||
| inputSafeAddress || | ||
| (await input({ | ||
| message: 'Enter the Safe address', | ||
| validate: (address) => { | ||
| try { | ||
| return ethers.utils.isAddress(address) | ||
|
||
| ? true | ||
| : 'Invalid Safe address'; | ||
| } catch { | ||
| return 'Invalid Safe address'; | ||
| } | ||
| }, | ||
| })); | ||
| assert( | ||
|
||
| submitter.safeAddress, | ||
| 'Safe address is required for Gnosis Safe', | ||
| ); | ||
| submitter.chain = chain; | ||
|
|
||
| if (type === TxSubmitterType.GNOSIS_TX_BUILDER) { | ||
|
||
| submitter.version = await input({ | ||
| message: 'Enter the Safe version (default: 1.0)', | ||
| default: '1.0', | ||
| }); | ||
| } | ||
| break; | ||
|
|
||
| default: | ||
| throw new Error(`Unsupported submitter type: ${type}`); | ||
| } | ||
|
|
||
| const result: ChainSubmissionStrategy = { | ||
| ...defaultStrategy, // if there are changes in ChainSubmissionStrategy, the defaultStrategy may no longer be compatible | ||
| [chain]: { | ||
| submitter: submitter, | ||
| }, | ||
| }; | ||
|
|
||
| try { | ||
| const strategyConfig = ChainSubmissionStrategySchema.parse(result); | ||
| logBlue( | ||
| `Strategy config is valid, writing to file ${DEFAULT_STRATEGY_CONFIG_PATH}:\n`, | ||
| ); | ||
| log(indentYamlOrJson(yamlStringify(strategyConfig, null, 2), 4)); | ||
|
|
||
| writeYamlOrJson(DEFAULT_STRATEGY_CONFIG_PATH, strategyConfig); | ||
| logGreen('✅ Successfully created new key config.'); | ||
| } catch (e) { | ||
| errorRed( | ||
| `Key config is invalid, please check the submitter configuration.`, | ||
| ); | ||
| throw e; | ||
|
||
| } | ||
| process.exit(0); | ||
| }, | ||
| }; | ||
|
|
||
| function isValidPrivateKey(privateKey: string): boolean { | ||
|
||
| try { | ||
| // Attempt to create a Wallet instance with the private key | ||
| const wallet = new ethers.Wallet(privateKey); | ||
| return wallet.privateKey === privateKey; | ||
| } catch (error) { | ||
| return false; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { | ||
| ChainSubmissionStrategy, | ||
| ChainSubmissionStrategySchema, | ||
| } from '@hyperlane-xyz/sdk'; | ||
| import { assert } from '@hyperlane-xyz/utils'; | ||
|
|
||
| import { readYamlOrJson } from '../utils/files.js'; | ||
|
|
||
| export async function readDefaultStrategyConfig( | ||
| filePath: string, | ||
| ): Promise<ChainSubmissionStrategy> { | ||
| const config = readYamlOrJson(filePath); | ||
| assert(config, `No default strategy config found at ${filePath}`); | ||
|
||
|
|
||
| return ChainSubmissionStrategySchema.parse(config); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we alphabetize this?