diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fccb414 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +GOERLI_RPC_URL= diff --git a/README.md b/README.md index 0d8495e..785cb08 100644 --- a/README.md +++ b/README.md @@ -1 +1,12 @@ -# maintenance-utils \ No newline at end of file +# maintenance-utils +NodeCLI for various purposes + +## Commands + +```pause``` - Pause all transfers across all bridges on selected enviroment (devnet, testnet or mainnet) + +Run with: + +``` +node pause -pk "private-key" -m "mnemonic words" -e "environment" +``` \ No newline at end of file diff --git a/src/bridgePausing.ts b/src/bridgePausing.ts index 8e515f9..d6a44fe 100644 --- a/src/bridgePausing.ts +++ b/src/bridgePausing.ts @@ -1,24 +1,56 @@ +import 'dotenv/config'; import axios from 'axios'; -import {Command, Option} from 'commander'; -import {ConfigUrl} from "./constants"; -import {RawConfig} from "@buildwithsygma/sygma-sdk-core"; +import { ethers } from 'ethers'; +import { Command, Option } from 'commander'; +import { SharedConfig } from "./constants"; +import { getWalletsForDifferentProviders, deriveWalletsFromMnemonic, sendPauseTransactions } from "./utils"; +import { RawConfig, Domain } from '@buildwithsygma/sygma-sdk-core'; const program = new Command(); -console.log("pero"); program .name("pause-bridge") .description("Pauses all bridge instances across all networks") - .argument("", "mnemonic or private key of the wallet") + .version("0.0.1") + +program + .command("pause") .addOption( - new Option('--environment, -e', 'Environment on which to pause bridge instances') + new Option('-e, --environment ', 'Environment on which to pause bridge instances') .choices(['devnet', 'testnet', 'mainnet']) ) - .action(async (mnemonic: string, environment: keyof typeof ConfigUrl) => { + .addOption( + new Option('-pk, --private-key ', 'Private key to use for signing transactions') + ) + .addOption( + new Option('-m, --mnemonic ', 'Mnemonic to use for signing transactions').conflicts('private-key') + ) + .action(async (configs: any) => { try { - console.log("pero"); - const response = await axios.get(ConfigUrl[environment]) as unknown as RawConfig; - console.log(response) + const network: keyof typeof SharedConfig = configs.environment; + const { + privateKey, + mnemonic + } = configs; + + const { + data + } = await axios.get(SharedConfig[network]) as unknown as { data: RawConfig }; + + const networks: Array = data.domains.filter((network: Domain) => network.name === "ethereum"); // just evms for now + + let wallets: Array = []; + + if (mnemonic) { + wallets = await deriveWalletsFromMnemonic(mnemonic, networks); + } else if (privateKey) { + wallets = await getWalletsForDifferentProviders(privateKey, networks); + } else { + throw new Error('Either mnemonic or private key must be provided'); + } + + await sendPauseTransactions(networks, wallets); + } catch (err) { if (err instanceof Error) { throw new Error(`Failed to fetch shared config because of: ${err.message}`); @@ -27,3 +59,5 @@ program } } }) + +program.parse(process.argv); diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index d226f17..0000000 --- a/src/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const ConfigUrl = { - devnet: "https://chainbridge-assets-stage.s3.us-east-2.amazonaws.com/shared-config-dev.json", - testnet: "https://chainbridge-assets-stage.s3.us-east-2.amazonaws.com/shared-config-test.json", - mainnet: "https://sygma-assets-mainnet.s3.us-east-2.amazonaws.com/shared-config-mainnet.json" -} diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..3275969 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,2 @@ +export { chainIdToRpc } from "./rpc"; +export { SharedConfig } from "./sharedConfig"; diff --git a/src/constants/rpc.ts b/src/constants/rpc.ts new file mode 100644 index 0000000..557d2dc --- /dev/null +++ b/src/constants/rpc.ts @@ -0,0 +1,5 @@ +// chain id -> provider url +export const chainIdToRpc = { + 5: process.env.GOERLI_RPC_URL, +} + diff --git a/src/constants/sharedConfig.ts b/src/constants/sharedConfig.ts new file mode 100644 index 0000000..fbdc482 --- /dev/null +++ b/src/constants/sharedConfig.ts @@ -0,0 +1,5 @@ +export const SharedConfig = { + devnet: "https://chainbridge-assets-stage.s3.us-east-2.amazonaws.com/shared-config-dev.json", + testnet: "https://chainbridge-assets-stage.s3.us-east-2.amazonaws.com/shared-config-test.json", + mainnet: "https://sygma-assets-mainnet.s3.us-east-2.amazonaws.com/shared-config-mainnet.json" +}; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..9a69c85 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,47 @@ +import { ethers } from 'ethers'; +import { chainIdToRpc } from "./constants"; +import { Bridge__factory } from "@buildwithsygma/sygma-contracts"; +import { Domain } from "@buildwithsygma/sygma-sdk-core"; + +export async function getWalletsForDifferentProviders(privateKey: string, networks: Array) { + const wallets = []; + for (let i = 0; i < networks.length; i++) { + const network = networks[i]; + const chainId = network.chainId; + const rpc = chainIdToRpc[chainId as keyof typeof chainIdToRpc]; + if (rpc) { + const provider = new ethers.JsonRpcProvider(rpc); + const wallet = new ethers.Wallet(privateKey, provider); // add error handling for invalid private key + wallets.push(wallet); + } + } + return wallets; +} + +export async function deriveWalletsFromMnemonic(mnemonic: string, networks: Array) { + const wallets = []; + for (let i = 0; i < networks.length; i++) { + const network = networks[i]; + const chainId = network.chainId; + const rpc = chainIdToRpc[chainId as keyof typeof chainIdToRpc]; + if (rpc) { + const provider = new ethers.JsonRpcProvider(rpc); + const wallet = ethers.Wallet.fromPhrase(mnemonic, provider); + wallets.push(wallet); + } + } + return wallets; +} + +export async function sendPauseTransactions(networks: Array, wallets: Array) { + const receipts = []; + for (let i = 0; i < networks.length; i++) { + const network = networks[i]; + const wallet = wallets[i]; + const bridge = Bridge__factory.connect(network.bridge, wallet); + const tx = await bridge.adminPauseTransfers(); + console.log(`Transaction no. ${i + 1} completed, bridge on ${network.name} paused`); + receipts.push(tx); + } + return receipts; +} diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index 791a21f..000327d 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -8,6 +8,6 @@ "declaration": true, "esModuleInterop": true, "module": "commonjs", - "outDir": "./dis-cjs" + "outDir": "./dist-cjs" } } diff --git a/types/bridgePausing.d.ts b/types/bridgePausing.d.ts index c67a35a..c3201ee 100644 --- a/types/bridgePausing.d.ts +++ b/types/bridgePausing.d.ts @@ -1,2 +1,2 @@ -export {}; +import 'dotenv/config'; //# sourceMappingURL=bridgePausing.d.ts.map \ No newline at end of file diff --git a/types/bridgePausing.d.ts.map b/types/bridgePausing.d.ts.map index 1b01a5e..57df4f7 100644 --- a/types/bridgePausing.d.ts.map +++ b/types/bridgePausing.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"bridgePausing.d.ts","sourceRoot":"","sources":["../src/bridgePausing.ts"],"names":[],"mappings":""} \ No newline at end of file +{"version":3,"file":"bridgePausing.d.ts","sourceRoot":"","sources":["../src/bridgePausing.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC"} \ No newline at end of file diff --git a/types/constants.d.ts b/types/constants.d.ts deleted file mode 100644 index 8b288de..0000000 --- a/types/constants.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export declare const ConfigUrl: { - devnet: string; - testnet: string; - mainnet: string; -}; -//# sourceMappingURL=constants.d.ts.map \ No newline at end of file diff --git a/types/constants.d.ts.map b/types/constants.d.ts.map deleted file mode 100644 index 66f6ca6..0000000 --- a/types/constants.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS;;;;CAIrB,CAAA"} \ No newline at end of file diff --git a/types/constants/index.d.ts b/types/constants/index.d.ts new file mode 100644 index 0000000..73fd687 --- /dev/null +++ b/types/constants/index.d.ts @@ -0,0 +1,3 @@ +export { chainIdToRpc } from "./rpc"; +export { SharedConfig } from "./sharedConfig"; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/types/constants/index.d.ts.map b/types/constants/index.d.ts.map new file mode 100644 index 0000000..9cd4389 --- /dev/null +++ b/types/constants/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"} \ No newline at end of file diff --git a/types/constants/rpc.d.ts b/types/constants/rpc.d.ts new file mode 100644 index 0000000..c127d6a --- /dev/null +++ b/types/constants/rpc.d.ts @@ -0,0 +1,4 @@ +export declare const chainIdToRpc: { + 5: string | undefined; +}; +//# sourceMappingURL=rpc.d.ts.map \ No newline at end of file diff --git a/types/constants/rpc.d.ts.map b/types/constants/rpc.d.ts.map new file mode 100644 index 0000000..b472ab6 --- /dev/null +++ b/types/constants/rpc.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../../src/constants/rpc.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,YAAY;;CAExB,CAAA"} \ No newline at end of file diff --git a/types/constants/sharedConfig.d.ts b/types/constants/sharedConfig.d.ts new file mode 100644 index 0000000..6b9e0dd --- /dev/null +++ b/types/constants/sharedConfig.d.ts @@ -0,0 +1,6 @@ +export declare const SharedConfig: { + devnet: string; + testnet: string; + mainnet: string; +}; +//# sourceMappingURL=sharedConfig.d.ts.map \ No newline at end of file diff --git a/types/constants/sharedConfig.d.ts.map b/types/constants/sharedConfig.d.ts.map new file mode 100644 index 0000000..affbd14 --- /dev/null +++ b/types/constants/sharedConfig.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"sharedConfig.d.ts","sourceRoot":"","sources":["../../src/constants/sharedConfig.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY;;;;CAIxB,CAAC"} \ No newline at end of file diff --git a/types/utils.d.ts b/types/utils.d.ts new file mode 100644 index 0000000..b86cc75 --- /dev/null +++ b/types/utils.d.ts @@ -0,0 +1,6 @@ +import { ethers } from 'ethers'; +import { Domain } from "@buildwithsygma/sygma-sdk-core"; +export declare function getWalletsForDifferentProviders(privateKey: string, networks: Array): Promise; +export declare function deriveWalletsFromMnemonic(mnemonic: string, networks: Array): Promise; +export declare function sendPauseTransactions(networks: Array, wallets: Array): Promise; +//# sourceMappingURL=utils.d.ts.map \ No newline at end of file diff --git a/types/utils.d.ts.map b/types/utils.d.ts.map new file mode 100644 index 0000000..38e71f4 --- /dev/null +++ b/types/utils.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,OAAO,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;AAExD,wBAAsB,+BAA+B,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,4BAahG;AAED,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,kCAaxF;AAED,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,yCAWpH"} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index dba5742..00f66b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -165,6 +165,7 @@ __metadata: "@buildwithsygma/sygma-sdk-core": ^2.1.0 axios: ^1.4.0 commander: ^11.0.0 + dotenv: ^16.3.1 ethers: ^6.6.2 typescript: ^5.1.6 languageName: unknown @@ -3005,6 +3006,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.3.1": + version: 16.3.1 + resolution: "dotenv@npm:16.3.1" + checksum: b95ff1bbe624ead85a3cd70dbd827e8e06d5f05f716f2d0cbc476532d54c7c9469c3bc4dd93ea519f6ad711cb522c00ac9a62b6eb340d5affae8008facc3fbd7 + languageName: node + linkType: hard + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0"