From cb03eea69d511afa7679550bc0c82b59949eabdd Mon Sep 17 00:00:00 2001 From: Franco NG Date: Wed, 5 Jun 2024 16:52:10 +0200 Subject: [PATCH 01/23] Claim CLI Skeleton --- packages/claim-cli/.mocharc.json | 7 + packages/claim-cli/README.md | 169 ++++++ packages/claim-cli/bin/dev.cmd | 3 + packages/claim-cli/bin/dev.js | 6 + packages/claim-cli/bin/run.cmd | 3 + packages/claim-cli/bin/run.js | 7 + packages/claim-cli/package.json | 73 +++ packages/claim-cli/src/abi/IERC20.ts | 185 ++++++ packages/claim-cli/src/abi/L2Claim.ts | 539 ++++++++++++++++++ .../src/applications/check-eligibility.ts | 30 + .../applications/publish-multisig-claim.ts | 145 +++++ .../src/applications/submit-claim.ts | 129 +++++ .../claim-cli/src/commands/start/index.ts | 53 ++ packages/claim-cli/src/index.ts | 1 + packages/claim-cli/src/interfaces.ts | 47 ++ .../claim-cli/src/utils/build-account-list.ts | 53 ++ .../src/utils/confirm-send-transaction.ts | 55 ++ packages/claim-cli/src/utils/endpoint.ts | 89 +++ packages/claim-cli/src/utils/getPrivateKey.ts | 71 +++ packages/claim-cli/src/utils/index.ts | 18 + packages/claim-cli/src/utils/network.ts | 29 + packages/claim-cli/src/utils/print-table.ts | 30 + packages/claim-cli/src/utils/sign-message.ts | 14 + packages/claim-cli/tsconfig.json | 8 + yarn.lock | 276 ++++++++- 25 files changed, 2034 insertions(+), 6 deletions(-) create mode 100644 packages/claim-cli/.mocharc.json create mode 100644 packages/claim-cli/README.md create mode 100644 packages/claim-cli/bin/dev.cmd create mode 100755 packages/claim-cli/bin/dev.js create mode 100644 packages/claim-cli/bin/run.cmd create mode 100755 packages/claim-cli/bin/run.js create mode 100644 packages/claim-cli/package.json create mode 100644 packages/claim-cli/src/abi/IERC20.ts create mode 100644 packages/claim-cli/src/abi/L2Claim.ts create mode 100644 packages/claim-cli/src/applications/check-eligibility.ts create mode 100644 packages/claim-cli/src/applications/publish-multisig-claim.ts create mode 100644 packages/claim-cli/src/applications/submit-claim.ts create mode 100644 packages/claim-cli/src/commands/start/index.ts create mode 100644 packages/claim-cli/src/index.ts create mode 100644 packages/claim-cli/src/interfaces.ts create mode 100644 packages/claim-cli/src/utils/build-account-list.ts create mode 100644 packages/claim-cli/src/utils/confirm-send-transaction.ts create mode 100644 packages/claim-cli/src/utils/endpoint.ts create mode 100644 packages/claim-cli/src/utils/getPrivateKey.ts create mode 100644 packages/claim-cli/src/utils/index.ts create mode 100644 packages/claim-cli/src/utils/network.ts create mode 100644 packages/claim-cli/src/utils/print-table.ts create mode 100644 packages/claim-cli/src/utils/sign-message.ts create mode 100644 packages/claim-cli/tsconfig.json diff --git a/packages/claim-cli/.mocharc.json b/packages/claim-cli/.mocharc.json new file mode 100644 index 0000000..5e9671f --- /dev/null +++ b/packages/claim-cli/.mocharc.json @@ -0,0 +1,7 @@ +{ + "require": ["ts-node/register"], + "watch-extensions": ["ts"], + "recursive": true, + "reporter": "spec", + "timeout": 60000 +} diff --git a/packages/claim-cli/README.md b/packages/claim-cli/README.md new file mode 100644 index 0000000..00073c1 --- /dev/null +++ b/packages/claim-cli/README.md @@ -0,0 +1,169 @@ +# Claim CLI + +This library is a command-line service that builds a Merkle tree from a snapshot and computes the Merkle root. + +## Run + +``` +cd packages/tree-builder + +# Lisk Token Migration +./bin/run.js generate-merkle-tree --db-path [--output-path ] [--token-id ] [excluded-addresses-path ] + +# Migration Airdrop +./bin/run.js generate-airdrop-merkle-tree --db-path [--output-path ] [--token-id ] [--cutoff ] [--whale-cap ] [--airdrop-percent ] [--excluded-addresses-path ] +``` + +## Files + +| Name | Description | Generated By | +| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | +| `/data//accounts.json` | Stores addresses, balances, and multisig details(If any) per account after a snapshot is taken, addresses must be sorted in ascending order. Will be used for MerkleTree computation. | Snapshot | +| `/data//merkle-tree-result-detailed.json` | Stores MerkleRoot, and leaves for each account. Will be used for examination by 3rd Party or public, also used by Claim Backend API. | `./bin/run.js generate-merkle-tree --network=` | +| `/data//merkle-tree-result.json` | Stores MerkleRoot, and leaves for each account. A lightweight version of `merkle-tree-result-detailed.json`. Will be used for testing of Claim Contract. | `./bin/run.js generate-merkle-tree --network=` | +| `/data//merkle-root.json` | Stores MerkleRoot only. Will be used for deployment of Claim Contract. | `./bin/run.js generate-merkle-tree --network=` | + +## Merkle Leaf + +Each leaf will be encoded as ABI-format, structure of Merkle Tree may vary and depends on usage + +### Lisk Token Migration + +``` +LSK_ADDRESS_IN_HEX: bytes20 +BALANCE_IN_BEDDOWS: uint64 +NUMBER_OF_SIGNATURES: uint32 +MANDATORY_KEYS: bytes32[] +OPTIONAL_KEYS: bytes32[] +``` + +If the address is not a multisig address, `NUMBER_OF_SIGNATURES` would be `0,` `MANDATORY_KEYS` and `OPTIONAL_KEYS` be `[]` + +### Migration Airdrop + +``` +LSK_ADDRESS_IN_HEX: bytes20 +BALANCE_IN_WEI: uint256 +``` + +Note that Balance is represented in Wei(2\*\*18) and in `uint256` format. + +## Params + +For both `Lisk Token Migration` and `Migration Airdrop`, a `merkle-root.json` will be generated. + +``` +merkle-root.json: +{ + merkleRoot: string; +} +``` + +For other files, refer to the table below: + +### Lisk Token Migration + +``` +accounts.json: +{ + lskAddress: string; + balance: number; + balanceBeddows: number; + numberOfSignatures?: number; + mandatoryKeys?: string[]; + optionalKeys?: string[]; +} + +merkle-tree-result-detailed.json: +{ + merkleRoot: string; + leaves: { + lskAddress: string; + address: string; + balance: number; + balanceBeddows: number; + numberOfSignatures: number; + mandatoryKeys: string[]; + optionalKeys: string[]; + hash: string; + proof: string[]; + }[]; +} + +merkle-tree-result.json: +{ + merkleRoot: string; + leaves: { + b32Address: string; + balanceBeddows: number; + mandatoryKeys: string[]; + numberOfSignatures: number; + optionalKeys: string[]; + proof: string[]; + }[]; +} +# `address` is a reserved in solidity, hence `b32Address` here +``` + +### Migration Airdrop + +``` +accounts.json: +{ + lskAddress: string; + balanceWei: number; +} + +merkle-tree-result-detailed.json: +{ + merkleRoot: string; + leaves: { + lskAddress: string; + address: string; + balanceWei: number; + hash: string; + proof: string[]; + }[]; +} + +merkle-tree-result.json: +{ + merkleRoot: string; + leaves: { + b32Address: string; + balanceWei: number; + proof: string[]; + }[]; +} +``` + +### # Only used at example + +``` +signatures.json: +{ + message: string; + sigs: { + pubKey: string + r: string + s: string + }[]; +}[]; +``` + +## _Demo/Testing Purpose Only_ + +``` +./bin/run.js example [--amountOfLeaves ] [--recipient ] + +# FLAGS +# --amountOfLeaves= [default: 100] Amount of leaves in the tree +# --recipient= [default: 0x34A1D3fff3958843C43aD80F30b94c510645C316] Destination address at signing stage. Default is the contract address created by default mnemonic in Anvil/Ganache when nonce=0 +``` + +By running the command above, it will, at `data/example` folder: + +1. Create `key-pairs.json`, which stores public-private key pairs, along with the corresponding address and path +2. Create `accounts.json` using addresses in `key-pairs.json`, with random LSK balance. +3. Create `merkle-root.json`, `merkle-tree-result.json`, `merkle-tree-result-detailed.json` using the accounts above (Equivalent to `./bin/run.js generate-merkle-tree --network=example`). +4. Sign every leaf using the private keys in `key-pairs.json` and output to `signatures.json`. diff --git a/packages/claim-cli/bin/dev.cmd b/packages/claim-cli/bin/dev.cmd new file mode 100644 index 0000000..077b57a --- /dev/null +++ b/packages/claim-cli/bin/dev.cmd @@ -0,0 +1,3 @@ +@echo off + +node "%~dp0\dev" %* \ No newline at end of file diff --git a/packages/claim-cli/bin/dev.js b/packages/claim-cli/bin/dev.js new file mode 100755 index 0000000..927d522 --- /dev/null +++ b/packages/claim-cli/bin/dev.js @@ -0,0 +1,6 @@ +#!/usr/bin/env node_modules/.bin/ts-node +// eslint-disable-next-line node/shebang, unicorn/prefer-top-level-await +(async () => { + const oclif = await import('@oclif/core'); + await oclif.execute({ development: true, dir: __dirname }); +})(); diff --git a/packages/claim-cli/bin/run.cmd b/packages/claim-cli/bin/run.cmd new file mode 100644 index 0000000..968fc30 --- /dev/null +++ b/packages/claim-cli/bin/run.cmd @@ -0,0 +1,3 @@ +@echo off + +node "%~dp0\run" %* diff --git a/packages/claim-cli/bin/run.js b/packages/claim-cli/bin/run.js new file mode 100755 index 0000000..7251417 --- /dev/null +++ b/packages/claim-cli/bin/run.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node + +// eslint-disable-next-line unicorn/prefer-top-level-await +(async () => { + const oclif = await import('@oclif/core'); + await oclif.execute({ development: false, dir: __dirname }); +})(); diff --git a/packages/claim-cli/package.json b/packages/claim-cli/package.json new file mode 100644 index 0000000..aa1488f --- /dev/null +++ b/packages/claim-cli/package.json @@ -0,0 +1,73 @@ +{ + "name": "@liskhq/claim-cli", + "version": "0.1.0", + "private": true, + "description": "CLI to claim process", + "author": "Lisk Foundation , lightcurve GmbH ", + "license": "Apache-2.0", + "keywords": [ + "blockchain", + "lisk" + ], + "main": "index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/LiskHQ/lisk-token-claim.git" + }, + "bugs": { + "url": "https://github.com/LiskHQ/lisk-token-claim/issues" + }, + "homepage": "https://github.com/LiskHQ/lisk-token-claim#readme", + "bin": { + "claim-cli": "./bin/run" + }, + "engines": { + "node": ">=18.0.0" + }, + "scripts": { + "build": "shx rm -rf dist tsconfig.tsbuildinfo && tsc -b", + "postpack": "shx rm -f oclif.manifest.json", + "prepack": "yarn build && oclif manifest && oclif readme", + "prepare": "yarn build", + "test": "mocha --forbid-only \"test/**/*.test.ts\"", + "version": "oclif readme && git add README.md", + "dev": "./bin/dev.js start --network=local" + }, + "oclif": { + "bin": "claim-cli", + "dirname": "claim-cli", + "commands": "./dist/commands", + "plugins": [ + "@oclif/plugin-help" + ], + "topicSeparator": " " + }, + "dependencies": { + "@inquirer/prompts": "^5.0.4", + "@liskhq/lisk-codec": "^0.5.0", + "@liskhq/lisk-cryptography": "4.1.0", + "@liskhq/lisk-db": "^0.3.10", + "@oclif/core": "^3.26.6", + "@oclif/plugin-help": "^6", + "@openzeppelin/merkle-tree": "^1.0.6", + "ethereumjs-util": "^7.1.5", + "ethers": "^6.8.1", + "inquirer": "^9.2.22", + "tweetnacl": "^1.0.3" + }, + "devDependencies": { + "@oclif/prettier-config": "^0.2.1", + "@oclif/test": "^3.1.8", + "@types/chai": "^4", + "@types/inquirer": "^9.0.7", + "@types/mocha": "^9.0.0", + "@types/node": "^18", + "chai": "^4", + "mocha": "^10", + "oclif": "^4.10.5", + "shx": "^0.3.3", + "sinon": "^17.0.1", + "ts-node": "^10.9.2", + "typescript": "^5" + } +} diff --git a/packages/claim-cli/src/abi/IERC20.ts b/packages/claim-cli/src/abi/IERC20.ts new file mode 100644 index 0000000..03d207a --- /dev/null +++ b/packages/claim-cli/src/abi/IERC20.ts @@ -0,0 +1,185 @@ +export default [ + { + type: 'function', + name: 'allowance', + inputs: [ + { + name: 'owner', + type: 'address', + internalType: 'address', + }, + { + name: 'spender', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'approve', + inputs: [ + { + name: 'spender', + type: 'address', + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'balanceOf', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'totalSupply', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'transfer', + inputs: [ + { + name: 'to', + type: 'address', + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferFrom', + inputs: [ + { + name: 'from', + type: 'address', + internalType: 'address', + }, + { + name: 'to', + type: 'address', + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'event', + name: 'Approval', + inputs: [ + { + name: 'owner', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'spender', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Transfer', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, +]; diff --git a/packages/claim-cli/src/abi/L2Claim.ts b/packages/claim-cli/src/abi/L2Claim.ts new file mode 100644 index 0000000..f2fa9f7 --- /dev/null +++ b/packages/claim-cli/src/abi/L2Claim.ts @@ -0,0 +1,539 @@ +export default [ + { + type: 'constructor', + inputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'LSK_MULTIPLIER', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'UPGRADE_INTERFACE_VERSION', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + internalType: 'string', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'claimMultisigAccount', + inputs: [ + { + name: '_proof', + type: 'bytes32[]', + internalType: 'bytes32[]', + }, + { + name: '_lskAddress', + type: 'bytes20', + internalType: 'bytes20', + }, + { + name: '_amount', + type: 'uint64', + internalType: 'uint64', + }, + { + name: '_keys', + type: 'tuple', + internalType: 'struct MultisigKeys', + components: [ + { + name: 'mandatoryKeys', + type: 'bytes32[]', + internalType: 'bytes32[]', + }, + { + name: 'optionalKeys', + type: 'bytes32[]', + internalType: 'bytes32[]', + }, + ], + }, + { + name: '_recipient', + type: 'address', + internalType: 'address', + }, + { + name: '_sigs', + type: 'tuple[]', + internalType: 'struct ED25519Signature[]', + components: [ + { + name: 'r', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 's', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'claimRegularAccount', + inputs: [ + { + name: '_proof', + type: 'bytes32[]', + internalType: 'bytes32[]', + }, + { + name: '_pubKey', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: '_amount', + type: 'uint64', + internalType: 'uint64', + }, + { + name: '_recipient', + type: 'address', + internalType: 'address', + }, + { + name: '_sig', + type: 'tuple', + internalType: 'struct ED25519Signature', + components: [ + { + name: 'r', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 's', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'claimedTo', + inputs: [ + { + name: '', + type: 'bytes20', + internalType: 'bytes20', + }, + ], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'daoAddress', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'initialize', + inputs: [ + { + name: '_l2LiskToken', + type: 'address', + internalType: 'address', + }, + { + name: '_merkleRoot', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: '_recoverPeriodTimestamp', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'l2LiskToken', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'contract IERC20', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'merkleRoot', + inputs: [], + outputs: [ + { + name: '', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'pendingOwner', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'proxiableUUID', + inputs: [], + outputs: [ + { + name: '', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'recoverLSK', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'recoverPeriodTimestamp', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'renounceOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setDAOAddress', + inputs: [ + { + name: '_daoAddress', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [ + { + name: 'newOwner', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'upgradeToAndCall', + inputs: [ + { + name: 'newImplementation', + type: 'address', + internalType: 'address', + }, + { + name: 'data', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + name: 'version', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + internalType: 'string', + }, + ], + stateMutability: 'view', + }, + { + type: 'event', + name: 'ClaimingEnded', + inputs: [], + anonymous: false, + }, + { + type: 'event', + name: 'DaoAddressSet', + inputs: [ + { + name: 'daoAddress', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Initialized', + inputs: [ + { + name: 'version', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LSKClaimed', + inputs: [ + { + name: 'lskAddress', + type: 'bytes20', + indexed: true, + internalType: 'bytes20', + }, + { + name: 'recipient', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferStarted', + inputs: [ + { + name: 'previousOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'previousOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Upgraded', + inputs: [ + { + name: 'implementation', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'error', + name: 'AddressEmptyCode', + inputs: [ + { + name: 'target', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'ERC1967InvalidImplementation', + inputs: [ + { + name: 'implementation', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'ERC1967NonPayable', + inputs: [], + }, + { + type: 'error', + name: 'FailedInnerCall', + inputs: [], + }, + { + type: 'error', + name: 'InvalidInitialization', + inputs: [], + }, + { + type: 'error', + name: 'NotInitializing', + inputs: [], + }, + { + type: 'error', + name: 'OwnableInvalidOwner', + inputs: [ + { + name: 'owner', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'OwnableUnauthorizedAccount', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'UUPSUnauthorizedCallContext', + inputs: [], + }, + { + type: 'error', + name: 'UUPSUnsupportedProxiableUUID', + inputs: [ + { + name: 'slot', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + }, +]; diff --git a/packages/claim-cli/src/applications/check-eligibility.ts b/packages/claim-cli/src/applications/check-eligibility.ts new file mode 100644 index 0000000..18a2e7f --- /dev/null +++ b/packages/claim-cli/src/applications/check-eligibility.ts @@ -0,0 +1,30 @@ +import { input } from '@inquirer/prompts'; +import { Network } from '../utils/network'; +import buildAccountList from '../utils/build-account-list'; +import { fetchCheckEligibility } from '../utils/endpoint'; + +export default async function checkEligibility(networkParams: Network) { + const lskAddress = await input({ message: 'Your LSK Address' }); + + const result = await fetchCheckEligibility(lskAddress, networkParams); + if (!result.account && result.multisigAccounts.length === 0) { + console.log(`No Eligible Claim for Address: ${lskAddress}`); + process.exit(1); + } + + const accountList = await buildAccountList(result, networkParams); + + console.log('Claimed:'); + for (const [index, account] of accountList.entries()) { + if (account.disabled) { + console.log(`${index + 1}: ${account.name} ${account.disabled}`); + } + } + + console.log('Eligible Claims:'); + for (const [index, account] of accountList.entries()) { + if (!account.disabled) { + console.log(`${index + 1}: ${account.name}}`); + } + } +} diff --git a/packages/claim-cli/src/applications/publish-multisig-claim.ts b/packages/claim-cli/src/applications/publish-multisig-claim.ts new file mode 100644 index 0000000..9c7a9f6 --- /dev/null +++ b/packages/claim-cli/src/applications/publish-multisig-claim.ts @@ -0,0 +1,145 @@ +import { ethers, ZeroHash } from 'ethers'; +import { input, select } from '@inquirer/prompts'; +import { Network } from '../utils/network'; +import { fetchCheckEligibility } from '../utils/endpoint'; +import { getETHWallet } from '../utils/getPrivateKey'; +import L2ClaimAbi from '../abi/L2Claim'; +import confirmSendTransaction from '../utils/confirm-send-transaction'; +import { printPreview } from '../utils/print-table'; + +export default async function publishMultisigClaim( + networkParams: Network, + address: string | null = null, +) { + const provider = new ethers.JsonRpcProvider(networkParams.rpc); + const lskAddress = address || (await input({ message: 'Multisig Address to be published' })); + const claimContract = new ethers.Contract(networkParams.l2Claim, L2ClaimAbi, provider); + + const result = await fetchCheckEligibility(lskAddress, networkParams); + if (!result.account) { + console.log(`Address ${lskAddress} has no eligibility`); + process.exit(1); + } + + if (!result.account.ready) { + console.log(`Address ${lskAddress} has insufficient signatures.`); + process.exit(1); + } + + const claimedTo = await claimContract.claimedTo(result.account.address); + if (claimedTo !== ethers.ZeroAddress) { + console.log(`Address ${lskAddress} has already been claimed.`); + process.exit(1); + } + + const wallet = await getETHWallet(); + const walletWithSigner = wallet.connect(provider); + console.log('Representing LSK L2 Address:', wallet.address); + + const signaturesGroupByDestinationAddress = result.signatures.reduce( + ( + destinationGroup: { + [destination: string]: { + r: string; + s: string; + }[]; + }, + signature, + ) => { + if (result.account.lskAddress !== signature.lskAddress) { + return destinationGroup; + } + if (!destinationGroup[signature.destination]) { + destinationGroup[signature.destination] = new Array( + result.account.mandatoryKeys.length + result.account.optionalKeys.length, + ).fill({ + r: ZeroHash, + s: ZeroHash, + }); + } + + if (result.account.mandatoryKeys.indexOf(signature.signer) >= 0) { + destinationGroup[signature.destination][ + result.account.mandatoryKeys.indexOf(signature.signer) + ] = { + r: signature.r, + s: signature.s, + }; + } else { + destinationGroup[signature.destination][ + result.account.mandatoryKeys.length + + result.account.optionalKeys.indexOf(signature.signer) + ] = { + r: signature.r, + s: signature.s, + }; + } + return destinationGroup; + }, + {}, + ); + + const destinationWithSufficientSignatures = Object.keys( + signaturesGroupByDestinationAddress, + ).filter(destination => { + const signatures = signaturesGroupByDestinationAddress[destination]; + + // If one of mandatory Keys is ZeroHash, return false + for (const signature of signatures.slice(0, result.account.mandatoryKeys.length)) { + if (signature.r === ZeroHash) { + return false; + } + } + + // Count non-Zero optional Keys + const nonZeroOptionalKeys = signatures + .slice(result.account.mandatoryKeys.length + 1) + .reduce( + (nonZeroOptionalKeys, signature) => + nonZeroOptionalKeys + signature.r === ethers.ZeroHash ? 0 : 1, + 0, + ); + return ( + nonZeroOptionalKeys === + result.account.numberOfSignatures - result.account.mandatoryKeys.length + ); + }); + + const destinationIndex = + destinationWithSufficientSignatures.length > 1 + ? await select({ + message: 'Destination', + choices: destinationWithSufficientSignatures.map((destination, index) => ({ + name: destination, + value: index, + })), + }) + : 0; + + const destination = destinationWithSufficientSignatures[destinationIndex]; + const signatures = signaturesGroupByDestinationAddress[destination]; + + const contractWithSigner = claimContract.connect(walletWithSigner) as ethers.Contract; + + console.log([ + result.account.proof, + result.account.address, + result.account.balanceBeddows, + [result.account.mandatoryKeys, result.account.optionalKeys], + destination, + signatures.map(signature => [signature.r, signature.s]), + ]); + printPreview(result.account.lskAddress, destination, result.account.balanceBeddows); + await confirmSendTransaction( + contractWithSigner.claimMultisigAccount, + [ + result.account.proof, + result.account.address, + result.account.balanceBeddows, + [result.account.mandatoryKeys, result.account.optionalKeys], + destination, + signatures.map(signature => [signature.r, signature.s]), + ], + walletWithSigner, + ); +} diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts new file mode 100644 index 0000000..372afdd --- /dev/null +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -0,0 +1,129 @@ +import { input, select, confirm } from '@inquirer/prompts'; +import * as crypto from '@liskhq/lisk-cryptography'; +import { fetchCheckEligibility, fetchSubmitMultisig } from '../utils/endpoint'; +import L2ClaimAbi from '../abi/L2Claim'; +import { Network } from '../utils/network'; +import { ethers } from 'ethers'; +import { getETHWallet, getLSKPrivateKey } from '../utils/getPrivateKey'; +import { signMessage } from '../utils/sign-message'; +import { printPreview } from '../utils/print-table'; +import confirmSendTransaction from '../utils/confirm-send-transaction'; +import publishMultisigClaim from './publish-multisig-claim'; +import buildAccountList from '../utils/build-account-list'; +import { append0x } from '../utils'; + +export default async function submitClaim(networkParams: Network) { + const privateKey = await getLSKPrivateKey(); + + const lskAddressBytes = crypto.address.getAddressFromPrivateKey(privateKey); + const lskAddress = crypto.address.getLisk32AddressFromAddress(lskAddressBytes); + console.log('Representing LSK L1 Address:', lskAddress); + + const result = await fetchCheckEligibility(lskAddress, networkParams); + if (!result.account && result.multisigAccounts.length === 0) { + console.log(`No Eligible Claim for Address: ${lskAddress}`); + process.exit(1); + } + + const claimAccount = await select({ + message: 'Choose Claim Address', + choices: await buildAccountList(result, networkParams), + }); + + if (claimAccount.numberOfSignatures === 0) { + // Regular Claim + const wallet = await getETHWallet(); + const walletWithSigner = wallet.connect(new ethers.JsonRpcProvider(networkParams.rpc)); + console.log('Representing LSK L2 Address:', wallet.address); + + const destinationAddress = await input({ + message: 'Claim Destination Address', + default: wallet.address, + }); + + const claimContract = new ethers.Contract( + networkParams.l2Claim, + L2ClaimAbi, + new ethers.JsonRpcProvider(networkParams.rpc), + ); + + const signature = signMessage(claimAccount.hash, destinationAddress, privateKey); + const contractWithSigner = claimContract.connect(walletWithSigner) as ethers.Contract; + + printPreview(claimAccount.lskAddress, destinationAddress, claimAccount.balanceBeddows); + await confirmSendTransaction( + contractWithSigner.claimRegularAccount, + [ + claimAccount.proof, + crypto.ed.getPublicKeyFromPrivateKey(privateKey), + claimAccount.balanceBeddows, + destinationAddress, + [append0x(signature.substring(0, 64)), append0x(signature.substring(64))], + ], + walletWithSigner, + ); + } else { + // Multisig Claim + const signedDestinationAddresses = result.signatures.reduce( + ( + addresses: { + name: string; + value: string; + }[], + signature, + ) => { + if ( + signature.lskAddress === claimAccount.lskAddress && + !addresses.find(address => address.value === signature.destination) + ) { + addresses.push({ + name: signature.destination, + value: signature.destination, + }); + } + return addresses; + }, + [], + ); + + let destinationAddress = ''; + if (signedDestinationAddresses.length > 0) { + destinationAddress = await select({ + message: 'Claim Destination Address', + choices: signedDestinationAddresses.concat({ + name: 'Other Address', + value: '', + }), + }); + } + if (destinationAddress === '') { + destinationAddress = await input({ message: 'Destination L2 Address' }); + } + const signature = signMessage(claimAccount.hash, destinationAddress, privateKey); + + printPreview(claimAccount.lskAddress, destinationAddress, claimAccount.balanceBeddows); + if (await confirm({ message: 'Confirm Signing this Claim', default: false })) { + const submitResult = await fetchSubmitMultisig( + claimAccount.lskAddress, + destinationAddress, + append0x(crypto.ed.getPublicKeyFromPrivateKey(privateKey).toString('hex')), + append0x(signature.substring(0, 64)), + append0x(signature.substring(64)), + networkParams, + ); + + if (submitResult.success) { + console.log('Success Submitted Claim!'); + } + + if ( + submitResult.ready && + (await confirm({ + message: `Address: ${claimAccount.lskAddress} has reach sufficient signatures, proceed to publish?`, + })) + ) { + await publishMultisigClaim(networkParams, claimAccount.lskAddress); + } + } + } +} diff --git a/packages/claim-cli/src/commands/start/index.ts b/packages/claim-cli/src/commands/start/index.ts new file mode 100644 index 0000000..3e53c0f --- /dev/null +++ b/packages/claim-cli/src/commands/start/index.ts @@ -0,0 +1,53 @@ +import { Flags, Command } from '@oclif/core'; +import { select } from '@inquirer/prompts'; +import checkEligibility from '../../applications/check-eligibility'; +import submitClaim from '../../applications/submit-claim'; +import publishMultisigClaim from '../../applications/publish-multisig-claim'; +import { Local, Mainnet, Testnet } from '../../utils/network'; + +enum Choice { + CHECK_ELIGIBILITY, + SUBMIT_CLAIM, + SUBMIT_MULTISIG_CLAIM, +} + +export default class Start extends Command { + static flags = { + network: Flags.string({ + description: + 'Destination address at signing stage. Default is the contract address created by default mnemonic in Anvil/Ganache when nonce=0', + required: false, + default: 'mainnet', + options: ['mainnet', 'testnet', 'local'], + }), + }; + + static description = 'Generate example data for demo purpose'; + + static examples = [`$ oex example`]; + + async run(): Promise { + const { flags } = await this.parse(Start); + const { network } = flags; + + console.log(`Welcome to Lisk Claim CLI (Running on "${network}" Network)`); + const answer = await select({ + message: 'Please enter your choices', + choices: [ + { name: 'Check my Eligibility', value: Choice.CHECK_ELIGIBILITY }, + { name: 'Submit a Claim', value: Choice.SUBMIT_CLAIM }, + { + name: 'Publish a Multisig claim with completed signatures onchain', + value: Choice.SUBMIT_MULTISIG_CLAIM, + }, + ], + }); + + const networkParams = { + mainnet: Mainnet, + testnet: Testnet, + local: Local, + }[network as 'mainnet' | 'testnet' | 'local']; + await [checkEligibility, submitClaim, publishMultisigClaim][answer](networkParams); + } +} diff --git a/packages/claim-cli/src/index.ts b/packages/claim-cli/src/index.ts new file mode 100644 index 0000000..d620e70 --- /dev/null +++ b/packages/claim-cli/src/index.ts @@ -0,0 +1 @@ +export { run } from '@oclif/core'; diff --git a/packages/claim-cli/src/interfaces.ts b/packages/claim-cli/src/interfaces.ts new file mode 100644 index 0000000..49604a7 --- /dev/null +++ b/packages/claim-cli/src/interfaces.ts @@ -0,0 +1,47 @@ +export interface JSONRPCSuccessResponse { + jsonrpc: string; + id: number; + result: T; + error: undefined; +} + +export interface JSONRPCErrorResponse { + jsonrpc: string; + id: number; + result: undefined; + error: { + code: number; + message: string; + }; +} + +export interface CheckEligibilityResponse { + account: Account; + multisigAccounts: Account[]; + signatures: Signature[]; +} + +export interface SubmitMultisigResponse { + success: boolean; + ready: boolean; +} + +export interface Account { + lskAddress: string; + address: string; + balanceBeddows: string; + numberOfSignatures: number; + mandatoryKeys: string[]; + optionalKeys: string[]; + hash: string; + proof: string[]; + ready?: boolean; +} + +export interface Signature { + lskAddress: string; + destination: string; + signer: string; + r: string; + s: string; +} diff --git a/packages/claim-cli/src/utils/build-account-list.ts b/packages/claim-cli/src/utils/build-account-list.ts new file mode 100644 index 0000000..a79bb31 --- /dev/null +++ b/packages/claim-cli/src/utils/build-account-list.ts @@ -0,0 +1,53 @@ +import { Network } from '../utils/network'; +import { Account, CheckEligibilityResponse } from '../interfaces'; +import { ethers } from 'ethers'; +import L2ClaimAbi from '../abi/L2Claim'; + +export interface AccountListChoice { + name: string; + value: Account; + disabled?: string; +} + +export default async function buildAccountList( + result: CheckEligibilityResponse, + networkParams: Network, +): Promise { + const numOfSigned = result.signatures.reduce( + (acc: { [destination: string]: number }, signature) => { + if (!acc[signature.lskAddress]) { + acc[signature.lskAddress] = 0; + } + acc[signature.lskAddress]++; + return acc; + }, + {}, + ); + + const accountList: AccountListChoice[] = [ + { + name: `${result.account.lskAddress} (${ethers.formatUnits(result.account.balanceBeddows, 8)} LSK)`, + value: result.account, + }, + ].concat( + result.multisigAccounts.map(account => ({ + name: `${account.lskAddress} [${numOfSigned[account.lskAddress] ?? 0}/${account.numberOfSignatures}] (${ethers.formatUnits(account.balanceBeddows, 8)} LSK)`, + value: account, + })), + ); + + const claimContract = new ethers.Contract( + networkParams.l2Claim, + L2ClaimAbi, + new ethers.JsonRpcProvider(networkParams.rpc), + ); + + for (const account of accountList) { + const claimedTo = await claimContract.claimedTo(account.value.address); + if (claimedTo !== ethers.ZeroAddress) { + account.disabled = `Already Claimed To: ${claimedTo}`; + } + } + + return accountList; +} diff --git a/packages/claim-cli/src/utils/confirm-send-transaction.ts b/packages/claim-cli/src/utils/confirm-send-transaction.ts new file mode 100644 index 0000000..910e5ca --- /dev/null +++ b/packages/claim-cli/src/utils/confirm-send-transaction.ts @@ -0,0 +1,55 @@ +import { BaseContractMethod, ethers, HDNodeWallet, Wallet } from 'ethers'; +import { input, confirm } from '@inquirer/prompts'; + +export default async function confirmSendTransaction( + contractMethod: BaseContractMethod, + args: unknown[], + walletWithSigner: Wallet | HDNodeWallet, +) { + const provider = walletWithSigner.provider; + if (!provider) { + process.exit(1); + } + + const feeData = await provider.getFeeData(); + const suggestedMaxFeePerGas = feeData.maxFeePerGas ?? BigInt(1); + const maxFeePerGas = BigInt( + await input({ + message: `Max Fee Per Gas (wei) (Suggested: ${suggestedMaxFeePerGas.toString()})`, + default: suggestedMaxFeePerGas.toString(), + }), + ); + + const suggestedMaxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? BigInt(1); + const maxPriorityFeePerGas = BigInt( + await input({ + message: `Max Priority Fee Per Gas (wei) (Suggested: ${suggestedMaxPriorityFeePerGas.toString()})`, + default: suggestedMaxPriorityFeePerGas.toString(), + }), + ); + + const estimatedGas = await contractMethod.estimateGas(...args); + const estimatedFee = estimatedGas * maxFeePerGas; + const ethBalance = await provider.getBalance(walletWithSigner.address); + console.log( + `> Estimated Network Fee (${estimatedGas} * ${maxFeePerGas} wei) = ${ethers.formatUnits(estimatedFee)} ETH`, + ); + console.log(`> Your Balance: ${ethers.formatUnits(ethBalance)} ETH`); + if (estimatedFee > ethBalance) { + console.log('Insufficient Balance for the Transaction'); + process.exit(1); + } + if (!(await confirm({ message: 'Confirm to Send Transaction', default: false }))) { + console.log('User Cancelled Submission'); + process.exit(1); + } + + const tx = await contractMethod(...args, { + maxFeePerGas, + maxPriorityFeePerGas, + }); + console.log(`Successfully submitted transaction, tx: ${tx.hash}. Waiting for Confirmation ...`); + + const receipt = await tx.wait(); + console.log(`Transaction Confirmed at Block: ${receipt.blockNumber}!`); +} diff --git a/packages/claim-cli/src/utils/endpoint.ts b/packages/claim-cli/src/utils/endpoint.ts new file mode 100644 index 0000000..8f58fbb --- /dev/null +++ b/packages/claim-cli/src/utils/endpoint.ts @@ -0,0 +1,89 @@ +import { + CheckEligibilityResponse, + JSONRPCErrorResponse, + JSONRPCSuccessResponse, + SubmitMultisigResponse, +} from '../interfaces'; +import { Network } from '../utils/network'; + +export const fetchCheckEligibility = async ( + lskAddress: string, + network: Network, +): Promise => { + const jsonRPCRequest = { + jsonrpc: '2.0', + method: 'checkEligibility', + params: { + lskAddress, + }, + id: 1, + }; + + const response = await fetch(network.api, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify(jsonRPCRequest), + }); + + if (response.status !== 200) { + console.log('Network Error, please try again later'); + process.exit(1); + } + + const { result, error } = (await response.json()) as + | JSONRPCSuccessResponse + | JSONRPCErrorResponse; + if (error) { + console.log('Claim Endpoint returned error:', error.message); + process.exit(1); + } + + return result; +}; + +export const fetchSubmitMultisig = async ( + lskAddress: string, + destination: string, + publicKey: string, + r: string, + s: string, + network: Network, +): Promise => { + const jsonRPCRequest = { + jsonrpc: '2.0', + method: 'submitMultisig', + params: { + lskAddress, + destination, + publicKey, + r, + s, + }, + id: 1, + }; + + const response = await fetch(network.api, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify(jsonRPCRequest), + }); + + if (response.status !== 200) { + console.log('Network Error, please try again later'); + process.exit(1); + } + + const { result, error } = (await response.json()) as + | JSONRPCSuccessResponse + | JSONRPCErrorResponse; + if (error) { + console.log('Claim Endpoint returned error:', error.message); + process.exit(1); + } + + return result; +}; diff --git a/packages/claim-cli/src/utils/getPrivateKey.ts b/packages/claim-cli/src/utils/getPrivateKey.ts new file mode 100644 index 0000000..9411632 --- /dev/null +++ b/packages/claim-cli/src/utils/getPrivateKey.ts @@ -0,0 +1,71 @@ +import { input, select } from '@inquirer/prompts'; +import * as crypto from '@liskhq/lisk-cryptography'; +import { ethers, HDNodeWallet, Wallet } from 'ethers'; +import { remove0x } from './index'; + +enum SecretType { + MNEMONIC, + PRIVATE_KEY, + JSON, +} + +const getSecretType = (wallet: string) => + select({ + message: `Secret Type for ${wallet}`, + choices: [ + { name: 'Mnemonic', value: SecretType.MNEMONIC }, + { name: 'Private Key', value: SecretType.PRIVATE_KEY }, + ], + }); +export const getPrivateKeyFromMnemonic = async (): Promise => { + const mnemonic = await input({ message: 'Your Mnemonic' }); + const path = await input({ message: 'Path', default: "m/44'/134'/0'" }); + + return crypto.ed.getPrivateKeyFromPhraseAndPath(mnemonic.trim(), path); +}; + +export const getPrivateKeyFromString = async (): Promise => { + const privKey = await input({ + message: 'Your Private Key', + }); + + const privKeyFormatted = remove0x(privKey); + + if (!privKeyFormatted.match(/^[A-Fa-f0-9]{64}$/)) { + console.log('Invalid Private Key, please check again'); + process.exit(1); + } + return Buffer.from(privKey, 'hex'); +}; + +export const getLSKPrivateKey = async () => { + const type = await getSecretType('Lisk L1 Wallet'); + return [getPrivateKeyFromMnemonic, getPrivateKeyFromString][type](); +}; + +export const getETHWalletFromMnemonic = async (): Promise => { + const mnemonic = await input({ message: 'Your L2 Mnemonic' }); + const password = await input({ message: 'BIP39 Passphrase (Optional)' }); + const path = await input({ message: 'Path', default: "m/44'/60'/0'/0/0" }); + + return ethers.HDNodeWallet.fromPhrase(mnemonic, password, path); +}; + +export const getETHWalletKeyFromString = async (): Promise => { + const privKey = await input({ + message: 'Your Private Key', + }); + + const privKeyFormatted = remove0x(privKey); + + if (!privKeyFormatted.match(/^[A-Fa-f0-9]{64}$/)) { + console.log('Invalid Private Key, please check again'); + process.exit(1); + } + return new ethers.Wallet(privKey); +}; + +export const getETHWallet = async () => { + const type = await getSecretType('Lisk L2 Wallet'); + return [getETHWalletFromMnemonic, getETHWalletKeyFromString][type](); +}; diff --git a/packages/claim-cli/src/utils/index.ts b/packages/claim-cli/src/utils/index.ts new file mode 100644 index 0000000..1da1976 --- /dev/null +++ b/packages/claim-cli/src/utils/index.ts @@ -0,0 +1,18 @@ +export const BYTES_9 = '000000000000000000'; + +export function remove0x(input: string): string { + if (input.substring(0, 2) === '0x') { + return input.substring(2); + } + return input; +} + +export function append0x(input: string | Buffer): string { + if (input instanceof Buffer) { + input = input.toString('hex'); + } + if (input.substring(0, 2) === '0x') { + return input; + } + return '0x' + input; +} diff --git a/packages/claim-cli/src/utils/network.ts b/packages/claim-cli/src/utils/network.ts new file mode 100644 index 0000000..0545969 --- /dev/null +++ b/packages/claim-cli/src/utils/network.ts @@ -0,0 +1,29 @@ +export interface Network { + api: string; + rpc: string; + l2Claim: string; +} + +export const Mainnet = { + api: 'https://token-claim-api.lisk.com/rpc', + rpc: 'https://rpc.api.lisk.com', + l2Claim: '0xD7BE2Fd98BfD64c1dfCf6c013fC593eF09219994', + maxFeePerGas: 1002060n, + maxPriorityFeePerGas: 1000000n, +} as Network; + +export const Testnet = { + api: 'https://token-claim-api.lisk.com/rpc', + rpc: 'https://rpc.sepolia-api.lisk.com', + l2Claim: '0xD7BE2Fd98BfD64c1dfCf6c013fC593eF09219994', + maxFeePerGas: 1002060n, + maxPriorityFeePerGas: 1000000n, +} as Network; + +export const Local = { + api: 'http://127.0.0.1:5555/rpc', + rpc: 'http://127.0.0.1:8546', + l2Claim: '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9', + maxFeePerGas: 1002060n, + maxPriorityFeePerGas: 1000000n, +} as Network; diff --git a/packages/claim-cli/src/utils/print-table.ts b/packages/claim-cli/src/utils/print-table.ts new file mode 100644 index 0000000..ca30d62 --- /dev/null +++ b/packages/claim-cli/src/utils/print-table.ts @@ -0,0 +1,30 @@ +import { ethers } from 'ethers'; + +const INNER_WIDTH = 94; + +function printCenter(text: string, fill = '─', width = INNER_WIDTH) { + return text.padStart(width / 2 + text.length / 2, fill).padEnd(width, fill); +} + +export function printPreview(lskAddress: string, l2Address: string, balanceBeddows: string) { + const totalLSKString = `Total Amount: ${ethers.formatUnits(balanceBeddows, 8)} LSK`; + + console.log(`${printCenter('*** CLAIM PREVIEW ***', ' ', INNER_WIDTH + 2)}`); + console.log( + '┌─────────────── Lisk v4 ───────────────────────────────────────────── Lisk L2 ────────────────┐', + ); + console.log( + '│ │ │ │', + ); + console.log(`│ ${lskAddress} │ → │ ${l2Address} │`); + console.log( + '│ │ │ │', + ); + console.log( + '├──────────────────────────────────────────────────────────────────────────────────────────────┤', + ); + console.log(`│${printCenter(totalLSKString, ' ')}│`); + console.log( + '└──────────────────────────────────────────────────────────────────────────────────────────────┘', + ); +} diff --git a/packages/claim-cli/src/utils/sign-message.ts b/packages/claim-cli/src/utils/sign-message.ts new file mode 100644 index 0000000..a58fe74 --- /dev/null +++ b/packages/claim-cli/src/utils/sign-message.ts @@ -0,0 +1,14 @@ +import * as tweetnacl from 'tweetnacl'; +import { BYTES_9, remove0x } from './index'; +import { AbiCoder, keccak256 } from 'ethers'; + +export const signMessage = (hash: string, destinationAddress: string, privKey: Buffer): string => { + const abiCoder = new AbiCoder(); + + const message = + keccak256(abiCoder.encode(['bytes32', 'address'], [hash, destinationAddress])) + BYTES_9; + + return Buffer.from( + tweetnacl.sign.detached(Buffer.from(remove0x(message), 'hex'), privKey), + ).toString('hex'); +}; diff --git a/packages/claim-cli/tsconfig.json b/packages/claim-cli/tsconfig.json new file mode 100644 index 0000000..f33b1bb --- /dev/null +++ b/packages/claim-cli/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["src/**/*"] +} diff --git a/yarn.lock b/yarn.lock index 367fac9..c47d3b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -923,6 +923,17 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@inquirer/checkbox@^2.3.4": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-2.3.4.tgz#05e36f1ad33313479395f65aca65ee848575b102" + integrity sha512-e+V2YLwDqajiftVisDOu+lvinyGEihQu0X4Y1+jF8ZOMQj/Fufa0QVdk0QsF825kc0kvqDigdtSylETsFg3PKg== + dependencies: + "@inquirer/core" "^8.2.1" + "@inquirer/figures" "^1.0.2" + "@inquirer/type" "^1.3.2" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + "@inquirer/confirm@^3.1.6": version "3.1.7" resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-3.1.7.tgz#4568196121e4d26681fc2ff8f1f8d0f2f15e9b73" @@ -931,6 +942,14 @@ "@inquirer/core" "^8.2.0" "@inquirer/type" "^1.3.1" +"@inquirer/confirm@^3.1.8": + version "3.1.8" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-3.1.8.tgz#db80f23f775d9b980c6de2425dde39f9786bf1d3" + integrity sha512-f3INZ+ca4dQdn+MQiq1yP/mOIR/Oc8BLRYuDh6ciToWd6z4W8yArfzjBCMQ0BPY8PcJKwZxGIt8Z6yNT32eSTw== + dependencies: + "@inquirer/core" "^8.2.1" + "@inquirer/type" "^1.3.2" + "@inquirer/core@^8.2.0": version "8.2.0" resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-8.2.0.tgz#524ab7a6737958011f40959a1d0e5a8b90ff3471" @@ -950,11 +969,53 @@ strip-ansi "^6.0.1" wrap-ansi "^6.2.0" +"@inquirer/core@^8.2.1": + version "8.2.1" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-8.2.1.tgz#ee92c2bf25f378819f56290f8ed8bfef8c6cc94d" + integrity sha512-TIcuQMn2qrtyYe0j136UpHeYpk7AcR/trKeT/7YY0vRgcS9YSfJuQ2+PudPhSofLLsHNnRYAHScQCcVZrJkMqA== + dependencies: + "@inquirer/figures" "^1.0.2" + "@inquirer/type" "^1.3.2" + "@types/mute-stream" "^0.0.4" + "@types/node" "^20.12.12" + "@types/wrap-ansi" "^3.0.0" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + cli-spinners "^2.9.2" + cli-width "^4.1.0" + mute-stream "^1.0.0" + signal-exit "^4.1.0" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + +"@inquirer/editor@^2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@inquirer/editor/-/editor-2.1.8.tgz#652224b651d4fd71c162ab52de5e36ba6110962a" + integrity sha512-Ob9GdfiDCi9PJSF/tU7+bfbp2bVoFOPxI8Aosqh39TOum3FTyHzUDsZzSmi36KYNWHnmMH33t6bHxg1bBFTwng== + dependencies: + "@inquirer/core" "^8.2.1" + "@inquirer/type" "^1.3.2" + external-editor "^3.1.0" + +"@inquirer/expand@^2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@inquirer/expand/-/expand-2.1.8.tgz#0c48f69991b473f472137a953cd781e9c933adf7" + integrity sha512-dsWqz7cx6BAXrkxxvmKvSoDjB9FTueS7TJjRjTJDHVP335Ntfpha2ogp6+RC7iYpKyuUzFI2qedl+Mme9KmTjA== + dependencies: + "@inquirer/core" "^8.2.1" + "@inquirer/type" "^1.3.2" + chalk "^4.1.2" + "@inquirer/figures@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.1.tgz#d65f0bd0e9511a90b4d3543ee6a3ce7211f29417" integrity sha512-mtup3wVKia3ZwULPHcbs4Mor8Voi+iIXEWD7wCNbIO6lYR62oPCTQyrddi5OMYVXHzeCSoneZwJuS8sBvlEwDw== +"@inquirer/figures@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.2.tgz#a6af5e9f9969efb9ed3469130566315c36506b8a" + integrity sha512-4F1MBwVr3c/m4bAUef6LgkvBfSjzwH+OfldgHqcuacWwSUetFebM2wi58WfG9uk1rR98U6GwLed4asLJbwdV5w== + "@inquirer/input@^2.1.1": version "2.1.7" resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-2.1.7.tgz#143a7a4e3dc0b85353b478b85649e15d1ed71fc3" @@ -963,6 +1024,46 @@ "@inquirer/core" "^8.2.0" "@inquirer/type" "^1.3.1" +"@inquirer/input@^2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-2.1.8.tgz#442cee18f0fce6ef850627c6653c4c4c6e66c1ec" + integrity sha512-W1hsmUArJRGI8kL8+Kl+9wgnm02xPbpKtSIlwoHtRfIn8f/b/9spfNuTWolCVDHh3ZA4LS+Et71d1P6UpdD20A== + dependencies: + "@inquirer/core" "^8.2.1" + "@inquirer/type" "^1.3.2" + +"@inquirer/password@^2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-2.1.8.tgz#d52dfcb4579cccde25a35287227a0d25ccbc7c41" + integrity sha512-uBN1Hxq9igMtlUDHxCssJ4wWEksCwSJmK1mGr8A0V1co+ZBWPZ3cTN21MhyMc7VxIA/e7cG9/D2Qwzso64MCng== + dependencies: + "@inquirer/core" "^8.2.1" + "@inquirer/type" "^1.3.2" + ansi-escapes "^4.3.2" + +"@inquirer/prompts@^5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-5.0.4.tgz#245c992510fe193ba297046b041196f8f3f414f6" + integrity sha512-J7gaXH0UoYJbVzoL+1oTXmV1lzIIR5RTGvLTUED0NcVc/B/DA1dvDl7Yz5VvWt9DpIVk0TdxlrgtazbiV3xBjw== + dependencies: + "@inquirer/checkbox" "^2.3.4" + "@inquirer/confirm" "^3.1.8" + "@inquirer/editor" "^2.1.8" + "@inquirer/expand" "^2.1.8" + "@inquirer/input" "^2.1.8" + "@inquirer/password" "^2.1.8" + "@inquirer/rawlist" "^2.1.8" + "@inquirer/select" "^2.3.4" + +"@inquirer/rawlist@^2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@inquirer/rawlist/-/rawlist-2.1.8.tgz#5a6e186035e334348e52dbdb82cdc39b67169eec" + integrity sha512-h1NfMVcgR8MC7ckpk2UQsxh2+411iexj5GOFA1Ys9rg//l0N9XAZJQ+FDrza9cxOmf57MZFkJ/WE13Bp8z8IIQ== + dependencies: + "@inquirer/core" "^8.2.1" + "@inquirer/type" "^1.3.2" + chalk "^4.1.2" + "@inquirer/select@^2.3.2": version "2.3.3" resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-2.3.3.tgz#771ef76fbb0846df3d1954b3b3fd2b684247b2ee" @@ -974,11 +1075,27 @@ ansi-escapes "^4.3.2" chalk "^4.1.2" +"@inquirer/select@^2.3.4": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-2.3.4.tgz#f1040402ceb51ca4eb7a08d457fba540f01ec6a8" + integrity sha512-y9HGzHfPPh60QciH7WiKtjtWjgU24jrIsfJnq4Zqmu8k4HVVQPBXxiKKzwzJyJWwdWZcWATm4VDDWGFEjVHvGA== + dependencies: + "@inquirer/core" "^8.2.1" + "@inquirer/figures" "^1.0.2" + "@inquirer/type" "^1.3.2" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + "@inquirer/type@^1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-1.3.1.tgz#afb95ff78f44fff7e8a00e17d5820db6add2a076" integrity sha512-Pe3PFccjPVJV1vtlfVvm9OnlbxqdnP5QcscFEFEnK5quChf1ufZtM0r8mR5ToWHMxZOh0s8o/qp9ANGRTo/DAw== +"@inquirer/type@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-1.3.2.tgz#439b0b50c152c89fd369d2a17eff54869b4d79b8" + integrity sha512-5Frickan9c89QbPkSu6I6y8p+9eR6hZkdPahGmNDsTFX8FHLPAozyzCZMKUeW8FyYwnlCKUjqIEqxY+UctARiw== + "@jridgewell/resolve-uri@^3.0.3": version "3.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" @@ -1052,6 +1169,13 @@ semver "7.5.2" validator "13.7.0" +"@ljharb/through@^2.3.13": + version "2.3.13" + resolved "https://registry.yarnpkg.com/@ljharb/through/-/through-2.3.13.tgz#b7e4766e0b65aa82e529be945ab078de79874edc" + integrity sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ== + dependencies: + call-bind "^1.0.7" + "@mapbox/node-pre-gyp@^1.0.9": version "1.0.11" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" @@ -1846,6 +1970,14 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== +"@types/inquirer@^9.0.7": + version "9.0.7" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-9.0.7.tgz#61bb8d0e42f038b9a1738b08fba7fa98ad9b4b24" + integrity sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g== + dependencies: + "@types/through" "*" + rxjs "^7.2.0" + "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -1914,6 +2046,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@^20.12.12": + version "20.12.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.13.tgz#90ed3b8a4e52dd3c5dc5a42dde5b85b74ad8ed88" + integrity sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA== + dependencies: + undici-types "~5.26.4" + "@types/normalize-package-data@^2.4.0": version "2.4.4" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" @@ -1977,6 +2116,13 @@ resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz#5fd3592ff10c1e9695d377020c033116cc2889f2" integrity sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ== +"@types/through@*": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.33.tgz#14ebf599320e1c7851e7d598149af183c6b9ea56" + integrity sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ== + dependencies: + "@types/node" "*" + "@types/validator@^13.11.8", "@types/validator@^13.7.17": version "13.11.9" resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.11.9.tgz#adfe96520b437a0eaa798a475877bf2f75ee402d" @@ -2412,6 +2558,11 @@ base-x@^3.0.2: dependencies: safe-buffer "^5.0.1" +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" @@ -2427,6 +2578,15 @@ bip39@3.0.3: pbkdf2 "^3.0.9" randombytes "^2.0.1" +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + blakejs@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" @@ -2535,6 +2695,14 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtin-modules@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -2681,6 +2849,11 @@ change-case@^4: snake-case "^3.0.4" tslib "^2.0.3" +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + check-error@^1.0.2, check-error@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" @@ -2754,7 +2927,7 @@ cli-progress@^3.12.0: dependencies: string-width "^4.2.3" -cli-spinners@^2.9.2: +cli-spinners@^2.5.0, cli-spinners@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== @@ -2789,6 +2962,11 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -3007,6 +3185,13 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +defaults@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + dependencies: + clone "^1.0.2" + defer-to-connect@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" @@ -3698,6 +3883,15 @@ express@^4.19.2: utils-merge "1.0.1" vary "~1.1.2" +external-editor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + fancy-test@^3.0.14: version "3.0.15" resolved "https://registry.yarnpkg.com/fancy-test/-/fancy-test-3.0.15.tgz#b83b44f26a558e76e1acd62a15702b863551f810" @@ -4273,13 +4467,18 @@ hyperlinker@^1.0.0: resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ== -iconv-lite@0.4.24: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" @@ -4321,6 +4520,27 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +inquirer@^9.2.22: + version "9.2.22" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.2.22.tgz#718cb4153f0d35176aab27d495f8e358d1e2008f" + integrity sha512-SqLLa/Oe5rZUagTR9z+Zd6izyatHglbmbvVofo1KzuVB54YHleWzeHNLoR7FOICGOeQSqeLh1cordb3MzhGcEw== + dependencies: + "@inquirer/figures" "^1.0.2" + "@ljharb/through" "^2.3.13" + ansi-escapes "^4.3.2" + chalk "^5.3.0" + cli-cursor "^3.1.0" + cli-width "^4.1.0" + external-editor "^3.1.0" + lodash "^4.17.21" + mute-stream "1.0.0" + ora "^5.4.1" + run-async "^3.0.0" + rxjs "^7.8.1" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + internal-slot@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" @@ -4440,6 +4660,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-negative-zero@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" @@ -4757,7 +4982,7 @@ lodash@^4.17.13, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@4.1.0: +log-symbols@4.1.0, log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -5035,7 +5260,7 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -mute-stream@^1.0.0: +mute-stream@1.0.0, mute-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== @@ -5287,6 +5512,26 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + p-cancelable@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" @@ -5644,7 +5889,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@^3.6.0: +readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -5797,6 +6042,11 @@ rlp@^2.2.4: dependencies: bn.js "^5.2.0" +run-async@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-3.0.0.tgz#42a432f6d76c689522058984384df28be379daad" + integrity sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -5804,7 +6054,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.5.5: +rxjs@^7.2.0, rxjs@^7.5.5, rxjs@^7.8.1: version "7.8.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== @@ -6359,6 +6609,13 @@ tiny-jsonc@^1.0.1: resolved "https://registry.yarnpkg.com/tiny-jsonc/-/tiny-jsonc-1.0.1.tgz#71de47c9d812b411e87a9f3ab4a5fe42cd8d8f9c" integrity sha512-ik6BCxzva9DoiEfDX/li0L2cWKPPENYvixUprFdl3YPi4bZZUhDnNI9YUkacrv+uIG90dnxR5mNqaoD6UhD6Bw== +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -6630,6 +6887,13 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" From 824c825b15203106768d65509af62dab6e77ccdd Mon Sep 17 00:00:00 2001 From: Franco NG Date: Fri, 7 Jun 2024 16:40:32 +0200 Subject: [PATCH 02/23] Tidy up code --- .../src/applications/check-eligibility.ts | 2 +- .../src/applications/publish-multisig-claim.ts | 12 ++---------- .../claim-cli/src/applications/submit-claim.ts | 2 +- packages/claim-cli/src/commands/start/index.ts | 7 +++---- .../src/utils/confirm-send-transaction.ts | 6 +++--- packages/claim-cli/src/utils/endpoint.ts | 4 ++-- .../utils/{getPrivateKey.ts => get-private-key.ts} | 14 +++++++------- packages/claim-cli/src/utils/network.ts | 2 ++ 8 files changed, 21 insertions(+), 28 deletions(-) rename packages/claim-cli/src/utils/{getPrivateKey.ts => get-private-key.ts} (81%) diff --git a/packages/claim-cli/src/applications/check-eligibility.ts b/packages/claim-cli/src/applications/check-eligibility.ts index 18a2e7f..4cc1f8f 100644 --- a/packages/claim-cli/src/applications/check-eligibility.ts +++ b/packages/claim-cli/src/applications/check-eligibility.ts @@ -8,7 +8,7 @@ export default async function checkEligibility(networkParams: Network) { const result = await fetchCheckEligibility(lskAddress, networkParams); if (!result.account && result.multisigAccounts.length === 0) { - console.log(`No Eligible Claim for Address: ${lskAddress}`); + console.log(`No Eligible Claim for Address: ${lskAddress}.`); process.exit(1); } diff --git a/packages/claim-cli/src/applications/publish-multisig-claim.ts b/packages/claim-cli/src/applications/publish-multisig-claim.ts index 9c7a9f6..c07f527 100644 --- a/packages/claim-cli/src/applications/publish-multisig-claim.ts +++ b/packages/claim-cli/src/applications/publish-multisig-claim.ts @@ -2,7 +2,7 @@ import { ethers, ZeroHash } from 'ethers'; import { input, select } from '@inquirer/prompts'; import { Network } from '../utils/network'; import { fetchCheckEligibility } from '../utils/endpoint'; -import { getETHWallet } from '../utils/getPrivateKey'; +import { getETHWallet } from '../utils/get-private-key'; import L2ClaimAbi from '../abi/L2Claim'; import confirmSendTransaction from '../utils/confirm-send-transaction'; import { printPreview } from '../utils/print-table'; @@ -17,7 +17,7 @@ export default async function publishMultisigClaim( const result = await fetchCheckEligibility(lskAddress, networkParams); if (!result.account) { - console.log(`Address ${lskAddress} has no eligibility`); + console.log(`Address ${lskAddress} has no eligibility.`); process.exit(1); } @@ -121,14 +121,6 @@ export default async function publishMultisigClaim( const contractWithSigner = claimContract.connect(walletWithSigner) as ethers.Contract; - console.log([ - result.account.proof, - result.account.address, - result.account.balanceBeddows, - [result.account.mandatoryKeys, result.account.optionalKeys], - destination, - signatures.map(signature => [signature.r, signature.s]), - ]); printPreview(result.account.lskAddress, destination, result.account.balanceBeddows); await confirmSendTransaction( contractWithSigner.claimMultisigAccount, diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index 372afdd..a49bb43 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -4,7 +4,7 @@ import { fetchCheckEligibility, fetchSubmitMultisig } from '../utils/endpoint'; import L2ClaimAbi from '../abi/L2Claim'; import { Network } from '../utils/network'; import { ethers } from 'ethers'; -import { getETHWallet, getLSKPrivateKey } from '../utils/getPrivateKey'; +import { getETHWallet, getLSKPrivateKey } from '../utils/get-private-key'; import { signMessage } from '../utils/sign-message'; import { printPreview } from '../utils/print-table'; import confirmSendTransaction from '../utils/confirm-send-transaction'; diff --git a/packages/claim-cli/src/commands/start/index.ts b/packages/claim-cli/src/commands/start/index.ts index 3e53c0f..af5f2c4 100644 --- a/packages/claim-cli/src/commands/start/index.ts +++ b/packages/claim-cli/src/commands/start/index.ts @@ -14,15 +14,14 @@ enum Choice { export default class Start extends Command { static flags = { network: Flags.string({ - description: - 'Destination address at signing stage. Default is the contract address created by default mnemonic in Anvil/Ganache when nonce=0', + description: 'Network ', required: false, default: 'mainnet', options: ['mainnet', 'testnet', 'local'], }), }; - static description = 'Generate example data for demo purpose'; + static description = 'Start Lisk Migration CLI'; static examples = [`$ oex example`]; @@ -30,7 +29,7 @@ export default class Start extends Command { const { flags } = await this.parse(Start); const { network } = flags; - console.log(`Welcome to Lisk Claim CLI (Running on "${network}" Network)`); + console.log(`Welcome to Lisk Migration CLI (Running on "${network}" Network)`); const answer = await select({ message: 'Please enter your choices', choices: [ diff --git a/packages/claim-cli/src/utils/confirm-send-transaction.ts b/packages/claim-cli/src/utils/confirm-send-transaction.ts index 910e5ca..a2ce699 100644 --- a/packages/claim-cli/src/utils/confirm-send-transaction.ts +++ b/packages/claim-cli/src/utils/confirm-send-transaction.ts @@ -32,15 +32,15 @@ export default async function confirmSendTransaction( const estimatedFee = estimatedGas * maxFeePerGas; const ethBalance = await provider.getBalance(walletWithSigner.address); console.log( - `> Estimated Network Fee (${estimatedGas} * ${maxFeePerGas} wei) = ${ethers.formatUnits(estimatedFee)} ETH`, + `> Estimated Network Fee (${estimatedGas} * ${maxFeePerGas} wei) = ${ethers.formatUnits(estimatedFee)} ETH.`, ); console.log(`> Your Balance: ${ethers.formatUnits(ethBalance)} ETH`); if (estimatedFee > ethBalance) { - console.log('Insufficient Balance for the Transaction'); + console.log('Insufficient Balance for the Transaction.'); process.exit(1); } if (!(await confirm({ message: 'Confirm to Send Transaction', default: false }))) { - console.log('User Cancelled Submission'); + console.log('User Cancelled Submission.'); process.exit(1); } diff --git a/packages/claim-cli/src/utils/endpoint.ts b/packages/claim-cli/src/utils/endpoint.ts index 8f58fbb..74298eb 100644 --- a/packages/claim-cli/src/utils/endpoint.ts +++ b/packages/claim-cli/src/utils/endpoint.ts @@ -28,7 +28,7 @@ export const fetchCheckEligibility = async ( }); if (response.status !== 200) { - console.log('Network Error, please try again later'); + console.log('Network Error, please try again later.'); process.exit(1); } @@ -73,7 +73,7 @@ export const fetchSubmitMultisig = async ( }); if (response.status !== 200) { - console.log('Network Error, please try again later'); + console.log('Network Error, please try again later.'); process.exit(1); } diff --git a/packages/claim-cli/src/utils/getPrivateKey.ts b/packages/claim-cli/src/utils/get-private-key.ts similarity index 81% rename from packages/claim-cli/src/utils/getPrivateKey.ts rename to packages/claim-cli/src/utils/get-private-key.ts index 9411632..2536e98 100644 --- a/packages/claim-cli/src/utils/getPrivateKey.ts +++ b/packages/claim-cli/src/utils/get-private-key.ts @@ -1,6 +1,6 @@ -import { input, select } from '@inquirer/prompts'; +import { input, password, select } from '@inquirer/prompts'; import * as crypto from '@liskhq/lisk-cryptography'; -import { ethers, HDNodeWallet, Wallet } from 'ethers'; +import { HDNodeWallet, Wallet } from 'ethers'; import { remove0x } from './index'; enum SecretType { @@ -32,7 +32,7 @@ export const getPrivateKeyFromString = async (): Promise => { const privKeyFormatted = remove0x(privKey); if (!privKeyFormatted.match(/^[A-Fa-f0-9]{64}$/)) { - console.log('Invalid Private Key, please check again'); + console.log('Invalid Private Key, please check again.'); process.exit(1); } return Buffer.from(privKey, 'hex'); @@ -45,10 +45,10 @@ export const getLSKPrivateKey = async () => { export const getETHWalletFromMnemonic = async (): Promise => { const mnemonic = await input({ message: 'Your L2 Mnemonic' }); - const password = await input({ message: 'BIP39 Passphrase (Optional)' }); + const passphrase = await password({ message: 'BIP39 Passphrase (Optional)' }); const path = await input({ message: 'Path', default: "m/44'/60'/0'/0/0" }); - return ethers.HDNodeWallet.fromPhrase(mnemonic, password, path); + return HDNodeWallet.fromPhrase(mnemonic, passphrase, path); }; export const getETHWalletKeyFromString = async (): Promise => { @@ -59,10 +59,10 @@ export const getETHWalletKeyFromString = async (): Promise => { const privKeyFormatted = remove0x(privKey); if (!privKeyFormatted.match(/^[A-Fa-f0-9]{64}$/)) { - console.log('Invalid Private Key, please check again'); + console.log('Invalid Private Key, please check again.'); process.exit(1); } - return new ethers.Wallet(privKey); + return new Wallet(privKey); }; export const getETHWallet = async () => { diff --git a/packages/claim-cli/src/utils/network.ts b/packages/claim-cli/src/utils/network.ts index 0545969..55adc0e 100644 --- a/packages/claim-cli/src/utils/network.ts +++ b/packages/claim-cli/src/utils/network.ts @@ -2,6 +2,8 @@ export interface Network { api: string; rpc: string; l2Claim: string; + maxFeePerGas: bigint; + maxPriorityFeePerGas: bigint; } export const Mainnet = { From 9a1d5cbea7237836ba85295ebfead783c047d5a1 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Mon, 24 Jun 2024 18:13:15 +0200 Subject: [PATCH 03/23] Update Claim CLI tools --- packages/claim-cli/package.json | 4 +- .../src/applications/check-eligibility.ts | 41 ++++++++++++++----- .../applications/publish-multisig-claim.ts | 6 +-- .../src/applications/submit-claim.ts | 2 +- .../claim-cli/src/utils/build-account-list.ts | 20 +++++---- packages/claim-cli/src/utils/endpoint.ts | 2 +- .../claim-cli/src/utils/get-private-key.ts | 16 +++++--- packages/claim-cli/src/utils/network.ts | 12 +----- packages/claim-cli/src/utils/sign-message.ts | 1 + 9 files changed, 61 insertions(+), 43 deletions(-) diff --git a/packages/claim-cli/package.json b/packages/claim-cli/package.json index aa1488f..574cb62 100644 --- a/packages/claim-cli/package.json +++ b/packages/claim-cli/package.json @@ -29,9 +29,7 @@ "postpack": "shx rm -f oclif.manifest.json", "prepack": "yarn build && oclif manifest && oclif readme", "prepare": "yarn build", - "test": "mocha --forbid-only \"test/**/*.test.ts\"", - "version": "oclif readme && git add README.md", - "dev": "./bin/dev.js start --network=local" + "test": "echo test" }, "oclif": { "bin": "claim-cli", diff --git a/packages/claim-cli/src/applications/check-eligibility.ts b/packages/claim-cli/src/applications/check-eligibility.ts index 4cc1f8f..e2e5ee6 100644 --- a/packages/claim-cli/src/applications/check-eligibility.ts +++ b/packages/claim-cli/src/applications/check-eligibility.ts @@ -1,6 +1,6 @@ import { input } from '@inquirer/prompts'; import { Network } from '../utils/network'; -import buildAccountList from '../utils/build-account-list'; +import buildAccountList, { AccountListChoice } from '../utils/build-account-list'; import { fetchCheckEligibility } from '../utils/endpoint'; export default async function checkEligibility(networkParams: Network) { @@ -14,17 +14,36 @@ export default async function checkEligibility(networkParams: Network) { const accountList = await buildAccountList(result, networkParams); - console.log('Claimed:'); - for (const [index, account] of accountList.entries()) { - if (account.disabled) { - console.log(`${index + 1}: ${account.name} ${account.disabled}`); - } + const accountGroupedByClaimStatus = accountList.reduce( + ( + acc: { + claimed: AccountListChoice[]; + unclaimed: AccountListChoice[]; + }, + account, + ) => { + if (account.claimed) { + acc.claimed.push(account); + } else { + acc.unclaimed.push(account); + } + return acc; + }, + { + claimed: [], + unclaimed: [], + }, + ); + + console.log(`> Claimed Addresses (${accountGroupedByClaimStatus.claimed.length}):`); + for (const [index, account] of accountGroupedByClaimStatus.claimed.entries()) { + console.log(`> ${index + 1}: ${account.name} ${account.claimed}`); } - console.log('Eligible Claims:'); - for (const [index, account] of accountList.entries()) { - if (!account.disabled) { - console.log(`${index + 1}: ${account.name}}`); - } + console.log('> =========='); + + console.log(`> Eligible Claims (${accountGroupedByClaimStatus.unclaimed.length}):`); + for (const [index, account] of accountGroupedByClaimStatus.unclaimed.entries()) { + console.log(`${index + 1}: ${account.name}`); } } diff --git a/packages/claim-cli/src/applications/publish-multisig-claim.ts b/packages/claim-cli/src/applications/publish-multisig-claim.ts index c07f527..05fda51 100644 --- a/packages/claim-cli/src/applications/publish-multisig-claim.ts +++ b/packages/claim-cli/src/applications/publish-multisig-claim.ts @@ -22,19 +22,19 @@ export default async function publishMultisigClaim( } if (!result.account.ready) { - console.log(`Address ${lskAddress} has insufficient signatures.`); + console.log(`> Address ${lskAddress} has insufficient signatures.`); process.exit(1); } const claimedTo = await claimContract.claimedTo(result.account.address); if (claimedTo !== ethers.ZeroAddress) { - console.log(`Address ${lskAddress} has already been claimed.`); + console.log(`> Address ${lskAddress} has already been claimed.`); process.exit(1); } const wallet = await getETHWallet(); const walletWithSigner = wallet.connect(provider); - console.log('Representing LSK L2 Address:', wallet.address); + console.log('> Representing LSK L2 Address:', wallet.address); const signaturesGroupByDestinationAddress = result.signatures.reduce( ( diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index a49bb43..c7aa8f1 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -34,7 +34,7 @@ export default async function submitClaim(networkParams: Network) { // Regular Claim const wallet = await getETHWallet(); const walletWithSigner = wallet.connect(new ethers.JsonRpcProvider(networkParams.rpc)); - console.log('Representing LSK L2 Address:', wallet.address); + console.log('> Representing LSK L2 Address:', wallet.address); const destinationAddress = await input({ message: 'Claim Destination Address', diff --git a/packages/claim-cli/src/utils/build-account-list.ts b/packages/claim-cli/src/utils/build-account-list.ts index a79bb31..91bf79e 100644 --- a/packages/claim-cli/src/utils/build-account-list.ts +++ b/packages/claim-cli/src/utils/build-account-list.ts @@ -6,7 +6,7 @@ import L2ClaimAbi from '../abi/L2Claim'; export interface AccountListChoice { name: string; value: Account; - disabled?: string; + claimed?: string; } export default async function buildAccountList( @@ -24,12 +24,16 @@ export default async function buildAccountList( {}, ); - const accountList: AccountListChoice[] = [ - { - name: `${result.account.lskAddress} (${ethers.formatUnits(result.account.balanceBeddows, 8)} LSK)`, - value: result.account, - }, - ].concat( + const regularAccountSingleton = result.account + ? [ + { + name: `${result.account.lskAddress} (${ethers.formatUnits(result.account.balanceBeddows, 8)} LSK)`, + value: result.account, + }, + ] + : []; + + const accountList: AccountListChoice[] = regularAccountSingleton.concat( result.multisigAccounts.map(account => ({ name: `${account.lskAddress} [${numOfSigned[account.lskAddress] ?? 0}/${account.numberOfSignatures}] (${ethers.formatUnits(account.balanceBeddows, 8)} LSK)`, value: account, @@ -45,7 +49,7 @@ export default async function buildAccountList( for (const account of accountList) { const claimedTo = await claimContract.claimedTo(account.value.address); if (claimedTo !== ethers.ZeroAddress) { - account.disabled = `Already Claimed To: ${claimedTo}`; + account.claimed = `Already Claimed To: ${claimedTo}`; } } diff --git a/packages/claim-cli/src/utils/endpoint.ts b/packages/claim-cli/src/utils/endpoint.ts index 74298eb..d60872c 100644 --- a/packages/claim-cli/src/utils/endpoint.ts +++ b/packages/claim-cli/src/utils/endpoint.ts @@ -4,7 +4,7 @@ import { JSONRPCSuccessResponse, SubmitMultisigResponse, } from '../interfaces'; -import { Network } from '../utils/network'; +import { Network } from './network'; export const fetchCheckEligibility = async ( lskAddress: string, diff --git a/packages/claim-cli/src/utils/get-private-key.ts b/packages/claim-cli/src/utils/get-private-key.ts index 2536e98..3912bb7 100644 --- a/packages/claim-cli/src/utils/get-private-key.ts +++ b/packages/claim-cli/src/utils/get-private-key.ts @@ -17,30 +17,34 @@ const getSecretType = (wallet: string) => { name: 'Private Key', value: SecretType.PRIVATE_KEY }, ], }); -export const getPrivateKeyFromMnemonic = async (): Promise => { + +export const getLSKPrivateKeyFromMnemonic = async (): Promise => { + console.log(input); const mnemonic = await input({ message: 'Your Mnemonic' }); const path = await input({ message: 'Path', default: "m/44'/134'/0'" }); return crypto.ed.getPrivateKeyFromPhraseAndPath(mnemonic.trim(), path); }; -export const getPrivateKeyFromString = async (): Promise => { +export const getLSKPrivateKeyFromString = async (): Promise => { const privKey = await input({ message: 'Your Private Key', }); const privKeyFormatted = remove0x(privKey); - if (!privKeyFormatted.match(/^[A-Fa-f0-9]{64}$/)) { - console.log('Invalid Private Key, please check again.'); + if (!privKeyFormatted.match(/^[A-Fa-f0-9]{128}$/)) { + console.log( + 'Invalid Private Key, please check again. Private Key should be 128-character long', + ); process.exit(1); } - return Buffer.from(privKey, 'hex'); + return Buffer.from(privKeyFormatted, 'hex'); }; export const getLSKPrivateKey = async () => { const type = await getSecretType('Lisk L1 Wallet'); - return [getPrivateKeyFromMnemonic, getPrivateKeyFromString][type](); + return [getLSKPrivateKeyFromMnemonic, getLSKPrivateKeyFromString][type](); }; export const getETHWalletFromMnemonic = async (): Promise => { diff --git a/packages/claim-cli/src/utils/network.ts b/packages/claim-cli/src/utils/network.ts index 55adc0e..1640ad7 100644 --- a/packages/claim-cli/src/utils/network.ts +++ b/packages/claim-cli/src/utils/network.ts @@ -17,15 +17,7 @@ export const Mainnet = { export const Testnet = { api: 'https://token-claim-api.lisk.com/rpc', rpc: 'https://rpc.sepolia-api.lisk.com', - l2Claim: '0xD7BE2Fd98BfD64c1dfCf6c013fC593eF09219994', - maxFeePerGas: 1002060n, - maxPriorityFeePerGas: 1000000n, -} as Network; - -export const Local = { - api: 'http://127.0.0.1:5555/rpc', - rpc: 'http://127.0.0.1:8546', - l2Claim: '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9', + l2Claim: '0x3D4190b08E3E30183f5AdE3A116f2534Ee3a4f94', maxFeePerGas: 1002060n, maxPriorityFeePerGas: 1000000n, -} as Network; +} as Network; \ No newline at end of file diff --git a/packages/claim-cli/src/utils/sign-message.ts b/packages/claim-cli/src/utils/sign-message.ts index a58fe74..683c3c6 100644 --- a/packages/claim-cli/src/utils/sign-message.ts +++ b/packages/claim-cli/src/utils/sign-message.ts @@ -8,6 +8,7 @@ export const signMessage = (hash: string, destinationAddress: string, privKey: B const message = keccak256(abiCoder.encode(['bytes32', 'address'], [hash, destinationAddress])) + BYTES_9; + console.log('signMessage', privKey.toString('hex')); return Buffer.from( tweetnacl.sign.detached(Buffer.from(remove0x(message), 'hex'), privKey), ).toString('hex'); From 66ef1053459c790b8f07f6bd7c2cc08868990333 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Tue, 25 Jun 2024 00:52:59 +0200 Subject: [PATCH 04/23] Update format, added test cases --- .../src/applications/check-eligibility.ts | 8 +- .../applications/publish-multisig-claim.ts | 11 +- .../src/applications/submit-claim.ts | 9 +- .../claim-cli/src/commands/start/index.ts | 5 +- .../src/utils/confirm-send-transaction.ts | 21 +-- packages/claim-cli/src/utils/endpoint.ts | 16 +- .../claim-cli/src/utils/get-private-key.ts | 40 +++-- packages/claim-cli/src/utils/get-prompts.ts | 14 ++ packages/claim-cli/src/utils/network.ts | 2 +- packages/claim-cli/src/utils/sign-message.ts | 1 - .../test/utils/get-private-key.test.ts | 146 ++++++++++++++++++ .../claim-cli/test/utils/sign-message.test.ts | 26 ++++ 12 files changed, 249 insertions(+), 50 deletions(-) create mode 100644 packages/claim-cli/src/utils/get-prompts.ts create mode 100644 packages/claim-cli/test/utils/get-private-key.test.ts create mode 100644 packages/claim-cli/test/utils/sign-message.test.ts diff --git a/packages/claim-cli/src/applications/check-eligibility.ts b/packages/claim-cli/src/applications/check-eligibility.ts index e2e5ee6..c1b2aa8 100644 --- a/packages/claim-cli/src/applications/check-eligibility.ts +++ b/packages/claim-cli/src/applications/check-eligibility.ts @@ -1,15 +1,15 @@ -import { input } from '@inquirer/prompts'; import { Network } from '../utils/network'; import buildAccountList, { AccountListChoice } from '../utils/build-account-list'; import { fetchCheckEligibility } from '../utils/endpoint'; +import { getInput } from '../utils/get-prompts'; export default async function checkEligibility(networkParams: Network) { - const lskAddress = await input({ message: 'Your LSK Address' }); + const lskAddress = await getInput({ message: 'Your LSK Address' }); const result = await fetchCheckEligibility(lskAddress, networkParams); if (!result.account && result.multisigAccounts.length === 0) { - console.log(`No Eligible Claim for Address: ${lskAddress}.`); - process.exit(1); + console.log(`> No Eligible Claim for Address: ${lskAddress}.`); + return process.exit(1); } const accountList = await buildAccountList(result, networkParams); diff --git a/packages/claim-cli/src/applications/publish-multisig-claim.ts b/packages/claim-cli/src/applications/publish-multisig-claim.ts index 05fda51..09750ba 100644 --- a/packages/claim-cli/src/applications/publish-multisig-claim.ts +++ b/packages/claim-cli/src/applications/publish-multisig-claim.ts @@ -1,35 +1,36 @@ import { ethers, ZeroHash } from 'ethers'; -import { input, select } from '@inquirer/prompts'; +import { select } from '@inquirer/prompts'; import { Network } from '../utils/network'; import { fetchCheckEligibility } from '../utils/endpoint'; import { getETHWallet } from '../utils/get-private-key'; import L2ClaimAbi from '../abi/L2Claim'; import confirmSendTransaction from '../utils/confirm-send-transaction'; import { printPreview } from '../utils/print-table'; +import { getInput } from '../utils/get-prompts'; export default async function publishMultisigClaim( networkParams: Network, address: string | null = null, ) { const provider = new ethers.JsonRpcProvider(networkParams.rpc); - const lskAddress = address || (await input({ message: 'Multisig Address to be published' })); + const lskAddress = address || (await getInput({ message: 'Multisig Address to be published' })); const claimContract = new ethers.Contract(networkParams.l2Claim, L2ClaimAbi, provider); const result = await fetchCheckEligibility(lskAddress, networkParams); if (!result.account) { console.log(`Address ${lskAddress} has no eligibility.`); - process.exit(1); + return process.exit(1); } if (!result.account.ready) { console.log(`> Address ${lskAddress} has insufficient signatures.`); - process.exit(1); + return process.exit(1); } const claimedTo = await claimContract.claimedTo(result.account.address); if (claimedTo !== ethers.ZeroAddress) { console.log(`> Address ${lskAddress} has already been claimed.`); - process.exit(1); + return process.exit(1); } const wallet = await getETHWallet(); diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index c7aa8f1..45e4ea2 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -1,4 +1,4 @@ -import { input, select, confirm } from '@inquirer/prompts'; +import { select, confirm } from '@inquirer/prompts'; import * as crypto from '@liskhq/lisk-cryptography'; import { fetchCheckEligibility, fetchSubmitMultisig } from '../utils/endpoint'; import L2ClaimAbi from '../abi/L2Claim'; @@ -11,6 +11,7 @@ import confirmSendTransaction from '../utils/confirm-send-transaction'; import publishMultisigClaim from './publish-multisig-claim'; import buildAccountList from '../utils/build-account-list'; import { append0x } from '../utils'; +import { getInput } from '../utils/get-prompts'; export default async function submitClaim(networkParams: Network) { const privateKey = await getLSKPrivateKey(); @@ -22,7 +23,7 @@ export default async function submitClaim(networkParams: Network) { const result = await fetchCheckEligibility(lskAddress, networkParams); if (!result.account && result.multisigAccounts.length === 0) { console.log(`No Eligible Claim for Address: ${lskAddress}`); - process.exit(1); + return process.exit(1); } const claimAccount = await select({ @@ -36,7 +37,7 @@ export default async function submitClaim(networkParams: Network) { const walletWithSigner = wallet.connect(new ethers.JsonRpcProvider(networkParams.rpc)); console.log('> Representing LSK L2 Address:', wallet.address); - const destinationAddress = await input({ + const destinationAddress = await getInput({ message: 'Claim Destination Address', default: wallet.address, }); @@ -97,7 +98,7 @@ export default async function submitClaim(networkParams: Network) { }); } if (destinationAddress === '') { - destinationAddress = await input({ message: 'Destination L2 Address' }); + destinationAddress = await getInput({ message: 'Destination L2 Address' }); } const signature = signMessage(claimAccount.hash, destinationAddress, privateKey); diff --git a/packages/claim-cli/src/commands/start/index.ts b/packages/claim-cli/src/commands/start/index.ts index af5f2c4..d2cb6b5 100644 --- a/packages/claim-cli/src/commands/start/index.ts +++ b/packages/claim-cli/src/commands/start/index.ts @@ -3,7 +3,7 @@ import { select } from '@inquirer/prompts'; import checkEligibility from '../../applications/check-eligibility'; import submitClaim from '../../applications/submit-claim'; import publishMultisigClaim from '../../applications/publish-multisig-claim'; -import { Local, Mainnet, Testnet } from '../../utils/network'; +import { Mainnet, Testnet } from '../../utils/network'; enum Choice { CHECK_ELIGIBILITY, @@ -45,8 +45,7 @@ export default class Start extends Command { const networkParams = { mainnet: Mainnet, testnet: Testnet, - local: Local, - }[network as 'mainnet' | 'testnet' | 'local']; + }[network as 'mainnet' | 'testnet']; await [checkEligibility, submitClaim, publishMultisigClaim][answer](networkParams); } } diff --git a/packages/claim-cli/src/utils/confirm-send-transaction.ts b/packages/claim-cli/src/utils/confirm-send-transaction.ts index a2ce699..691a7db 100644 --- a/packages/claim-cli/src/utils/confirm-send-transaction.ts +++ b/packages/claim-cli/src/utils/confirm-send-transaction.ts @@ -1,5 +1,6 @@ import { BaseContractMethod, ethers, HDNodeWallet, Wallet } from 'ethers'; -import { input, confirm } from '@inquirer/prompts'; +import { confirm } from '@inquirer/prompts'; +import { getInput } from './get-prompts'; export default async function confirmSendTransaction( contractMethod: BaseContractMethod, @@ -8,13 +9,13 @@ export default async function confirmSendTransaction( ) { const provider = walletWithSigner.provider; if (!provider) { - process.exit(1); + return process.exit(1); } const feeData = await provider.getFeeData(); const suggestedMaxFeePerGas = feeData.maxFeePerGas ?? BigInt(1); const maxFeePerGas = BigInt( - await input({ + await getInput({ message: `Max Fee Per Gas (wei) (Suggested: ${suggestedMaxFeePerGas.toString()})`, default: suggestedMaxFeePerGas.toString(), }), @@ -22,7 +23,7 @@ export default async function confirmSendTransaction( const suggestedMaxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? BigInt(1); const maxPriorityFeePerGas = BigInt( - await input({ + await getInput({ message: `Max Priority Fee Per Gas (wei) (Suggested: ${suggestedMaxPriorityFeePerGas.toString()})`, default: suggestedMaxPriorityFeePerGas.toString(), }), @@ -36,20 +37,20 @@ export default async function confirmSendTransaction( ); console.log(`> Your Balance: ${ethers.formatUnits(ethBalance)} ETH`); if (estimatedFee > ethBalance) { - console.log('Insufficient Balance for the Transaction.'); - process.exit(1); + console.log('> Insufficient Balance for the Transaction.'); + return process.exit(1); } if (!(await confirm({ message: 'Confirm to Send Transaction', default: false }))) { - console.log('User Cancelled Submission.'); - process.exit(1); + console.log('> User Cancelled Submission.'); + return process.exit(1); } const tx = await contractMethod(...args, { maxFeePerGas, maxPriorityFeePerGas, }); - console.log(`Successfully submitted transaction, tx: ${tx.hash}. Waiting for Confirmation ...`); + console.log(`> Successfully submitted transaction, tx: ${tx.hash}. Waiting for Confirmation ...`); const receipt = await tx.wait(); - console.log(`Transaction Confirmed at Block: ${receipt.blockNumber}!`); + console.log(`> Transaction Confirmed at Block: ${receipt.blockNumber}!`); } diff --git a/packages/claim-cli/src/utils/endpoint.ts b/packages/claim-cli/src/utils/endpoint.ts index d60872c..3e296d5 100644 --- a/packages/claim-cli/src/utils/endpoint.ts +++ b/packages/claim-cli/src/utils/endpoint.ts @@ -28,16 +28,16 @@ export const fetchCheckEligibility = async ( }); if (response.status !== 200) { - console.log('Network Error, please try again later.'); - process.exit(1); + console.log('> Network Error, please try again later.'); + return process.exit(1); } const { result, error } = (await response.json()) as | JSONRPCSuccessResponse | JSONRPCErrorResponse; if (error) { - console.log('Claim Endpoint returned error:', error.message); - process.exit(1); + console.log('> Claim Endpoint returned error:', error.message); + return process.exit(1); } return result; @@ -73,16 +73,16 @@ export const fetchSubmitMultisig = async ( }); if (response.status !== 200) { - console.log('Network Error, please try again later.'); - process.exit(1); + console.log('> Network Error, please try again later.'); + return process.exit(1); } const { result, error } = (await response.json()) as | JSONRPCSuccessResponse | JSONRPCErrorResponse; if (error) { - console.log('Claim Endpoint returned error:', error.message); - process.exit(1); + console.log('> Claim Endpoint returned error:', error.message); + return process.exit(1); } return result; diff --git a/packages/claim-cli/src/utils/get-private-key.ts b/packages/claim-cli/src/utils/get-private-key.ts index 3912bb7..bd32124 100644 --- a/packages/claim-cli/src/utils/get-private-key.ts +++ b/packages/claim-cli/src/utils/get-private-key.ts @@ -1,7 +1,8 @@ -import { input, password, select } from '@inquirer/prompts'; +import { select } from '@inquirer/prompts'; import * as crypto from '@liskhq/lisk-cryptography'; -import { HDNodeWallet, Wallet } from 'ethers'; +import { Mnemonic, HDNodeWallet, Wallet } from 'ethers'; import { remove0x } from './index'; +import { getInput, getPassword } from './get-prompts'; enum SecretType { MNEMONIC, @@ -19,15 +20,19 @@ const getSecretType = (wallet: string) => }); export const getLSKPrivateKeyFromMnemonic = async (): Promise => { - console.log(input); - const mnemonic = await input({ message: 'Your Mnemonic' }); - const path = await input({ message: 'Path', default: "m/44'/134'/0'" }); + const mnemonic = await getInput({ message: 'Your Mnemonic' }); + if (!Mnemonic.isValidMnemonic(mnemonic)) { + console.log('> Invalid Mnemonic, please check again.'); + return process.exit(1); + } + + const path = await getInput({ message: 'Path', default: "m/44'/134'/0'" }); return crypto.ed.getPrivateKeyFromPhraseAndPath(mnemonic.trim(), path); }; export const getLSKPrivateKeyFromString = async (): Promise => { - const privKey = await input({ + const privKey = await getInput({ message: 'Your Private Key', }); @@ -35,9 +40,9 @@ export const getLSKPrivateKeyFromString = async (): Promise => { if (!privKeyFormatted.match(/^[A-Fa-f0-9]{128}$/)) { console.log( - 'Invalid Private Key, please check again. Private Key should be 128-character long', + '> Invalid Private Key, please check again. Private Key should be 128-character long.', ); - process.exit(1); + return process.exit(1); } return Buffer.from(privKeyFormatted, 'hex'); }; @@ -48,23 +53,30 @@ export const getLSKPrivateKey = async () => { }; export const getETHWalletFromMnemonic = async (): Promise => { - const mnemonic = await input({ message: 'Your L2 Mnemonic' }); - const passphrase = await password({ message: 'BIP39 Passphrase (Optional)' }); - const path = await input({ message: 'Path', default: "m/44'/60'/0'/0/0" }); + const mnemonic = await getInput({ message: 'Your L2 Mnemonic' }); + if (!Mnemonic.isValidMnemonic(mnemonic)) { + console.log('> Invalid Mnemonic, please check again.'); + return process.exit(1); + } + + const passphrase = await getPassword({ message: 'BIP39 Passphrase (Optional)' }); + const path = await getInput({ message: 'Path', default: "m/44'/60'/0'/0/0" }); return HDNodeWallet.fromPhrase(mnemonic, passphrase, path); }; export const getETHWalletKeyFromString = async (): Promise => { - const privKey = await input({ + const privKey = await getInput({ message: 'Your Private Key', }); const privKeyFormatted = remove0x(privKey); if (!privKeyFormatted.match(/^[A-Fa-f0-9]{64}$/)) { - console.log('Invalid Private Key, please check again.'); - process.exit(1); + console.log( + '> Invalid Private Key, please check again. Private Key should be 64-character long.', + ); + return process.exit(1); } return new Wallet(privKey); }; diff --git a/packages/claim-cli/src/utils/get-prompts.ts b/packages/claim-cli/src/utils/get-prompts.ts new file mode 100644 index 0000000..d06252b --- /dev/null +++ b/packages/claim-cli/src/utils/get-prompts.ts @@ -0,0 +1,14 @@ +// Sets of helper function to be enable the use of `sinon.stub` + +import { input, password } from '@inquirer/prompts'; + +export async function getInput(inputConfig: { + message: string; + default?: string; +}): Promise { + return input(inputConfig); +} + +export async function getPassword(inputConfig: { message: string }): Promise { + return password(inputConfig); +} diff --git a/packages/claim-cli/src/utils/network.ts b/packages/claim-cli/src/utils/network.ts index 1640ad7..2613a3c 100644 --- a/packages/claim-cli/src/utils/network.ts +++ b/packages/claim-cli/src/utils/network.ts @@ -20,4 +20,4 @@ export const Testnet = { l2Claim: '0x3D4190b08E3E30183f5AdE3A116f2534Ee3a4f94', maxFeePerGas: 1002060n, maxPriorityFeePerGas: 1000000n, -} as Network; \ No newline at end of file +} as Network; diff --git a/packages/claim-cli/src/utils/sign-message.ts b/packages/claim-cli/src/utils/sign-message.ts index 683c3c6..a58fe74 100644 --- a/packages/claim-cli/src/utils/sign-message.ts +++ b/packages/claim-cli/src/utils/sign-message.ts @@ -8,7 +8,6 @@ export const signMessage = (hash: string, destinationAddress: string, privKey: B const message = keccak256(abiCoder.encode(['bytes32', 'address'], [hash, destinationAddress])) + BYTES_9; - console.log('signMessage', privKey.toString('hex')); return Buffer.from( tweetnacl.sign.detached(Buffer.from(remove0x(message), 'hex'), privKey), ).toString('hex'); diff --git a/packages/claim-cli/test/utils/get-private-key.test.ts b/packages/claim-cli/test/utils/get-private-key.test.ts new file mode 100644 index 0000000..43f362e --- /dev/null +++ b/packages/claim-cli/test/utils/get-private-key.test.ts @@ -0,0 +1,146 @@ +import * as sinon from 'sinon'; +import * as chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import * as crypto from '@liskhq/lisk-cryptography'; +import { HDNodeWallet, Wallet } from 'ethers'; + +import * as getPrompts from '../../src/utils/get-prompts'; +import { + getETHWalletFromMnemonic, + getETHWalletKeyFromString, + getLSKPrivateKeyFromMnemonic, + getLSKPrivateKeyFromString, +} from '../../src/utils/get-private-key'; + +describe('getPrivateKey', () => { + const { expect } = chai; + chai.use(chaiAsPromised); + + let inputStub: sinon.SinonStub; + let passwordStub: sinon.SinonStub; + let printStub: sinon.SinonStub; + let processExitStub: sinon.SinonStub; + + // Invalid + const badMnemonic = new Array(12).fill('test').join(' '); + const correctMnemonic = 'test test test test test test test test test test test junk'; + const lskPath = "m/44'/134'/0'"; + const ethPath = "m/44'/60'/0'/0/0"; + + beforeEach(() => { + inputStub = sinon.stub(getPrompts, 'getInput'); + passwordStub = sinon.stub(getPrompts, 'getPassword'); + printStub = sinon.stub(console, 'log'); + processExitStub = sinon.stub(process, 'exit'); + }); + + afterEach(() => { + inputStub.restore(); + passwordStub.restore(); + printStub.restore(); + processExitStub.restore(); + }); + + describe('getLSKPrivateKeyFromMnemonic', () => { + it('should throw when mnemonic is not valid', async () => { + inputStub.onCall(0).resolves(badMnemonic); + + await getLSKPrivateKeyFromMnemonic(); + expect(printStub.calledWith('Invalid Mnemonic, please check again.')).to.be.true; + expect(processExitStub.calledWith(1)).to.be.true; + }); + + it('should get valid private key from mnemonic and path', async () => { + inputStub.onCall(0).resolves(correctMnemonic); + inputStub.onCall(1).resolves(lskPath); + + const privateKey = await getLSKPrivateKeyFromMnemonic(); + expect(privateKey).to.be.deep.eq( + await crypto.ed.getPrivateKeyFromPhraseAndPath(correctMnemonic, lskPath), + ); + }); + }); + + describe('getLSKPrivateKeyFromString', () => { + const validPrivateKeyString = new Array(128).fill('f').join(''); + + it('should throw when private key has invalid format', async () => { + inputStub.onCall(0).resolves(validPrivateKeyString + 'f'); + await getLSKPrivateKeyFromString(); + + expect( + printStub.calledWith( + 'Invalid Private Key, please check again. Private Key should be 128-character long.', + ), + ).to.be.true; + expect(processExitStub.calledWith(1)).to.be.true; + }); + + it('should get valid private key with 0x prefix', async () => { + inputStub.onCall(0).resolves('0x' + validPrivateKeyString); + + const privateKey = await getLSKPrivateKeyFromString(); + expect(privateKey).to.be.deep.eq(Buffer.from(validPrivateKeyString, 'hex')); + }); + + it('should get valid private key without 0x', async () => { + inputStub.onCall(0).resolves(validPrivateKeyString); + + const privateKey = await getLSKPrivateKeyFromString(); + expect(privateKey).to.be.deep.eq(Buffer.from(validPrivateKeyString, 'hex')); + }); + }); + + describe('getETHWalletFromMnemonic', () => { + it('should throw when mnemonic is not valid', async () => { + inputStub.onCall(0).resolves(badMnemonic); + + await getETHWalletFromMnemonic(); + expect(printStub.calledWith('Invalid Mnemonic, please check again.')).to.be.true; + expect(processExitStub.calledWith(1)).to.be.true; + }); + + it('should get valid private key from mnemonic, passphrase and path', async () => { + const passphrase = 'foobar'; + + inputStub.onCall(0).resolves(correctMnemonic); + passwordStub.onCall(0).resolves(passphrase); + inputStub.onCall(1).resolves(ethPath); + + const wallet = await getETHWalletFromMnemonic(); + + // Verifiable at https://iancoleman.io/bip39 + expect(wallet).to.be.deep.eq(HDNodeWallet.fromPhrase(correctMnemonic, passphrase, ethPath)); + }); + }); + + describe('getETHWalletKeyFromString', () => { + const validPrivateKeyString = new Array(64).fill('e').join(''); + + it('should throw when private key has invalid format', async () => { + inputStub.onCall(0).resolves(validPrivateKeyString + 'f'); + await getETHWalletKeyFromString(); + + expect( + printStub.calledWith( + 'Invalid Private Key, please check again. Private Key should be 64-character long.', + ), + ).to.be.true; + expect(processExitStub.calledWith(1)).to.be.true; + }); + + it('should get valid private key with 0x prefix', async () => { + inputStub.onCall(0).resolves('0x' + validPrivateKeyString); + + const wallet = await getETHWalletKeyFromString(); + expect(wallet).to.be.deep.eq(new Wallet(validPrivateKeyString)); + }); + + it('should get valid private key without 0x', async () => { + inputStub.onCall(0).resolves(validPrivateKeyString); + + const wallet = await getETHWalletKeyFromString(); + expect(wallet).to.be.deep.eq(new Wallet(validPrivateKeyString)); + }); + }); +}); diff --git a/packages/claim-cli/test/utils/sign-message.test.ts b/packages/claim-cli/test/utils/sign-message.test.ts new file mode 100644 index 0000000..30d5e20 --- /dev/null +++ b/packages/claim-cli/test/utils/sign-message.test.ts @@ -0,0 +1,26 @@ +import * as tweetnacl from 'tweetnacl'; +import { AbiCoder, keccak256 } from 'ethers'; + +import { append0x, BYTES_9 } from '../../src/utils'; +import { signMessage } from '../../src/utils/sign-message'; + +const abiCoder = new AbiCoder(); + +describe('signMessage', () => { + it('should sign message and able to verify signer', () => { + const hash = append0x(new Array(64).fill('e').join('')); + const destinationAddress = append0x(new Array(40).fill('e').join('')); + const { publicKey, secretKey: privateKey } = tweetnacl.sign.keyPair(); + + const message = + keccak256(abiCoder.encode(['bytes32', 'address'], [hash, destinationAddress])) + BYTES_9; + + const signedMessage = signMessage(hash, destinationAddress, Buffer.from(privateKey)); + + tweetnacl.sign.detached.verify( + Buffer.from(message, 'hex'), + Buffer.from(signedMessage, 'hex'), + publicKey, + ); + }); +}); From 484134e26878dcfb5e9a3a31cd0f068d8bfad209 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Wed, 26 Jun 2024 13:21:48 +0200 Subject: [PATCH 05/23] Update Claim CLI Readme --- documentation/Claim_CLI/check_eligibility.png | Bin 0 -> 30546 bytes documentation/Claim_CLI/cli_screenshot.png | Bin 0 -> 59163 bytes documentation/Claim_CLI/submit_a_claim.png | Bin 0 -> 123709 bytes documentation/Claim_CLI/submit_multisig.png | Bin 0 -> 62023 bytes packages/claim-cli/README.md | 170 ++---------------- 5 files changed, 18 insertions(+), 152 deletions(-) create mode 100644 documentation/Claim_CLI/check_eligibility.png create mode 100644 documentation/Claim_CLI/cli_screenshot.png create mode 100644 documentation/Claim_CLI/submit_a_claim.png create mode 100644 documentation/Claim_CLI/submit_multisig.png diff --git a/documentation/Claim_CLI/check_eligibility.png b/documentation/Claim_CLI/check_eligibility.png new file mode 100644 index 0000000000000000000000000000000000000000..0458a2a2d73d7c6d3b7982f5754796028385c0ca GIT binary patch literal 30546 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 literal 0 HcmV?d00001 diff --git a/documentation/Claim_CLI/cli_screenshot.png b/documentation/Claim_CLI/cli_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..28be4a7a951a36a8622621f6c0703ab7682147ea GIT binary patch literal 59163 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 literal 0 HcmV?d00001 diff --git a/documentation/Claim_CLI/submit_multisig.png b/documentation/Claim_CLI/submit_multisig.png new file mode 100644 index 0000000000000000000000000000000000000000..555c885d2af2373d8faa3a7737580e68ca10a1e7 GIT binary patch literal 62023 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

  • `?x@5AlG@1 z4;AWkg1w$%oz;4S4I^UBxR~b;bb%JN%2GT)pN&BTovG#i8huEJ?sE9{%IMZ(Whge) z@4jzKaF@8+zuS>SRPLz-&{b=ON{t&i+OEel+XG_!NWy6ZTb<#&2<6*kq|lJAETN z`l}mvC~P4pw=%-Q3h-jY1+fL~&A8{5K=2X1u&Em7W>&k{1f@IwB%A&I=9iAO?0Pfg zX>zM0`UMm`2k=jnO|V^MERkpn=K|2gyw?Tz-CpQnTyx~LRB~pRFK+^c`Ik=pKN8Uw ziKxx2DoRPuzOW7)W24t{$n;ZO5 zhBXc;Dg1ixDQK^!sKau#!GBLEzPyp(-4lP63LSJ#$mp}eOG>2!k;*L4zwm}YOk4$P zc#rBoWTxyts_P~z2Ix-c4{`18j5xH$rRH;^i#LfxGnZ?}5gt8^5)N}k=JNa=*#T=Z zb@5w*0CET^-Bs=dO}7+OJcQw+^Fm3POu+_X3k91VT_WekaA!mTUU2i6t|$T6V@)PN zz6!icy_c5ap0)Kp;C$nNIZ%0-J)8JTFiS#<979shde+-sl_wSjB5A_%kCwS4jU-*T z{O(LxBKrc^eRbQ*!#9SgqeQCdJ!*);(g7`MSUfKj^%aOhN$h6`C>c)DV_f54bDHF= zC;EjKD7X=`P~)DA2VkK<&XGnz!%i zr?mSSEKp-yLwbd1g`!ZPW$HjJL-F!f@1XbzgQ;9ql19*>O9A{SlZ~?J#a7vW;;j=x zr%BlLzf~X!&GyaxCwT3K!~A37on`TSw=?Ys=h!8w$d}9#cD5KaMeprFeLhl4h(f%= zjzL|S&-#>FHixpt8_N!YOrPkG0y(i;HxgZV5_sW*95JnaJr4)Z_*W?kIMo7eTi{KO?5*r(Q8}Z6j7o4f!!}Bk?(x zFM=O|q-4vUo9InIA5y>AaB5#`*+=-C<#(2E=UdxDG9e&N_L4zAJ>^!bKbefoV!H!n zba|-l{B*xv1}C$0(O^^MpW8X<^g!ZmTj3NY-2m;uTx=n;u#(*7!L^ZuNEoNDKBI4LbBI+2**4kYeB zx@X_v0i9;+yl+_XtZUX!TnmbREPCTCoh(kBbuDbqA3|4H7ES+GO=XUXM-;IHcAQLp zW$ry{vjci8&oYMcQ9k?iN-2IMOl1|wz?9|hYyN75GUvA9h$1szh%%qsawUkJcLbyW zjj23cZmx4V)z-!nU<&3%ysMots|sGG3P7a3kri z)jq+qIF!rgy-D9K42s)zd)<@>tO=G;X~ zz=?fSp`z2O>Z_mxMOSY$*aDZH_@Mm@|Hw2WPg0Ry|}Ws%(QW(o%~XDliwCExw##n_BG>(#t$FJX@5)wtOK<+ znwkxx9i3!)0HhV+@IQWIlFaWNW_`sQV#%wSKC_SxArTQI7&2hzW4$VngjfDeD$ihK zqX-5W(|IA-Tip_a0Rr&Jkn=(KZgHQ(aQ|YjfCD>&23HHn#D+unRRRwpSZ)zWP`XLW z`y{~{+kwhnNx(tDphj+d^Zd&LwZ2jkmVQwu&mfwH$0QQ*@D%Y1I453N7*7~mOlbP6 zrSLTXGy<~0+gke8Sib=41%+}d4Y8;iwOI{fa?C3}fmueZ$w-V^S#$n< zijb)20HH#dWzC;umbPdPz}M1h$q!slU}Z79Ej>{0W`$>EkU~rxdv~4qi_)FJh($e> zc6$NXz=!*NnG9wryug>_i~*hPAhYBCH{;H2B-#{&c_vY!xfk@`-_UtuJ6nyH*jEHUTqqhD#&~eWK z9JYYppbn801o-t1V7udeS#szM?lyT2fDGMOHkl-yT=(ZgeIz*X4Z|EpDRjelpyc!V9gEhH@4Mwhwg*q- zqIns<@K3_j(ejiq(;>EvaG=|uhbAmVF+xu@vJwNL{vVR zK(`1I*eHRO1L9GqqopvodfX?sezlflab_ZngtC@k`htzSf*fgBtsUna7c{Bw_upBu z2kw-o$Ma0rc2Y!kV&hsvPpY6Hjr5qg?~tzSCbYhFtj5teP1w{V<#u(Sn@TJRtMA)M zTI^o+r`uJ_WsQAi0jo8tNE_wei0=f{?dM*kTWARihZov)61ZBDe&6S;+d3tNO{{>- z;)a*9>t`nv2EB5#$v)X-!Cyo_D4hEI9k@H7#SK)bRNQ6vTv+<6D>6LJ00fba&;ey1fnxuyRtN+|fNJtV z=%pU0PIJkh0I zoiEt7*22#-BY~#5D#ZIMRankpbWK%ee9)=%8HG^(IREW$C>wyTJ+pS?HR5~3WY-HV zHX)a81JGI*e1yYlc<;wr#%=?*rDH;5s)IHj{3 z^w@SKDYG1~8zm|g@Ez{FrTBG{J`LEHgf>UkraAzLQ59X~N(<>$oWn+y)@lM~P4)Xf zwUwa{)22I>n9R6TghQOB>U6h!o2DV%cN>(*eH~@DoH9carET=C5(!(OOc4`mT9AuZADqIKs9h@+3+me(m&QEtB8-c zM{b-#C+r0gx|u1m6@G;V)tLW34>|%(y^5AIZ52|Ooir@w7Jts{hF@t> zJtU~w!zk}nBZ7s&^^hb*RuP(;)b677RyDm%tWP-SJblYj37@-8@*Tu+36Z z8V3s4Dq{3?n%Otxnb{+0CA8I>%$UU28fguyg~F(rQ1S`)>^)!{f2p-mp#4BAO!XuX zpkc)RXDUTk=Gu_G`82L_bfrztgogzS-7Uq>@C(B6Sof)8P^^4TlBxzb10n5RgQtC6 z{PIr0bvOf@qQ~!gQx4=&plhcnB^_V^M-$;P=txQ*bg1YYlb+_$1OWdouii(z=ik8{ z(l;sf%}26x$Z>*Uiz0)@?FpT=gmhT^$;>~+=dP-8q6nH8_TImRF{;YsyFXQP+whCfS*aBC!(`R7es!R1wJI_!-t*n!iq~9 zTaHxq+A_*-saSU&-S+3d067J_#}E5X#HUR%c%F!RG1DWSsf-Lz(4>wWEZ2!|6R0ZV z8|ljZ=u0P$el9uXzf`mXd=J4wCmbBlz=m->^gJLDjULgD>}w|^KZ-I0XMp;3V$_coXClv+vtrQ7m303Xu+@~f8 zWhpv9sew2jwo=77yq;keH<1ILP#^gj(I$HI7ONZU5ViHyjJgXqe>YT2<2I9&A%2Q- zWI-t85NWyg6WP}oT z$S{2DVGBF!8n5h&-q6-%+wG5>9mrkfi_CwIbY_r^^)opyWOT0Z9h~VDoq6es!z_D@65e_J zjKni=pP!xI7h(k~>LpQO#!j>0-&Z&|^#sZg-I4d1mR?L3wVLn`6)keTt18kFY;t;$ z<1st;J_w?izJ_#iL~U;EQ(?5@RH)85!-N-H;cRA?L|Vzr#ecw36yceXR5x9EBPM#h88D-RsVhe(P!VYQF;u0nv) z6?6M5Kw~J6H$v!$J@zw1yuFd_vit_=k@J;3!X8_>{SVnn7A)e^Wxc-DJKOgPv@>6Z zO6=^6yEH!M^fjr?eZgy#FP*ia`-4dC7$i$v_2E$GcMWy`x>pk~Hj_RRwL)fSXD>ew z%Rp%!Jhs2ghOq$tf!e|+c$aWlbnZLpwDQX5$rI)RTdzzAF`~*&DP(}-$5+wDS_P_0 zt{0rPy0{YRNf$|HAThp*M@BD*MRiY6pO1(LO9zU`aL}!#`2=bVigZ}o%m_TeS%ZPK zM;llr^vr&2ZqLEtX3N=Www{l)7!jxuY+B>!kZM(d{x>zb`en4~Solq4XO1IjL|+A? zm)zJ#mlkrDB!SY>G@6U&QIW6aM2Ev z$aN{(oJMQzkugoD`$Syfg;i+Z5p^07p?^ztX4m;^rVH>4U;XR=dY9pO=kx;LH;qMz zK&`IPogK*u0=g~TnCvz?uMsg()))|aDgw?psdiyp4G9>Boams6;*6-p8qz!Ui~2fEYHOZJNb+n>XqEDoC@@-AfmrVE=;^FYxx>1=evR5$V2@$$P%GZY3xYSJ~TwC zzyz+)b}BGZE`=FddadJBC)_}k|9Zh^h_~gZO{NlVj;ru^dL)bEXyxl-gBb(p{5W}sVfKg#`;mdl0j5f?nK>DdXx@vPJG!EAV;SY38rsnDRC#HK#)x{dbZTJxt>r0;p0)d)Cs;dhigJea zOAFFuQ`5kdNt$HaB7e;nCd9C$UcPU~Um1Yt0G@@p(_ z3F!;2462A#()$IkEKYfqMRZT24^X6$SEy9!$Y>iQ`Km}3f3R%mexJE{MwIAI2_P1R zbGxi?We_W!^k>63P1FyRp%xf@*oZjbx^!<2f$#IB<$h>D%Njt1NiiG=4pi+#MUHou zb@@gtmZXsyTOLJ3cM>svXY)A$W0Db;$1x8eQ7`?Yhhwbrc%n8_RCF7{MWt>ha$cLr zR3&mU5r7-3{9%za9pAbA5oK80)i4|kT42Na51Q$IMxO0w%xC7(`p~=hDkAS=pxq*P$+6Mhjx|7PBd6)oSsYJoz)h<3O;mtc7!1 zoQ0J=yzMW$9G0$~g{xV*!Y1ckB6ns`lsT+CEIkb=V_?uJ@xWsjWdI2XMJEHGYt4iSZJ|Eutsux~WkDQxe1xGryEqd`z5@s(6mE@vS?g9rw! zv_bCG!T203vR_b089M$Z2*}No757=Oqm(YgC=Xko>2Oyb^(U@)`rzBT=uF=)(ZCIY zg@h2%ga-@qMScHs;Fz;pPUpM{e-U>?W^qc$W%Pb(t1oQ;yw|){VBy{$NrK%h6vjw* z2AU5Np-Mijf-gPBNf>-V+QCHRZNu#>SyT~4=!u0h9M;djhkEs{t9Z2oUBxtlqz59+ z>N|6iNx{E*983A=U5mVJD2;jZTPl45q9)wDZE;gX+oo;XE#Y#J=Px~x z5@d@!I^p#~$ih>dc)XnZTvwNZpCUMq$*xqirL$Qg7;Iy^h@*TRfg&l$ojRxV83sS4 z+Iok2uGo|RUdhseFiy5!X(0u7q@F6s@U%SA6>0l+hscHi;I=k4Vtq>u`m{}S$6g^V zqMpaB3h0+P0mko*&B1+^Mt~UhwkqbHkm8Q_y9&ENjvO ztFZR;GEi=bWqV1U4CQtMw}k65A+Z!t+Y%b%xIK7@$TeI{u-vGo2$&t86m0op4-WTi zyAJmp1f~Q8mDcKKCmfZx&nrhz#g ze}X1PqPF2y!^pE5ruN1K#{XIy*}borovi80p+rS8p{g+hg<73r%Brr$yftux zho|jgvIlQ`H!byf+G>dKxp%11Cc><_r%ozYJEIia>9LeP*A|=D?!T8x73XHi`pKHr zPjpq+ziIc&qr`buW$1948poNg$lcY7aO>e8wsq4qb$erX35If<-8b-{>bk2dd$`gbGWEIsip5i`1SCM zEiqkE{12x9$miY{QKBsO**!|H0(R^K7D$RKJGm;49JkAfImq#E_oLVMfPKIxL*m;^ zngU5{Uyl^|T}NrOb`Be+WzEMsDU{DMBnf_|nWVDGA7k-Ruq4qb{D$rRkBo+(vd!}($lP-Gan6=KeVvvR&JNX+@cV8Z03`EC zW)Vt-ACJlTU));h3%sCtq!_sC`YY+we@FL;_G@b3yBvzHlRfMJ(+#U_By80LJK=*p zEi)@Q5g#=9nlCQ0fXp(RUgZ=L{t_))AG=R}hFe+p9U&n6eF8#!a6};E#V0tS&)bQK zjG<3~pQ>e8hnO@OgkT+ks?~IuBz3Ff{pEan=g6$sr57r+6+2C4MPvmg)el91{BDx&q!=DVhE;zOs>N`#K z9i)CbS9>N@K~mUj_=ALd>A*(t*(o;c#5CLD4xT=6)o%f`{dKCOhwG=;6vnpZM>m3Z z*BaOF1}Xs;gP<>SXKVT|atWB!M$BLQblLC(trO|%XGRh4Z#fNo`FwmuApf`j_6wk2 z&9G2tx3x~xy9 z(^=L~mbN-srxaMg{99SOdFDb?cT1>pZ!R)ADU%C^(N0X(`3=GnecyJS#w+Z;;yG8i zjAYd~-*na9uSc>f^mWGA@@#%2W!mh2(AoquMnsn`x%ZP{Z>`J+!c;|BGl&4x2H@9# zHku3j7KbP7>tu=>X@5p(5`8Otzmmz}UMYo?3l4G{&LkOWTo@R+?d%!hJo(J?=GV;M zCg{yGa|`UqJ>j1}zFL7mPTBJ1KKpdfEYhW?K1e?sZXb)Y=>&5gWHB9pvIi;e6aDi9 z5cr-c^VRo`0Y=%JUK5Y4a1}f?*S9sZRU}q%ZO7G0?Ie9||FfshT3+C=y?Ke32T$9* z$nDAPvD!;Y4za$eNK|bM*GsC0 zt>fwOSbL0t|EClI(SS3_?>|>l3!mjJ9^Y_D)o!};{V`8zoJgK_P~%Z8iN_op$@M?8 zE2qJ~zIpw({E#@Y5|Q{)NGsPN)c{vAcTz*v$P#tm+jCQVwZ?5dQY(?`&%-CkuSwK@ zHj$KZBceIWq^^|biBqPaiJJaP{=A!~JBMK&J38E71wv+Rp-%qTuk=^) zF32M}0k5eRG1-vrMFeE7QGnQO0OYPJHLgSIcT%Vy>z5kYOb2eoWe8jl-(V~^Z5*lr zkV2wKR$qrI*_$nR7hah|Px!3yU72hqLR@FGxu~$v##EHct4dAL^M>{SX&pzTTEdme(Rsuf11bZ*CfoHmG$T9*@kGgRX~|9%wYzs?=) z^bVcFyozUk3AQ4LLQ2R1xU-s`Yh0!k>9e7%@*{|r zz5C0cdo};~s8LI(!+C{2fnZlk$PE#uwCFIU^uXxv3M~sx^EP&FLzucpFn_aIv;M1Gl z9plG9_PCS9Cv*AW16bf!2#|wX+p-4yuoTF2JJ^sT@g_Bzqbj0b9?uWRoPc;v&zs^gIh&yiWpYFg<14n`yY+I9ss?n_yr`O^_T2J%3Pp5j; zhO1Dl&c%;-!?tFC{()j8W8DcPDLpvo3J22U_)n zVkfgltSJzh*KGM(`|s$@sy+qsyJVB27}#+&Km(~)3ik!F?_|Hp=E2qj_B^nk))d+U zc*LEiTP7*gcER99{reQIIF7Y%U`U5YBehz_V^9k571b1v=U!?AA9CuO{u%WvT zTlwoqL_x$;g)o;V)~-b?x{1S2Q;rEKtks3Ldr_A2FJM#iopc25HlTtIx|_g}kJSxJ zCupg_#}s2|%@m00HUj+ujQKn}ayPmfUjiHhBW2C=a+&PNv&Lou3p*nqEadA&r0VqD z@4L2YlHXSf%XIWBU?C%NKpEiMJH`XSxdbGY5iBP)9EM3lUx0&;+IYzE@ou|Yy~{$9 z+C_sG4ZQX7C>P)ISBr~1z=AP-{5JR^MKPTsB}A&;C-jvnd`WsOqMxTJNF-`+CX8}I ztjr$7W+MYrXUtG%k;EE3JFGZ*{I#um$@m(p8$Wp$4?v0@%%u4v3S{^Z2XAD|c2UH8 z9{cRV2bp_P!|1BM3&f)Ki(%=#u$J3b@8UsJtgUV{Dp{zO7 zV@DX=(d@NB2RueO2?Kkt5%jhR?v^Qn$QqRG(I+E( zfLkQq8W72}83E|pwr;}x5tH{rsxaJ}bVr-1Ce`El^U5UH_}l?7(m1;?ocl4-mbR=P zS2N4#D?~86>91!6tf9Qe?DI$Cq^8opIQAUKHL(Jt#|PgoFBEPArNiy9Be7fu*Diak zRXYy%@#_`MA%8Z2WB%ga1rc*BFyvNR^8F?m7UF)Z*W^C^AU-?Myt6!vZMMDg)@^^a zt`V2mdbndO#jFR6CDZ5bQ+|s)0j77wzy#j%$JdVnRtr-bmR>xX5b-+L=JAN%n1GQ< zGIxnEUICbFLPUZH-srvRj*W3N0g>D|7P+D3+NL92|4n8wvG6Rg{lOaiiuQ6>frSX- zZI$|jSQ^3x@A3Fw`9All+~~;imf#zwCl7d7gx;8l%jH=sFm2^r#OcgIz@k-xG_oN8 zIX9gyf>mT7wOp#-*+TZCZk=dvwI`w=;z|d8t~Dy>bU4Aj3Xf0M^$&x|j>{;XJgLVA z55ImOKmUxB|8Be2p5u>FU{maL^X1fU_iD5q?s>KC^Tt}*l|Bb@64fbk1rTer89y<140?UCV6k(2O00igG!&An!WMW)b!TW!^OBl z++g3(s@!UCRe5DNXFkgml?~l9W0iq-Lk07CwceA7Y*W%z0E%?qKZmgoVAzb4In5!{wFZJ#yEEUmSulmGQ746;N)aL=C$RXfRBdHd38H;BbLPb)4Pm#% z*%s1_g756<>=uxF$(|qq_E9Z_kl*1yTci%_6Xb#J&xer|#n!X-sV<7EHf^pH9_4bP zmqFlZPF}u>FbucY=XlL-#+C7LIjMjHwp z0m)dE%(UfK&!SKP1s`$Jz!6Evlww4d&ORM3xP$&ct!ncDRO!;V0HP^ns58*XB zqS+SG8u;mbe@kO!Ut-|c&Y^vdvcw|#OlqA;6#$g}jPPF3?Uj~?=;TAz^vQp(AzxIk z)l?)oh(4=JX3}QuwWx>l(bQ!d17?zm3ffm zeJ8TSnO}mvS>~Y>wdJ`oGEfS_ialH>!$R(Xl~~l5c>+w5o>kSy%6uVIo!hxIXpg;b zY#_bS(I#T2+|&}7$}Vl(y6fAW39cN8Pzb%RSvV*3u%iO57-c_RTS^`i3M9Ep z^1N~jnQb<#II8ha-Rr7vJ_8+^C!F+1b3tbJN@Pr$D#ukBpxv9=om<1?>m+*6A}AQd zFcB^$B!pR##8sd)$bf*x0r%GAh6dX-Wr@`;>W4PKB!sF@7)r1{Pb`Y+e3uhkdtK=0 z;4t4HNdIe6zH89()Z&|hlie;P!BsnHJK$tcBt;5*1)L3AZB=}^hS*$~B8S)bUrj_q zi%OM;Joi)%MLrn+G+QmwN>_^5?#;)AG0N5(zI$xc);+A_Uat&g+xgm$$3e1IJQwn9 zl6*qXAc?s?Zwv~VhFe3!guaW}Tq%4lG3{W$@(#@^{bRepewBWzvkh-I z?4XZ2u>4AF36!cMe6cdlu%TGj4($9F)zd1m8_UXcv>xLk3GV@9HN6&Ft_Y$WCSZuG zzC$*YBP3x3?H5FRwsS5Z98bAbSrL@Xdy5kt^ZFd7A~s+?8(pCC9lN7lPr1;;ag&tt zR_CGkI(et%h^PvmVgPk4k-4~;PN+a_hPtwH&Boe7Ya-X-qQpMz=~ z96#U&Qro=(Pw{=%cbOY`eIYOFkLUMOJaw9Z=%V5*v z1DOA+xENc(%Z#aZnVxc1vUvNTf2f=K3iv(ByB*?W(AjU+He(4QK#~rhFVJg?#L{mK zYy^&^Ugrw|&WIw2L72eMU)(a$6qZ+9K_2*M3^LiCYbpX}z@dAKZ=0^zQ%pW*=UoN- z@7L5zw;-9Nd*mMZd83Y^QbDcmykl}b$jz9cDsOsI_;@YfahSBCIHm8BeUE;9Fn3e8 zord7>=a?r5yX3rJX2n~zz<%P+0L6Tp@P?_O=_P5|0NlCBegb96m;U`#)kV1+7KSN- z;;?l5bcmAJA_?yFIv<FwHga@COb$qdz!WT|*l&Kh{w*1)X0Qy}O@%l&4 z1sri3B=<4fnqVogT9ZY8Fmf=A(`vKCP+IyXY?zU5oxEDXiRAc}K8>8eeCm8~tr^Hn zA`C9Yrwqk_M-dUJg-(Q8?yOI=Y;O$U1TCWGdbxKD5|J!FyvATB26e8AiORWSI_M2#lZO0UT{FU zbY;L`Bv-kz5g1e9>{DiM-x)v}&OY4?HmkZHml1}2v@w!CF#FLaOTNji`Gd%K(VPaFP?)*E|2%3oYI2f!5d5hpI4@ACe-tbUWuR z0S1BhAm(6jV)1gICXy`%f0id+Rtm_e0N@NT&&~)4|FsZ>;gig zuLW(o`3I3GD8!M4&!pWzvW%%O`Z%OA7)+cc&NEP0I(#mmeoDe4!6bzsq=XuR2wuiG z7_9M4zWnGs#`S}yJa!Z8b*7q^S-BVkZ1_$%^-UM5Fv5V&>aX;^dYs6yAnH25*W+yV zGnyB$I|sv&Hd7#e3$l;?_wqCu^p(*vU@fSMsC=o^n*pNEE&V|F%7D5cL4HdxKWG7l zn=~rpZy(Oa%k{Aka@3D{1$4o92B2el)^fz6Z~lQv_mQE$Jt0xA2&@Yae7n067V0ahXaK)WpG}OF2F2|pS$POJo?H9Rg<;$_-c+g) zG>fk-`F&e;4@B6Yoj8jgAZ_V`ZYT3xW2pzim0(8BboB4NC{YirfcfD9hdf^i|Ts{>a?6(75iw!n=FG zl%Eu_W6oKAkrdwNg`6gnthM>LtEx8w##^WuGYRo3d4lnm)jlDm`ex71-UuZ5tdsCy z9w6K_oP6cR>ofZ;AZ)V6K;TFSKk#o?2=lRo4;3hH5_5gk>e|}7jIR2jOvsCxSiDu# zWuX8gDaf-d4qy_^>mkK>3y|><#;3}`7g*Fg6LMUK!P!h@$yPcn9ggN;dx12OFLytq zyl=(6nJw(H3dgAe3o5i&`%_T7cl`iE)2LHee@&2(oBNu4WEA60^aABEJ>b|)0cw0E z`?NB&GYZ6s7ePJWBBY4_X8}xr(O4Igl3|=JW)IQgudWEUv^6;7O&g(HmPAt8kQ^Nx zj8Pv*x-8U+O6y zT{+(}ft?mv7qoL`&cbp6zpKr3g}_@_hNy}7qq>>@8>`9CwhH`cS>}-9(=ZC7ExP~x zyuDrX(PlWgS$m5i>K%X1=Ls5hFmvbPZ>;hLd9QVF+PSlD5>x@gRM0=G063#>UGEa{ zdPeD}iGseKp%fDQ+3(hpoh^$8;??WqXWvc-pAi!ca)@*GEf*7KTxcOo zS0=@L;oErwyn)2%+OjP-ItOMGXpzn*$6p!9HC`{9#Re0~1kfF$oLTVLf}y1oib z<*noh_QD!@n`cIel|oKGNd}7bj<(nJC$(*HhabmjdL-%&I+n+}(%waAB|86W=h|G3 zqE1Br>;1EZjC~B2cj^pO2Cjp&!*?p~!FNO zK6#UoaghH8U7?HC!7|;x!uyIro$3A0>i3_I@lHr$wHD|3}z+N5l2C{liHlLJ*?`K^VQuXwe0uMv3U1(WCcHL>oP# zGe|^=)y$ry}ifxx$md`)_VW77H0O@dtcY}X~!19d8*)6+)B+IyO}r2 zjdapxqHRB$0HE~x%nLo~&!eMaH_A$}pNFGTMEfK%k1S95A-8|>f3$A@@BiVl(7NL} zA>P5cn5IFNI_ZMW- zyuvlwAC)9AFqeIXDpgx#85ZwdM zv9p85fsZsCO7!e-gjV5dE($=^w4W}%dH#yi@MgFDlO#}FRZ1+vsy3I^tbnSy z^1r$l2Kw8b0%cVG*JwXA(QI;p7MPf~#SK%f`SRFYf%mCQv=Q3j9gWb33Zn zqATauT(e;#yML7X8AijLbMfzV&rm{eN}&Rl-(?TvxQlkN%Hpu)=nC$!^VSF{5m_u_Y+j~8Em+hCs=xWJ$_1IC$>s*)0gS;AbJ&mSS zVF0k}+_kL)p5XsDI?!8)I|d#Ke5HS`NI&1DB1lS^uFNaWR528Ag#qYzRJN7%kLLcs zxk_>WDExGxBL)u5Z+kftWBg}lzecYckzx-vrnZ`%RbV{~B+Oxc_5b%^b7WuX=uh=p z8)Uz+W+r_8YlPw#XV4zsW{KixdtrCE$;nO)+E>gJ_W@zqn+Wje{9hZC&!O zsd96rMpBZ{aNn*g{=F!0j*pQ-7G#W3J` z2j)8Cs+&uUr0sTX4iJV!Kr1l=_?DV>zjbt8V1O^+xf2NxKY1Ieb|Nn)Q{xJ5X>A}R zz?MeDy*%i0nN|?6PTpEmyi>a`-U4oQ^$9;9=Fb%!OD+5>U^t(Ij&HB2!e)g20$@IV z%|-%c12{^6{**81+MkNgD&`hnzoH(1UtOFuJYKu)DQ+VjYV1$8XK!z^lXMG!x>fY* zE&!YYRsebb~x9bO1JBikdC~OWS~~$}(eUk@2~c#U=Bo!ofEp&Q*XxfV0Ld*8~d&?p=%)li39uc&`@qbZx!u7ha;Gh z128zpfKM{QE7ssLL2>O@ehD;^7zvPVyX&Mxo_p!6`PKt|;qy%GIR}*_|C;kGXbq5C z6E9kyB7G6do(7H(FJ8eV|X(VAk0_@nN-7Oz`Kr zK}SK>wb<(Ft}So!zi$Tst?b~rU5WgQAPZpP2Ugn~D15VaiV>&(5djm@e6cNB3^2cV z=D(gg-(ssxR_P;VgMLeZF+^Sg&zqoR-U0!f&lIYupDD?ni! zfV3i%ux)Vn)=3lyaI3pk5m2)A2jqqeaeIJlsMhkbu~@U{<1PCT5I*$ia&ys|D+jXH ztyBzX0)7Mg15=uVaRI&wnr}#KIS_-Xx-|ijoP~S<>~*&(S{)JaS3q1w1)!8L0PL8# zf!k45K#-*Nrua9e3@r#eJQaLs*3=WjRJ#W->K|AM(Hdg32Y>Y>ucpwtn~Tg$KAs znTI}a0XL;x4X{I1rTU3b!*rVOcl(~-sN&J03|)Jefc?zHAcY^K-eaP(-*gS=6Ijp@ zCyQ_*21A7o7ZxC;Pfs2sQ%FjqxcK;PpjkoHyMR-U{_m&J+Tmk109}_S^BG|J*RJu5 zfSp5Mk(?Ql1`sk0Icl2xyEpcM$5wf7_yLrZR(qE(OOkl1hR3dx5@XFIX?GSv(4Jgv z>Rke`!1Z=zZ}OuqIEk+DS0aoL%m}m^)+ikXYWAg+0ds$1z;;yli434`oZ-u3Pw!3J z#fV9W^3G81C?b~wvT6Ra+?)muh!lG&0s3Y@3iLW5yYsb4$5jqTZp$olgj_e>GbVJv+%>)xG?9FlC*?L`9ex;3EO}Y8-x>oUZ*@;CTSh zbL;Ff-cQ^Q`7X7p2kGO6u5QyJwfdcvB^LHF0Px(Ip#Q34hOd9B-A%a|Ks3a3uGgyp z!RSB9JZ~c&=Ky}&+GPM3m8=xY2cq(x-rxlQ22#UhC4Fc7Y{{}9{zh8w5+9SW9K4}p zV4J&R@|MFl-cFVS20)?&YUUbj$Yo5v1|p~K*3$EUnx0%(Tk<>sS6X7I3X1uTq8NnqMqK*DXfPEClo(;B?`ZkdlH$6QFxIqtI145ZFxh#j&h+LN3pI*imVYO8C$+<$fA6FO9G$Red#J>uK(EoGb2+FBA>tB-mvM(IF{!D^!bT4(f?#lX^gcHd) z{h?yB zPuwEuU#%i+r!{zf>o_|oaFAW|V)tI`qXDkPG;Cn!cufr&`2oTi(wsqKQ(2Dc>c$AZ z!hxJE)^Ju1%?a*}Uwy|np1`!B41^B+M<-q>0tde~g&hN;%-SP-LMaa@P-}BlnTL38 z+$nC;X$S(^EXkYG80H`SX6t^LK2d#wIY#69UhzI-#p^LxU`E9=5C z(PVthqE!7$_{D=T6Kru2=r^6R?M|c>!ZloLK}^%^)C6^wf6{m`=oTdVH)0AV1Yzc2 zI^0DQ0BWSG_kNv})=&aMobMFj(HqODu)w=3JoslKLiX6f1QUu8uYkCl62LL;h*Jgl zi^d)>0%0rdWx-ev>&<@DS{vh)RsdPX+o^*|B4qD*0ULtI9-hca*7UHM82UE4APv$Xjsb4ZhMwrN7iC{= zcO8NW*mcccl5@5~DF8u;hlz&z$2SpzXox*|P7p=hH^55j_?25E`V_z8=zawAGKL{X z<5J+4o(Z4>F9r;ZzZaWMzqby}nv&gLBX$elezto%D7!t#uP|Q)#_)F)ie9OARtFPjY%tc`zlXK{eD#XFGo!Tl{#hdr?mS} zO6X5*BBJ{bG+CMNriZ1dtM5F3KD`gkr`fHNHGoo|zrUxNy8Xn`<}MYB{SPwegNLZA zF`inE*r=F(F&4g|*MH6>hXTLx0-U=iDpJ?4$*$z8;6+o>+tAM<_a6&>uWYFFoXU$! zr13xz$;S{0!zt`#Jsl?-^tYkm*|YRKrOPuZ*9uHVqcFxG|z_yKuXniIt&;Bcd!$G;}dZ7W^E zuFWv%=H<4c!q%SEZl29YZdMJqLD3FI^DYhC8Gb*-U$tgDVnZ=1{^EE8rxcqAT9)k9 z-)JoFePsKHbhcX9&Ov?r9Ieu;Q<{@eB1m>>dn|s~&s+KHJ)~>YA+u^++cIC^1;>r6 z7G}k`Q05Utk=G&*G#YfyhNAf72?rdtJiWkFo)mdyC4OMHWlAxamqA0J|8 zctGURIr<2M2sFnEZzE&-a9I$~>s3iG{Rz|;FNUk*fk5)Ar>q9sV)mk;xI~^Ql*6vg zRJv}`)UMs&G0b4<>!pVKSdU`?47u#-rM{Kv=}(@(3jK+9Q|~5R1IAHz(ALd%pSb!W z%u&4b1VSG!tq@hg$YsH_xaMe)+53qvf15&)B2F7_Pn-vJ3uxhbi4Zby_yXw^2a03I z=<(;(PxY}NnBcU{7bT%Opgk|XXhjOKF}o4-gA2S@Pn&OE;1cyAH(CfUTvQB$C?#@@ zW=U?>@*}687;zW{rW(jZ)Y^F-=XjTj`WF%1($y5ys;}7OYJFW4MJ4sI(Wmx7 z9B^lYAK+{mJ5LDcSFH%xs7TG&1n}^&^LyZXf-yTd)n5g%QV^Xdg&D8Vp_&}S%`M;u&iL3>Z zb~5#lcYk!JULg6>V`clzzSIj=>J&s>fr6ngUS;+qQ@}Mrv>q;P` z28cVw5cax}ZyU>L&)R2DioY5(TZ!$BYuGlq#^}7#aawx-tZmPS1Zm})wL(zGh(r2a z^(5tY3i4P1Xo8W@zeEX{)C?Xhp*FHuTsbcH!WWkxBah3~-U)~UwS?9DwaH=l(_Ae2Y@)=I zg@2(NBo)k=!(QytF>daN5lmc1YZk~IFuCYp$;3qn;y|z$Dzk*d+VAEGPdE}a^;9fg z91+_rm_EWpMEZxEi@Wthx-?JN&8MsS?zjKPwlcWgR#TsOmBb1yz1l@Z?H?Z-R=KBb zs^(M>Sg3@G=RtMuxBU*IpqCfgU=Rugb&Qixw;R8e7OKbs)NJ+VnI-MamEYGuDN=T5 zW`Dwl1f%x0Z6R1`W+bmSF4SHf?S$uT#^>XNc5L$LVoQ9=Dz(};88V=p14u#Z>QzJE zgPzjf);LiNcEhLO#-TDC-bmOP%x5)y`gT#rton;zz$nRmphvwskZkh|(eBnm9 z?+>=De=GiG?%wP&bC>8notdBgSDQ-hYOls+nlJQ(ehNkCszDLEB8OIq!!{svMEorl zRI7A@lBiL{0r7e)J#EmLoGuL>%IxMop}7k&HIeJ75=5T? z4v>=Z9Vi?^u9gIH%;vkufYXb9otrpUHkrUO?%!KN;PR@R+GJojb$fW-?hE#fRt_OH zEROA|ZolV0Ws2JC=7gvBJhQ1W=6mI*jCso#))BKz(V&gW%f_&k%>&8l8QJ+-%-lsU zl4qtWrXTI@s|R^C-JM~OR9MoYQMd7ME8osK3b>anx)s0$H-{Q<^~%lb?R5FnjK_@a;@8;+-+wl z5c~UJe)d#|5H`^Vhb~jPZ<`TPmE9W7`tWlN>F*MeoGeieY(zxr#SYTSP+nKfYNC}N z(r}0Z<}_87VfyzObe^4o9`*(oZ@y^4*~$EywDL<<`fqF@1wludm2Aku$W;$9DICaN zUGRCK5(8m_IvPq$7OaVJ9b?=a}_tfdNr-Wc#osptT4!|Du~o% z7;nLp!(g<{i}YauqW|0$_=H?Og*m&&ZoRG?4nlJ9zu&Z&dIUMk{EymJn&*{`jhj;a z_oH}H$S_XotKXZ(CYLu0xP%K=wDnu4&r=e6LyXSd>%n1ZjFh}@;A@hzzzxm!klMNR z?;RU@d{u&v1Zgn3zZW}z?ob|cIE>$l-wXXq2?||k6<%-)aaG;1N+cj*s*xVXAwu10 zQn_#2AzZxUFgz>h!-oIYUZ)4QR*fe0DSDvCQC3OcE7i!8GSP@6G~}kd<1v*{!Fa7@ zuiF~S;KO{5&BA9)y5ul8^YQ06uO`|IsELRgMfrG@LPh`;$(Qq)qip!vBZ!Cpgb#_; z?+@`A8{4rI3K5{?X}1|?L8+E5L!NW5O24|t5ehhJC?4lSRwoPvebldalIQ0@;ABa! z3CIYC6?tq$2$1TvoIj3epd+GJ z3Aq07?gPn+mh~HWaKDwH8g*6Y6SK@-&#w=g+&NI?$rftpf`vq8r9_`pKPM9Uq@-CO zBB%H$xtjnSPSgGjME!*@k|1G(>Pn9s_MNAexi{DLJZzxvnMVD05`$#%j^hdk%CTcA zx0^cSYllaW6c2V3R2%t{0%nkC;8>Sm>9ys>3zoYmnifVf$mw6S8P@b>9^iqEbHI`F69XOl83e=@Jrcm65l@we&VhtGtjFIBo< zuhL=rjS|5&(eH|Z&N4}=6)GJ=Og_%^wOV*d*zflbq^kINyimI>m|k7ne(DMK2zJ8J zbuk`l|Mm37QCZMcTJzZ9*5`<4)A|Lusx{Ivz8it_ zl_u@Be9uO9lv1EZ?t?g8std)bIdluq7S$CRRiSF5l^*;}VP>He1^FU z@GQOQdBJ3oRDIxe$R?k?!rLlxdm0o!w(TpBz#2O`!rwT?a2514TkiN+pUv=z@sdxz zLChfLZWUSP(S$86k)Y-mG+^erF%K{{lBW;lnyYLfVKWF;3iaebIbV`|w(k-PO`Kx8 z|2|D8j~fNjI#i)1BzmZn*vNndvqULQA) zq&NR->w^E<9l&!?zB8mi;;Z^cNaA!Mr1%J0_0yAJlHk`meWX-QFqu^Ad9MVeg*<^x z*O=gQ{>c$;W80{6Xe%kwpA2i{`>4a!gQFisvUpNWd7&H-|3<;LdX*TP0{F z4wk7Zc;w3>=_7DBU(wXbpsXShI&fVhXZq=g=(BBxlk6Shwxw%SlFaNp>5__omPx<|bpCbPew0t$-x&PR_>I{9s`}FfWoAx?KGz8OAQZuLDubuVblM<3sMvx<*4 zq+F!puDdhWg# z)sD7f^N-nhUK|u9!u9-yy|3i7pQ-EgvZ8BN3vJconXNJCvt#mVb%+s*942(oxRBgo zD=4rd+(%I`TIBFZeF`7vhgN37+H#vWDK3|iXv|Y}j`N7%=_hOAl=$@OovVR1Zr>l@ z8#Kf&z8?AgDT`BIPVz6+M)doSza+GN6OI?I(xl;^y!Vj(uCkK-T=%W288SE=cPG2w0XZ^!~iJ#eIs&0jjS ze&vA;e*B~Yj}$f)`jF)JwF3S8$H8Zou!o8}O zku-x>d%0q`+;JVgM**(;qH4#o27*raH@m}`{G~iAlo0?h+EC$HR2qGBB%7&A?7wAa zQao-RUmm-GLrO1qYKS{Q5f9vO6gtz*Zgze6@n?@7>{d@1sc-{MmbU>{TMf&Wm*t7M zoar7i@R1gm%h2y4$Q8q7L`Nr|30-C~!6ZuDI$(mQRHYEOhG@2>Bsg0fke}D@C{Y8x zz@O&Z^F`7WNwdFI%Ey$={7IY3iyB#CE>2+?fh~I(9^zOZd-jxB8rN%(C*9A1IVtEY z$7D_e=`+%7X|WPpAW(EQ!?ZQR;qZFysHLmf341{NWX?cPw;aW->u~@9Hg7SiV4$xm z`(SB5gqAC7MU({J5N>t{^5)C9Z58A>r8vE5!NE(guX7;p%f?0#DI@K!&aaU+PXd;7 zWgXOH{Y~9nU#pUa%{sxk5l#@(=b*Ii*Uw)kjyoC+ypz2@r3t~Qq+xn@4-0=#j_0hN zAztY7O@4m7^>5WsyY&micE|eeD{z6|OyVe!U;`O!GkTaP}h z;WA3)dfZs7q4ugFd!ZBw@fG56_g21db{r$BwoYSgMlNaGbWwQ+PcaGydT3@82v6I< z;m2Fbz4jfn1kKK^SG2wf1pCy&#USMT^)Jsn#OB4PBRvDXI%W^B92?QjYb@=M2;XIj z{c9H^qrO^sLxcQZjn;=*HfmTj=`VBnN;Jq1m`PxVMHpKIAB%RO(6(=Y??ESjSlym8 zmk`A8z;S-!pV19J#TXYdS}`;X9ONwO{1%H~Wp{2RS7L3UMur91Ctt5{nISokFexXW zuByl!CQjYy!JVSddHo|<=f-hj3i^$N;XnYLMuL4 zQtQUKRN5P&@eO3n3yEB)i-#J6rtUx~kTy7~V3hZKMIsj;H=$@k6ZdgAu8n>R`s;Rt|SL@;tHtsHcO z)h26nw{_Md2#S-*AfU^Iq(~_7sG`}k`5xbZ;<9m6^J8#$Jl&vPWTlWbrN(x;khP`H zGQzyk6n_yhdt$FYQS|KhsI9Z=^G#{|%sSzezv1bmu$dQ1nji@&yY_suspzoyX3wOdb=ds z=8eojvStfX7$0+dWcd{+zDpsAUugB#ods@a`ZdFyC+4@*azT_ZPp*pl){!he4*$Ig zX#0Q#Jf)IGPIh7Ca}Gs?M@05V`Sbbx9&Bm{`07ajQ$U7*M?@GYtiFrRB$8#Qv|zNE zq@E0BddE1&Uz5O3g$O$%!4^9X45g?oKC^%K4gX>_FSIPZ;Qx6i*||{OnAzE;r`blp^l#$T5c+12AZ1iE25nt-?LG{ zNsD{i--|U<4pwUgv46y_se1oqrSOz|r|aC6S+O%S9(IL8 zzR@Cu8j#66Vtz>DnFT31V0@lw zH>aY3#$>YlsX@Q+qru3nnxj{TMhL2z7iKr*T1Q3pWSG@+--)U*SaWj^sGHH09kE~9 z)I98T&KZ|})dZMl*KaDV7ZB$D)WI*}3bzmsUhT1U^W$8M9X|luLWVvp=WLz~cb4Np zLX&O*y7ir+=Ni)Z7Xp=;mvav?h?Oita0{aL;B2YLewoJLrmssf*$^kvxkwK1T7Jm;EmU1X>Ha|cG4}GqHT}> zaHncir5eD`*TYpaYy9kHGc_q;j69Z%y52@9L)`O6Y%f_(b7fuh=f&WM*RPNLXU%{T zgi+-+OW})nOYIs%BbVtRbRZ(op6NWudrt8N zXbtfToTvSEA?_4D8btSSAiS_}b$F4y6Gcpnj_&{#AEIbPl8+0;T8A>_zq3eze`#{# z#;YouO#0x@1p=*67kEV%mR1D*Hu8>_m$*K%6%hr(N`IEiZFz@+8d54=D3nXEpY-=F z$7hV+D$X7nLg&>)5K6!)?v?o%L^1(5NCK1j-qkLjbh)(pI2d?rCRXJ*&#t|i6OO*m z-%9GklG)xmP;r(D`b2F&GsKI{yXP-fC>q>-fWGrlUBU3f6&TAY7el+(YpPEx_$sxn zHCuP*z*jK)fX?x+yz1kNW&DMi4+RdHgJp5uTtu01(TT0X4dnRtmVZ3n2MT(7ie+3? z3rzeJ0jx#q^e|lVC`Kdc%kwMM--S9Dg<8X#zrg?kEwnz^DF0Q>M~@o{E5i%s@>@9 zEFL1Urp~Yawj(TvEW1N|FIK{>8na-Jp4&W)Q7 z+V~JN$FGL>)BREd)+dcLkT5J7B)s%+TI*_w+rLfw2) zrZnqAPNspoYk4dr^&)J3E*cUBx)4kcPKgs^>@znfP90`|ce^ChtQs#nEqv9gYi}01 zneJpvgltO3T$ALjULR#TP`yw_DdR#GB@Z%f+}<~^d})8FDUrqwJPs59#4X}2e~G^6 zx%w+X#)x@_z}cZ({?Q#*MWFTMa?XGLAOS>~V^EW5jk0fVXs>Fms(xlum=jZWw$jNV zE^OZ~2nKTW=DRQE`8?QzuQb|rwtrmE8p&xRnx8`|_uub-Yl<^)ZBY>?si3{QsBz-n z5OavGoXIeV5sq*~cLNZor60B4c97{?MX&>YEU4o-4Iwt!LrU{@Y93Q4u}t2A0L^;L z+wkh4jzu!*nBOoWo@cwcmWX0u^=ad92k98{_pEOvGYUYab*y(*tR-&FhIBfE__PE= zDX_nir)@pgOfK0Y&ghLF!Hd}&xJ%{MsQd?E_htxAg!@Ofn{CFbmfrZ9I*h(>9at_G zV{9e|o`L6^8+v$P6l&(tc->}YAA8~R<+-Cr5au`EjV7#a_jNLVaBHju zstEHrj?U0dk6h~q@dEK2rYw_^4c3hwoecJbg842akWZs7Uw10xr}U5B33s)w@{Jdt z=!~v3LW5P~(|-XJg{+YeI4GXFHE-+QP}P|WW>Wo4oG$+R<}XpOg{_n;8Q%Ev#DKA^A0 z%Am8tA=n0K?ze|_p3AmAe;pKe@+^`!Igs$!>4HRj;wXMg-{eei`3kvV*mW&=F*T$u zRgg`7WSt*aqt=#wDSPj@5z8$noAP>Je4n;7Gi}3dzP0>r1mV(dV@)a1ugUtQ z!;SI~IDR~bfghy%e-n`r=V$YuRP1ZU9ZpKiZ7jgy%82S|s0`_RqW#bt!URLbh! z$P0hc;BP5D#~QcgRWLho}V_}Z@Z~%vmPyFY4KB~ zkV&CG;PT_npr5Mn!S{n+{I@RRD}y)ZW;#i~fST_64|U{iNr5^8^GXa>S^w}KUQM>-~=Jc8D`VMo}jXazDK8X*~M z$-DG(OB59X`0fgQDW|oS(2=m#FxK^I@JuFiBI>nUPq{D9X}{7904wf8bDh~d5< z540~Yv`b)b>6oT7q?2+d?5|(>%$+-~_Y|R0n&bi=Uj*HZ#MYVEP|7q*HUXt^HroU( z1Hg-P+}X@|6~N44X%^*qphQMRUR_@{)VnYFwmG(&JcFpcO2Tk0_=rg-cCWXH#B#W5 zUkFoAWqTzZs`Zo!F>%B!r18=^)8rl>81?c~+1Bpft^WG1+QqwrXI7{oLIfKGgf4GeAQ1(2TeCzHDy^JBAC{Sfb~*O$ z5=DM@iZLPA_ILE@-|BtA_}q|ozeBe`)9dhaOm13cq)lgpPwU->nixCE7L6+!CWe6~ z44;HN)9C|Zk}*z(cVm6g2WB{j$9nxG>$B}FR+KqZvduMKEY2#UB zvLIVwj)0(^89UIqEPrOHl4bu=P@T5tEG@>ki&3<@yU4Xx-dU=P1H*CH;C*S z?Gdkx;Cp5EbUeYxBvT%`c`tt{w>XNc&n-A2xi1Xk6$rKPChLuD;<^N49KIz}1uU3^ zzi)OIgFJ`j{qz6;j;|QY)v)bG9Q*SR6wER`=lU({=-gDGZ@<^hN~ z=xeTz7~gLKwU2>`|LQk>fI1*uXTPiaaw)M~kOHqZ@QXp_{p6}bk5$|3oBq}?QZn{M z0n%y)iXUf&kyhV%sPfane89Lzs~~hGfCP7mC!`-3hD#ef|Fp+yl4y-HubPM>t@R_1 z4Q0do;*{UNp#5P>l1uqPY#nzb&f+J`3s)}j9n1YtCwYcu`1ncs8=Lq3T$p$9=7c6u z%AhXC7Ua;2RYWr9CX(8fMH+wJh#CXD+{fh1r%?mJC)GWpAOXZ}?AhEYHGh4~$?Lg( zWw0Z261!m?FfWj53*S(GBsOU9+&SRUou$V&!9x}wU$zhjb22E6RZ+uX2~AprcV1k* zcM-Ztt*EtI@J5)!VgAM`J^mF0!7I=d-BdLVy4tN>nf8kCchJd5yVa+4+C)OzS7f0k zulZ8b)}Aa~(=X``I`JCsLSOW2gXMD031lTFs3bXaFv^EzF&_Y)KY%iQRgBGT+}O3H zr0%QXVVjzO7vyl}B=e&0u_#xalMccvvho{0I(NSs(_B4sI2#c#KYH5)@PsL(|7P9e z)oTB|Zb_dZo)!m^vA4-h$@^#=$H3f_p$y-XhB^Cv)hl4Oxsuwr%fb*b3|b3@K9>Ff zzngd>5b5CYv1dtkC4V^#4NZx2a~T|P?*Kr<&cG}gi|OAi1c$>X^amdqlN5?PxPck= z9qWoLBkyNp-%@VW%e+YPP|G)38@FUNs0jbH`o=;3l-`b@4rJI6`D5g1TG+f zaNDwM&+k25R?;uGHo8ROHdSfC&02ksS-|U1mgQoY6nOw}&q-O++K0l-;PxWyVF(8L zk*?!MBeV3KnVx!0lntzUPgHUA_BvQm6fzXnLK49t4yxmfdgHiYtHwIfa9rRe+}<>0 zdzK(t=?}O$zudzpC}dn(5BT^``i2u;OzOF!jr;DH~$E) zPwC4)FY`Dv%2=uF4_@M%;&JI$0b?Vspti@)X*Fxy8Bn?`?0@dZh1`)jq+=XgEcP2K zHr6_uOFDX#OPD)q<4f)%YiJeG@C-xZnQPbbP*C7kYM(=NU-$BOPV=3LrT3L+ zyEx94%(kBOi=Jd1BMoiC0JC(ypbR~TQj)l1N9D=guA0G)-vlxYD20+}{pb;~OCc-h zTR+x`k8?$CG^pT9h&2YVK;QR-rlm13zuC&dg7gq}<{3JWxb(omAH1i22#d*B z9^cu-hJ`(3(3-iHIb*hpe3J&R2a&)O@`Bhgq%SCCVqU&Xj*sm1WkSIhaKLimny<-W zhI~BVyc2xc6b7mXf#5D7IZY`exQj|te_pQsxc0}MI{CHwz(sT`x zZ@2kct3-kD$oWUhnZfII5UN|<1OKYX!65lCT|>ng`dR0y5KY-#!V9qCM+?1hBQB7t zT%+%b{kK&&Cy@cZM21MLLfiNs0{O;5?7#nub(IyClEj%fF;TdW;rdy7y!5V( z#H;D2wBh?ki}Go>U}n(P;5^hj?(E&h#S?$hd%c>W6m5kmve@pCRiKk&6l-}H*$ zF4bQ&Mt)bm0^88di6Y4@>kN4&T8};o^Am;AorKVtUwF|(hCv|*Trw?aT1f+gn?1ID zum}&PbY2DyPI=$~W=1l+?uW!<43b=yMYrPtW_@F~>a+jyjp~gr;UkIVrjwfpUS6lgw1W*~&trYt0hK;r9xtC1)&e@Y zgjM~l|rdUpnWnoty7FP`5@KUSZBDOcU;MIgl91@}r!a$9qNq6OiCfN>b4fBHR1=-?Ti z$!9yv93EKCSmvZ9Ul$L0+cWaF9J8d%Xe;mk^y{O5A$+6a#fr5HTu#<6__ zxlasJpvoojm*;}Hv!hx&(y?aaWwhmzq(&Q0>A4`G6l~c8)pEd9S*w~5$Wijq^wV(> zsVt8bedO|{Hhiy!91(-xhXWd%7xvKw1$~;C_Lj!KTL`p9zUh2ysMa5#MVHIHE*%dE z@vpfz`C4+v>N99CO5uSAArUNNt#()XNctoz&UaqgfHYk9|FqY<(yB)fdB_Isuw zMz7_ST&5ora4W1yuD|BfZ{o;oSu=B-)53RLdHAm<{yt6U_vGmSX4XBEn5VbF87Z(; zVYQr;XxnYeIy!aQAqePxdDJtMt52TCzoJ1$NM0^NrdS*Xgbcz7S<1iT_7yOoW&mTI z7WU__N4|6nb^jfYZ<=>xES z>sA^qD|0>hWnYBlGg1lOC1@tdyonirWawT_9p0N$E6w-o@jN|A0=dQBPuKz8%{}JY z_Tv)aZ^}L>l#QeO7~CoGy{l|IvUVgE^hG~D;Oq*ynEmHP)y?i+n91u--Nf`KFnR_n zy*wk7)PQyxw3R%^XPR$~`+l@Sg0A zf~Zrf69k?l7na=Y?=REdco^qdlC=z+AP(C0)3jdb{o*kn`cW(xw83k=qSDtef3djg zC}IWl9*<8PKQt})0jPpVGSTcXr^;&Z^zpls)+CW$FfHL)PbkIPJo4bw6!O=mlnFmO zgqSCy&i7D}=^z+2VOv+dF?a5r^?Y<`)A96MNmQa^SlZWDir(eQ1Fx3i9+qZ&F*8+W zM0uyb2&3R32(Zrth8!W)dufLw=i)En2Omj)(A{f0H%0;{bKsW+{s8lK=A;}Tl|A{K z>hViFH?I9JMOmGE6J7o(c9l=uZM}fVD_4yll`|~G5@opohraSVi5m)J?t3@lQyr^i z9sgFPjk;Bu3~Q%AA&_7AdXOlQu?lEIG7env8GtyH>(Drrr3ViMY1Nef1_JMb(9Thj zhZ7Bx$6X+WrS1(?@A|RUb;ko&!I>Dw$L-lO34%fAjhbZU=!He*OHPT@kpgk0;(?Ub^tQ$`f#GWeyE}oO!01ViM5jy2zCCb{m!soc$St z?3%s4)Wg{CX%(0m6@Uie>P^h9Gp{@H3o2>;P0;rp)ba8}oRVARbMWcX-9NzwFVUUpgYT23>s ze&fpqb$l%+6nd;yDhg467|PGhj2Hf?dHfS z>2)y)CZAa#LmD586>;nkupLH|7X^sND0p^UN4jzL)3$+yuM0lrqzCFI)DqbNmU_e=tEwyGSJ1 zr-VJk4m2tE^o-!RL~Y2)&mQ;7J0)O#S0LDFr+v!+tso@;Tb+LJXZEg*^ii`%i^_Fs z4i|xzC+YlxB{`LP$0GOjd*h3c`*SDFHRtF?7L}YY4hLChhDef_fYVA&KR`y({QKOw z?6hCEqe*KBUUZ^k1V3BXq0Met0!&(8=W^I*e}`m-?EWN9q#uO=DBPX(sZcs!O76j? zlAd=&N%S4efqq%L!ym0txP_c`H_vKDC>B4V1uHwBSRNH_>XtSwF3z_ygq^h9n7=y5 z)Qxhe6|kX^7NT9xf_#0-^9`3QII94tC3iYry}c(y#sceMb847_FU}8KNa!6x2|@^= z{8F$oVUACCC*w{Q4kV)+(x-fEnQ8nHg-GsxS_~X6Bcc>(Ypei?B%cnYhzhVs^QK()x?0PpUTKHp12q{+Z8;&d zy2)n@Qvu7-c6l6CIuk-~__`b28~y2Ns<=_!>hC9)vO#sG7euzLr@Oq-2WnueQ}F9> zu%Qz^cDPE?SG!xCKK?(J4@K-(JYf``brqn@M#r2AuiLkse>3d!zEB1lMeHsBaiO?4 z*y69+irG2yw#cX+5RL(^F|qmFP>P4^Kya@_hIm`VqDf#PdCTd~F~0a&igf%qj@~v= z?R4^Soo&ub4gE~e7oR|4=}q|*!ImgYKHcKiNtpl4D`3)!{d`p8Y8sbsTx7{xz=7IL z-@Tyh+pwPKfXo0!qc4FX5;9%mn;oiQX{;=A&s`iGZhg0x7t zbhk)@G)PG|2na}*q;xDA1f&~WbT2v14V_=rnd5VdNSdqmeY#NEB8AtA>E5WZm->SSHcUz zk*+9E7ED0~QMSfbVtu&f*7HkGp8R@UvJfh*)Y%slZ9{lKNyTvWh6-!kXd$9c228z9 z3d+F0!(T>#A^sH<<_+KL`Sdh-NRAJRyL?QyhY)59X|ihN2N7G9i5zFEJ{(}<)x+L& z3HiSC)WExX#myfFnZBtU8)o{8F6iMDWYW$n?Cx_C+1Muw!(9;r_x*n^1sI(JjdQ_z zQqgGRATqc0^@`^N%k_zuo%U|SkaIU)jn$mqZ{^y*eH;}_m{BN^d%N#_vfNDOAX?P@ z^hog&DKkbxxZd>4pGevXc%I{g-uXUi@C0kqUfJ~t_lvREN4<=0kz*01a%`~1~3 zisfLDVrI*nYE*#FAPDmarT!;&ULqh)Zdu=gk#B z5XaKCm#v_zOxd#T*wl)SI-a2TV7u7XbIYumi}L$At5@bat6)R7e1*Wu6G0|8+jYT^ zSpRdb;3Aq!XV!w2wmfk&MF_K?k~KtK`=<9^)ojzbgmL3Op{7JfD20J|+4sL};jE5j zgkqhZCfh^=XhEgXk#9aEu3&iQyRvL087$#j;pL;hJlWtj$rv@}y2s{4e0<*V;xxdL z0ojBB3DfK5QYcnO&sCUE!@M;t__@lZH{R&p;bG@!a-tVO?H+eCW2xu0z08H_3OgQ^ z?3ht!QJKip+5p`PXKs1~%TgW#UE18~{#b(DqR5spG;^J@Q)6{W+FrE=7X@XC7Dywi zJ$~GJcKZCNV$nsdz86K&iD&og2B}@koopd=LhgtOKYDQthVKc{L0`}s@I%mI@sUKn z?3R6{+5-xLV}8{dZ?V{4SvAL;i|~gj@NInE^w|{I6xqpC(4phUv1s_5wqSHD&3m23 z*rgAk!{rY}5g}1;B5f9DKX}nn!`kjOylznDUmRGM+PPUo78>PNu@qHs!}ABEK(d@Ot5zU40h@;5p~&M@5kETjT419eHeoclegv;DkN7~3q5D`@$8i2)67UNmLwb! z3*`FEVOuLyixB5m^?2^l#xdYz@PfH2h3>J5`k0*)9K$>XgP^#6HdgATaxQZwjLwF8 zFeur6Zlg;8-A`$COpo#qf;e7jK#WNaJg|K~p0f<{!}=A4!aj2KX%icg8mc#QG-3Ch z%WR%qyY3#&aLUx-5rom7LyT5_Vdy0uD2(Did8~nyRll;9K-KY{4>f4yy5o*uMfY#B zQ5DP^b=HSz{Rem2cr)xZ6bCeDwrZh=5SCCG_$bepnRtPVdsr zb0?Cg!&}qaR&#>4E`I#D)C=#YS*qF0ZdCVMpUbXeniiZ`PB=3m_W*DCIlkUubb@lz z{U(MzNkg|LR(R3H<;ait*G5eJ+>(ncXjSB=DwRiZpwA}Pdj5$4Y3p#+<|t`w%M` zEuP=-bn50Sdqtb?;kR>+U)GwAL*vy1)Jh~93)Y^9A z&M(Q5!S_9{UcR9~=cN@S%2`fHB9S`_PmMIy5xC6U2sMWfOsqKf;Ex}9qO6=-B{xO& z2wK8*f|`T$2KE|vU?IVt;iruuC&cT{LmVPg7tL@orSS#!n=CIIxvUJpnwjfW;O-oSrR z`9I)DN2W`^h&29fpAugenIVE}68_3KyjerN1ipMcp!1=l1rfQ^bTCBWP@zmyU13v{4H=}5h(^5HHMfuRDCU_5S@aD>na&q^Ip@u&{iQ<;=Lol4Cs9c zKMm@;$7rpwTExo%Tb+cYj@3M4EUA*KJ8K8&T!kUPy-Wl)@qaAvTGNkK&>L|`U4|!T z0eQ8(uy61*z#O?Dq@|Q_6?x&1Ls-Mhj7|}u5-8a(rULG%3aqbj_pKgyU_@r+e8+hU zm@}>xMTUcPNv!sJGrHoxyRYy(M~n1&eW*oWPl+t&`h**rQ6atluG~MY+~tHb2IUd| zDM3_i_}wcnf^ztsqN&io2yuL>WzL{T4wk*+L$Sw=n=|eJV*>oRPI`03;7a=EgCoAX9 z8c+{fa1<$0`ZskfR|afP4oinDXP#OAM*i&khQhn~gdF743 zpv6bUaWQkyL;5u=6d@u^o_E`CTCxiH`G}(|}6h zH}MyrI#|L`dz$?7N60o{N`YDr&*bZ@QbELi97AXU%=#^i? znVLmhSM*Keg~F6jD{gJ-5U4>rX`cc~91DXN1B-1PCdWj@2(~7iLsm5GwGbZdh4&1N zt0H^<$AsaLM+Sp0_Zln^3QJw7_bDNZaehK1m!O#TPW%zINXP7J)X|DIo+M)(3U;}y z_sM3#iSGyNJ1B?4X?bZD)miRx?+f&8B6UQ19c5RXucoN=VNHj;F7<#iGK%;*ekAzK zVIVQK&sv z+!_(_o;dqy+3}cBWPLjdnQu6hcI3UMHXT*8aVjw$uik~opC}8s!YL`8;3)GERQKpP zh{?O`zyb4l^1ilt!y1ht{RiA8cF3ao-tu zI)3aPF|jR=VKRhu*GsucBEI(sbUeHDh2Pjax*lHq0K{Exa%u+IIsyHZidl=4xT=h+ ztHT@5i=~!%{gwKyW}WoIBhud47hLIVxCIaO$!flAH=o5~6vn+r#DafxJ_4nJ*%m1w zXU)dPXaaOjJkx|MySO)?zgK!wgBGdWl?4SODpdTV-DV`Wq%1nEc&u9p$MQ$$s$6R% z(5I8$fBrMRSV{6jq?2b!5sK*RX)yJpuo;7wF5ShTDh!?aknPRG0cX$Lkdjo)>sQ_7 zaQz@su6q8HR;iy%-jt((9CK?CtkUkdW*EBVH=v(#A=rz;&W;C#Jv^9M5EM=;%FW}(7$ z(CU|cO7Pvs#zv2mo^#N&F-;A2_iiPqHC%i??9&^N)j0j9!5uRO2UZ^GX~pRWdO})F z7H`-oKm&hcLBC&}x;Juhg@gTqaFVovvp}Vm+S{#Vdi7%l?*o z9}DPZ8s0-)l@1Aul?Ooe-7Zmrf&_M$cb|3FOO{0@bVV^*ZN~})#})C_*XV4$9(7#3 zfmLysD__f+wiD}dsPQ0moI^^4Y|urLjQ$2%i)uVubf(oLN|c_-a%>Hc0k=xjfz{5C z?(tfMS-jQCeCod7bqEd_&40MIZQ7F%K@*^oS2t8_^(Nf&Np;N7-TGh1fF*(}{t$Kn zs(68EZGy2oR!K#AK2M!cmeHG{6x&bkdHb)RYZW}0iT+0YI)~R*pWX468&nFq|An;A zeERwrVBE}(tX}ic%4#ctj=RibZ`2}Mti!yvpGe|mD-;Z!^$iDb7Au8)B`YRX5@Zklx zimEgrl-8dI6RCcFmm^wuWYBaux2L)s?JA!POaAyq>}Wp?0R4U*IZZ`{MU!*R3p%73 zw2qQ>EE)F6cpQtfS$i|mt(2?57?gpZmu;tLqBfMeY}Lu}khg={W1+HdB zJv`O=N!wiiwt0UXv7bM?d_~d8rAsWpKtC_ztwe>0eMNkIfQ1&V(8^H;$`NVQ9v>R| zTq;mRyTWI^0m@@mRpMC|gwpD7ZL}*?O>h~dGm6PBWfJi(HJGDFj`?#qNLy%)cdyDt z+hd8@OvbQ!eVK2XuGUDMUpP;x?Oq>yLgU=YuglO!`3Lj4l7twUnNqB()XoQ)ow=NO znGqohC87rs+#LNuf=8JaNwPSncj$f$g*-!T1M2RFX5$^t(b+z`Qd-hT+nS4`cTo^$ zll2gY_OtxhetUgAVCz-+kP>q+Ac z-0_t8UDhzb_Hk4}sPWQ$(B!o_*I*NwBPNDx=-#5_soj0yBN%M37(Xz2qjKP&_|mX zSv3Ehuz!l@|&vvexS<#pT>kz6M{1&JIxlv?HttcY+H7H54eNklz#q**KAOJ=tpc0#PjFpb@V0<+8>fS+S60TUVJ^!H0 zi40y$*@t9YvDXJPit)1H_JcInU(TD2{h1yFJwH3@0dp2Ld0n&iQaY~Kqi`KJ^Yc`_ z@iG8jtsy8KlJu^cm=aPIHRS;2@iC9hUyRqp&$~hug zfiLM^IS!I3=8~UxOEL_LK|VK?vCsu}kKov#{^val+LH}79e+8A<`J@OlTEc)S^GQ% zAUi-$WD56etEWshjCX-7TsLab80DF{XZos#U-6sZl;NRbwtaCoIKR3sa-pDK!!@SjEZ8}AVNQtaO_f^e0nn;nEopKE07<-Ign5DW zpk`=qFAP6U2EM^DFgHK|HtUaQ6d_*%Y+x8o=4x&UG_$4_V+-Ni9)llK1 z9tAIl{Nqfc#AD;y_f#D?+5MzqVHcBKg4MrFKbwmmMNK(^>EID22jAvcv?K3!;bYHBhv0c=^RXx%8${#S5(7H9dvB zsVb%)-d+OvHq>{|q_yLx!pm);LT^>ookTQjP&xEd+YKis%Thh(JE56DZfeYk zDCv)KN;^YGx7}iYjrI(vwInz!*I=+^z>o46gPI(a_B_^J7WShdFWzUV{laj^5Rn!-QmK6Dmpx+Rsdn96P!n5n_v?4!57&W@|}@W^5Cmdh{_KM(AmLC;c#~|Ne%u zV?A^o?E#WO@G*v-v`42lXK#!XRmVEAF_%YBt*A@6K)8~)#)FKcq0UANq23~mRi0V8 zi&=Yn)sDvZU~rq-47PrEwK1!`m->XK{ZA3Ja2xl}5kOE1@F7q^j$9ezh0YfBqS%N* zE4I<(0l@Lf1$b7dp3zl9Usn8Jrk?Hg(8s?9@_WS{J|Hl86?62@ZHO6>0$Rsir#bYr zB~&$d+70@j7(>rf;?7;8TS`)pVRxssx9rQ7hB0eeS4j zPiWJD>b+Oh?7K;Jr#i}M9~XVXVSnK=Fu7IL)BKI?Ib{$DJJDlYSaD)D&GX55`!3Q# zsC}xvYzMNR%S|L^JT2uOS*VauT@{|ce;R&h;< z|0D0v+M_?c*l7sM$xdSTe>RAu!FciUi_t?3B;|hhY|KO_>rufJ94k?6MM0ZB<{Bo1 zfZIACFoL1duJ1jcb{jh|lLF($ zXb6p)?3ptl)Gb8xxx*`ZqcGM2<;f0kGsRC0Vv<0B^0)X5w4m@t0m#X`GB&Du#X)Og z=D21H`o89Xj@7)emSInMQJKERr8z6pgtoUK%uccnzICIRUcS^<=-EGM&k6$kY;J~2 z5<7?U0oxl*m0Zv^6+R20hQ}OeCjz~iR0u1n{fPb%P@;YdBisU0`K|nm;^qX zB$5gNO!c&cuzN46dD$X(mV+(aD}*U3K7Y>A7S~z-oI@Sc#uZF*Ac)gymZ4~YY2k{; z9*hRWQE*ZFJHmjnQ_e$aq?{UX=pq?bas_$$Mb@_~hf1VV8`od47SNi%D7%4hHz|go zRiA_UlI8nrzEvSmb;`y`q-}f0e!+R+^Z99ZR}%&jyAEQ)=xsHg=qst%Z<~ zk`Q)KbQ#&C@vWjcgp(E?8_59;5;qkg$M}v0F+YUa|LqpVF;q`Jw`7E{e@ZD^nsl(L zudXO4!Ji4yQD(jD)r}Kq04Aei+>H{0J{U!M4G256)d4~Jm(ysi5{vg=Dt-C%j^@O1 zL6mXF#zlrUE82uk8g7&FTAf`fnvNOwHhp$Ee@LKRt_l@i-V2 zl(v>*DftLo@M5lrbDq)qu1InkdI}PIOYYd^Jd4Gm-VR%%_;#OfZse*&kXjRmosWL( zRm&TR@a%Vd*V>X0OrKzM;|FOWKh?mpSgKrvo8=u%yG%%D@TvNyyftSx;p6QBjX)q) z?0E+Q&kZ`Ohab{}V-Ap%k*dc*o}x0vyX$8>psF7K2L}Ro#G)d6E4Gq_o|@508-vPF z{FC5|rvqeFU(+0ypaWtg&=T@_o$47V7NYNWpa`AAcJ#1lKu>G;433@hW1H}w+t~H% zJ+?k1)CEKs5GQAlp1>#mFs9s%e5|kZX(azYhY$o>x7jx$EpGQYe>2~3*1>)_c^9gP zjAOW3(x{@LA8wj3y5Oqoy5D-@s_%6m)Xc={*0aoBvE24_A&D2)q)vsKA;qRisqVab zkK1|b7K6G?8e+iPP*gwbf~IAYOaLYDH*(LskH^&1kiw(2EPN(pGXFmxq}{W*uDM}1 zoGuQ?MA18%O0Tbf0a|0L%+K7(&#>btenCq2E_GZxw<4IS`)uEs$op$lER5mDop0?b zH@UT+!G;l7%-`yBz?{_k%?(+Sft0`U9$C!K6gWV?~4jf0Pk{L{`@<6RIn1F1LOW##*G{#k$3A z9GLi2R_Atze(TB*kp?iC+uq{a)8KKnw&NT(P%~2rGKVSkJBG9=bsDPkwN~Xj>Cj8( zs-eouwIDJE)70ZgsG>}S@vf$p3Q}+4j%+4#&3HW2zR+5A-g&7D*at#n=ufnUFgV1W ziYUe3RtA#)m9d-foUb;|X^(rJ$n}gM;H~o3aK^FhQh+_gW!Y=>Bo`^*NzAX1f+i6TCuDAxs_9nQ(R^#H- zba}O^tvt=VY#EEnfT$fpf+AUHzz4x-`?oDuTG{QDdMlmG-dGzO(u0=L$mjSWq@9MdZmqhS3s$YIH7J}Poau= zYR{pNnE{qjYkoeG&^$5=Z9&-Ezm7U+jzcINQgokIsA0bQ3(90-l@-7Y7|?OuU2 zSEIUiD@cgpe$CY-dqu^Fen+BIa#*ZuXfFr1moYetRih zr{rs_d${7YK;!x}-%NrxydOX12g$wvxggaR z&ZTnup!mjJ#`SQa@oN~r$u;QRg~fkgi&#+gkk^3O;>Q*?y%%%jc-(xxa}8~w9#nV0 z=C9N|UE1rHb>FraqF>WFJ2*^ezRIba$wcHy|Cg2Anl5sDP^QCKQp1DHx9jA^1V-Doaq{f4KR-56P_y0D(Ur0`DdKXO)xYFfFU4h5pkM|Ghm3lU@W!r6XcZ zT5s(YA%tsGjj#_)aHSq)Abth3JC1t)>mzIbda*x|XO9)ahyL&0J^GgG@dwVl{W|b7 zH~(6mw0}MQA4zAL%DDga3rd+VBHrXy!1?y;fOo(C*BZQ+|F2z-=~qSk^xxO?AD4Nd z@b^bHQW3vt_pkeaHmMl#zqeuV?bk8=Yem3EMtT2Uy+0|gEW6hK!~XyOyuf){l&e4- zlA-x}$F|Sb3zj04_B}oObk14t9Kb(_q^FrvNIo~%YvKl*e_y!k+TuFn=4x%s*{u}- z1bRE|jf%GM*SpTmmL>CS<#Ro|RmNb;|1aD9e{L<}m+6GgrKUiacP|i}(<^B>>|O23 z#<2oS+|#(7>X{M}xAn|8CBY;@1Yx~E56l(5i4c!K1REq^;*BwEfSWbrdfPOGT!DDv z0;m`lM)Y`=gFj>$V{-3g)Kx^JF%AGI;c6CSLAkSV;3?nYCBpNo^r9H>qW)J~4}@kG zZs4bVqvyMibJO!p7yY-fJfMuRcKEwmu-hUr}QF4?{&n>M520&hah0vbwYrg7hj$z2wGprm^)l5ve} zg#H9nd6oxD*xt402j79j#|cnyT(fv%`X$d3ma@R4cQKMs;I5BI(Z*eO9W%7N4np-f zxj9fzafrL?yr7JF=3cXSMs3@*D-i-)g|y&RA5fYMrt(7LwQS72Vrirzm&#JGMEjp6 zXpdYylZqyz76!+7zO@8~>D_u?_f2@^vD0^_37wj41S8Movp|My1bo(c!Lx922LU3T z+pB%Q$EM7V-xa_MjaBNMnLnwvnyNU?B(!pPeQ})zI@Jdm0Md`CREd|o-CoTX#C@pOJ5QnC(Ah%I#D8Krxus=ZL{mC91$XC3LBCOqi| zRzsU~&8RtKa{N8Qi8oLmjom>bd2`)-{-7tGS<7~MW&wUu(C~F(Ay4o)e^mj73j}6- zF%Y`vY@VONSekjxA^d7XP!?PPiK)vA`LV9Tq6%q^T04D};zb0K==v+0Oods`JSVPK zQs`oMRW0U8m^&Aks8Fyl+dP`75`6xfPDn%N^J7)@Os3|mLqlmSqrUiJ=N&{%5JW<2 zsg58L`YIVr6pyv?YOH|_u`&X$F9g% zah4Vp-8L=tE;X(1dj}w2Bk2Dx*22(fkgGvtQ`6gEb_ghbRSl<&d&TT*USJ|wf-|Us z#~JEO`5n{(w5VW_I)Z>|5lmgwdBUozJ>ZG}_Eq{)lIU*WdpEj>(*Gfp)w}KrRIv(r zlhC#6frO{z1PH3p3XX0NBA0v$T?d2CO_dH!Xko5T^+;fALO18CQBu4-YhFtM_^L@X zpz8>`Pg3wO>JR}OxY>@?fq4;yJB`Q1w#>lQ*gc-Jyhh(O!!xj{-^kt6bg*O8|FD{5 z8jolIRB3h=)d`-mENujC!2$TA1i;EW7;{sEL%~Wb9(yZ}m-t=o$CY{*BXE|&AwX4U zRb2yTzXlMG>hd-e{UhlmGyipwZ;wb1DW_|8H+2(MLcz$CS?LI@J(seH~_p{Q`e#-$uyX2yq6VY5b7Zp9w6l?>OjZsfvYZuy5MR9 zo)0u>$Yb?wSMf$r@NB_-wiDKipAE8tG5HS{?B&p3r}Za{hvVQq#a(v0+G>Vd9@mIa z-Jv~c?qm6(+;)3UUGMPhfm1!)YINjCn$hLT_n`}_Au7w!T{A17%Gy^pz%`>xg&q8% zeAboBPI+`v(ljvPXm-HnT?j9fBE{unF~v%39`U z#Td9fA6_01PF4%Q?BdXs@OU?C4#!_9KB%5^MBw)T>7q$WM4V921e9+*#kWYl|J77t*N`FsrC|2abD+m_i4?)HBOvlZSA1hPhDyvM_k3(@($r(*v|Z4 zw)|j%(x9{p`r40>=@8P3-w1hORND9n4&`jww>&E$q>|*GM`o&_dq81RJTN2-Gv^LE zALXHV2>uiu71B;~Qk~(K@=^JhDqG~Sh`3n6Dc*a#JFbOeMx+VfKwi;v$gf<5xNl8!)+yb$i-e$1BrSJWY7TF{Tw}7fRw_ zk(dHeixFeh{aPy*xUok%!RjS)+_y0#V(pHQ?pQ+bky1BXwVWp1dfD&U#W;~Yqwn7} zDdc9;%MfNGyk1o+qTZJ_`C)wzX9n<43WRCbH9|zWh^-@hPTvuh zL*MZn{z|fwUE%{NY8lg$q`Pc{&%0`Q{dnrOo;J6vFCetkt=W>Z!&K4PNLKe;huk5g z?|)%XLQZwTEv%~e#s@}WvQ9m06jebk8V1EYzG#r3B@K7DkBa6*0IQJjI7L*W;wZ(x zvL}{>`87w>DU0WC;N$v%j=zi6j}+n&Yy4}xW9PppQFxzZ1mVD788;0NIhd3x3U}?C z2YVsB++92xs4 zVd-(_4=3IY;DTb=d3b+I~yk)6TMSzTK1 z7Yv0Tge9#GW$0a7%NlFrRyWvBf=diy!B0hD=ZD4h4MH^Tw2f4`d2P~PhoFyTzPe2V3kPwj{hLvi*jc9Oy~TK;hn> z(6(&&YdS79UUrC~Ch$^y2omOihA)o6_7C1XMRX(tAn^_CNfBmM8J4nmEM>K+@I%qh zf;3s`^6&470KN3drY3uj_>D^13yu;EquNA0$X`8&xpv5e;tH<4N(Bz)F$3O!9zbh} zLg+Y-;!kkBt6tI!znE5?2_Wd3RftbQrW42ZJOOozW=-88Z7N-Q?F`cD|F5Q91u3I1uu_<5iacHuv%3Eke6e?6o z>Ng84Jf16-sZ5R`6(oxX_<>Q%;qgVB5}Xr-P%HBlQiD*oAYrz?fxMn4bVca872AYE zE)rD&TtCV`R)D25(6n&<<=3Lg@%LJF!^wtcCFo0q>iP1NQ!xz&e;nBKMAL_SYdI9k zwqv}i&TqCS1!ufUpbt0TsJJf9HzrMBB&yFSHlqH((xQqQz&bX<1FUIGEQ1 z9R@LLjS6y_nRLNa$#$YinweHLXo^@4Z@1aSiLg`^Z3t?%$5>UQTToozYe$?+Zh^6-GxLhK0@u zo573`AesM}A|6NBXfmkSY97jm)EQ=q{sQ8Uf^CQ|B8#-OiT;Hh{!R_gp-3T?ZPm~* zX!7ARYm=E?Vy%SQM=K9MxRDw@H~8#ySFx3+vmP-fX<~BZ?#k0AlRT=eEV^i!ekv_u1ieZ2a0REo^<{MIZIbN zFNbT6bM=@fY7+FkTn$5IfG*G#;6{Mi1j&v~VRRj}x~L8p667Ko8bcrRb^E5YIV-Z4 z$sC(H=#Ui@J>s3BamAD?Wrese3%yu-DGG)(I1x3DKTf@|gU45(lz$%_9<`AB`oxxy za^Ok`*?~wh(%HzIR980RnPiFQAQK*#$?#2zdvHFNSjS;o#=0#>7{R$;?4tAw5 zDRho;=hWnzK5&H4Ykuv2W<$>69dPyf=B3OcGvdd0vOaqfnX03m zP^RSSRue%~dgI5|=2LHHydh4mtj`op$Me_Jq*^E=F3-aZOR!=lxx=lxKSt$H%zS)? zhnFsyT$9`Yk9QJn&<~zSIaiXMewIbgZj;N=Vq8 zktAG!by>;!;JV(9YOC|~7cCdhXZqi^esO%DqJOz?N>5=xGpO1%mD{=wRs~D#{lyS-#~CV|1MTr=vF15FiX=6bNh(I_DB`e zJ^b17=2zM_^>ra<`Vi2~GH6+MnRQ+{PPpWh=6o-Tz|(J+zWH5Vh1%3O(h53(m13J{ z&lW%j2KGd&id64`ZtCAE%1q}8$ju5U*{EvB5|D~VkhQN z6c3HPw)?Ck9OKl#36whPA*Q}7M)%4OW*}u1H+KzR2~R2`uq4DU8r?&X+GDB(ryAaaB1+1s9~*C2MjU%tB6jFu-Q$=a6Sz-vyhw;Ni|IDG7?O&%DaajlGLPhtp+~S zKB|RA`u>dvDG?m@$*=?G-J7PN1`n6d%EZ)x-WWD6;Ra9dxg3UGecQPAg?BOT*q^s67?c#yX4^S~ zPGwa#-kXj;B)c-yDVW|+{8+Aih<9qH`~$WZM#xdI;{qD-O6n%0=5RR_vBXQU&fQVT zELMK-2vB_8O;1{PNW84$OqXT1U+y^b3KObiz%&4Rx%noS0yQ5uHH1z0Q+QR_jL3un zQ{EwU_zzKJF63VvLIUNn+$Q~rwP6sapK}Cr(dFKqf$!7hrFA~CtxDFLtB+CSDr5*g z#@cC6C}^6rZN9Ep%&(&egt)}xMm*}WTB@4Tizj#AE@!;^BLC1guNGZ;9WN;*rNIM7 zEWy^}L;9fs8Ko+ldS+X&T@WU~T_};;eB|q=<0#1K#&C9*^h*7s7hrm=PCfCL zLZteK@6rs^?D(-H(c8m`k>9T#9BC141rrzjN+T4JEy&8$WJEwglkZ36R<4#8?yY5P z(!~#BX>M+Qd4kFYtXFf+Wj);fU9BW@MEl!Vhz9R|3vy);H2J=P%a>4N`rsv>+@r>N zFxIyb>6DJYw!_(G_gFolH=3$R4KEtxqKpS*L6?uljkWQchG3+ELiP%-z%o>T_m;y;RK)RJidJ* zr)OF??FqjgeSf}B?Sno8d;du9_xZyic3M=&rcmEG1+9w1KBpE#>)uR_ zZ6b%MyoUl3rfBZ39j$u?wUo;|9FcVG2yC+`lzl$tr_X8g#bzPQR!4=}mmuEWFmaI3 z+gn@4b@ugJ{SF5>7cVWQy7XPFIl8ZTD?AbXc?F+_M1>(9gOn3@xWiE#@_kJ%cWNS4 zblyusKKQF{X!Z+*sNMG?0Dh*}#v@2(g=R$Vz2g~6->Fvf?T%l|sj`%UCM!ump+fp)dA z+oL*`X(fW`vKfp+^}-*YE&>g@*m=NyCYoCBWsvow)C0q%+3VVX+ZZE!ql?s8yE@fV zPRO(@d ze2{3z(T$}=R20jCHB!FXc(|3Dhh7)I5y>~8u=E7)QslYvPAXRyg#2NqY%8 zKC;d$S3T$P97-23G`>T5U#~i!raQrI?SSUdO7nhaI;wqrb5@qG1kA#4aQ{2**mJtH z_iu};mmnYi%H;WQuQut6n{Qs8#Vefl7*!NvOkJr?l(p!*@-r(G^3I{QGwKFx72z>o zf_+}|?KN{&8-8@gZ|DrFHop%vl|%NYl`oXttsxM7UY#$GZNE*%8@A)wHIOSf4DTJ_ zDe&Xg&&eG)dk+)RkQroBKYvz{)OY(ATx{2lU9-$#TT*I^)2B~ zx)PuOZp>ha_c^X3G~!iJ87s5Z4o#IDGzAlfSCky`OHBuvmq-=baz2rih}bj)ViG>I z-Q}6b;EqAjC7~{PRbcYXVcD(lLy4vV4mHSKs#_Xg7mTd6GB&|R#_&)1W}OeNIRwhY z_t|Xc8?3a(SIDA5+@FurO)mMckDF!{mOkqEc~^ZiJ?z|{VzwF4tx+CoMD|X44u0s# z7tY%&=IYNntHf?1yP2~H0*3zGS1zB!TsM|@cC|3T8U7dZpWGIlD zWG&c-U5a7f<;Uzi-S)ri7|xaqLX>Js#Nre*GbnQV-z<_sq%!riBg69NF0=!?Ns3_lr9R?T1aFS6ZIFJCd}B`O^EgXS?c zx5Nkr5{6C;`EN{l(fkW48f2yImz8?$LD<+abP5k(YeTk&B0Qo`3&!)eDJdvt^~nqL zw}}ei`Mw}mGh8P4a-=N$*t5V!%Ecwz)5IeMetp?hE|DLh?=iG2rp+JrDo3ys2DV?B z$&wkdo?rP0I*O*&b3aA9_70ua{aF-x8_X~7x~9zKr}Pfe(SLq$p7^~uM2_@{}`<-_~)X19vr2NefwPb9)F^r zGA=YWa*urx67B%+6tR^ZFzs+5d3@*FG2)Jg9>5pyn*Qb%q+yQ?X{raY;zu4lV zf3iIVJbRXY%8KJkICtR2XkL)}UL$uPeX)ob?^>blLX&=^zzI4SYtLBnmSHqk?mnop z)U?;yuY3wsO$ouH3-r3a2td?7J6{!c{MRyZvIn2{>A@eSd&AF{x09AkBH6_v(Deo24W^6Dgo2 z@K`c}L_>S_D+q`J0A$St2;&d*ua74pXKVo&pQ%-2vjofr^X6ir`BRFr6L`}g=ITi* zo8|)uaS(!dSu34?b=D;m(Urw~14qo$IwSLED~$QCjWjdD0Tq|ij(ZOnIKk+ms^#S; z;JHa$e@B&Po`I5xtG#`Z{}hNumk+VHCMgi}4?0!G(Nmk{xaT;@&=@|ZgK~w*kGcyW zQbY=R7fU;b=Wk)dX0Z9>P zf>>(0+-0CDJLxglwBH;VGYmlVQDP^1>HD^mn$F$x5v^ewFMH}5FlnOKL4;i`P_MT zb{|OSsud+nOj88@4EX%gUR%x7w&6hHteqT=(^a9bQ($4!biP^! zGT=|gwPfFBc>q2r3a-5N_2KN51Lo!WG+sxO2Gn)=T~HUjPnY50Fko2Bf4Nbsuywu)1Pd<%E9tfG(4Zp(gEGPjV$}>pu9}8p)?*_CmlRWkM}B+A9|Nxqaiy1L z@SpfOopS?olU#n$Xa7a34pigqFG2Ux;G*3Z$@Sq8PvDFTi@W+U0*76#FmBRPpa6XoW?GA%TBD&Ze5dTfL zI0JtAII4SO03+azn2Ij#&AR`xz!TIf66`>&5c0FI7VD03!9%2p&hO@<3`meG(>g%t zs&rC%?gWZ2ot~^EH=t6>?lsOoh~ZsoPI|(6oeJT*SoFiLI9A|4=%@|zj=6)3+Tn^| zBUElt3he-^)`wr6PhI>8x22$lx3@x&1R5IVQt+Y>MnL&6AS0?6N+pX|3|cRAs+>XQ7}F%X=A4hv@x zHRd~*J-C$3=GuC@#BMcNM(_j_+*Ha9e21hz#uBaOukvmcRW1u2!j;wD$cYAsDQOaS zFzGe%t43Tc<2R$r#52*eM2v}`$TrL)+GDg+m;O(8R~`uE+P+()QIjPNW60JbvPMXD zkwmg(IVelnWl&P07>u!XIF{lV`x=r+lQ=O(TI@nehpFsYMwTpn_d9*(_p7hJzkfb| znK7Pu-{-lX=f3XizV7=~aXON>9)EMUp%+cJW|EonOnb!{I@b|-NgC&g+79!iKAF~cO_X4$4u zXR*hLi&>5n!_hKvJqFDJeuk@({1@-a;=|0j z8iQKtMFkl+4YB5HUzP4Zq`Z)9#F}lW5cDUnHM@+tFho z1p=(X&zpIA04EbaUwmo~AB=f;Ln zkRcqiy$m%B&Ww_*I}xlwn^+s-RX1_r<^p$9vJLDq*B~PkW5GjS85nUNKUG|%W$35O zEAH$lg!GoOJ-;vg;OlQ=#vylJ<-EP`Z#b49dDd=I18cI0RN|(HT~t}J-$`EIrYVy0 zg)!BR-ThD?VO^0f>jcMaG#QqJMULO=fyqF=OIrrU?#1jwoiL3Tg`OLq>XzHXgVmvJ z)Nh#Sr?X#S*|8~k5~cp{xEnalKSE_o?s#{R+3JcR{hQAkQulyQ>#uZinFy60E4Gk1 z%uD5zHHrFzLkd+;vrshie5(oqR!>&Oh0`QN$4J2u@Cl`wI=Td?xL z{JOTKxk5gJ0^ZQJ`ZKt-FGdRZ>Z8R-uBl?X`k)fzKITLVA%btG9<$wnFoNaXaz(b)#c z3hkkBXQZ0QZ7okEY|eP`*-r z_G~qG5;z=jUqX%t4`0mic+LbX?nl2PIALPewlLQ5Fb;KP;NUk-shf0Yj%wv?+wr){ zQ0?mcv(Ouk0q34=l{1WC(@xrKm(X##Rh5!l{Q8UOxZqvD9u-;{a!Q@Yl+65h=qlO3 z#?Vm|+@7>eA++lp88_P>sSv!)?(D~s-Ng<;J74cs_Bn?)p~y)Xr$*@$E-Qy%BwAMpofXSAINqS zLr&R15-t`8>pF!Lqwy~w)A@~SeV^5*5u5T82#;GOkD%+~&9N)sMU4eU!t~bl3}bFr zKn4t2ZS))hgSl)S;3Tz0jv!lDLePdoFX3ezQRLRZI+(|ZH|EgWCUJ%6AX_7Q! zkwRg~%<-HflNa3uTxJ&CU4xA26{}GF70Y;YkSKz7sKvGN!qZ)R=UT*?%X)2cY&dno zxfv)LsfX{xHdWJ)Y+C4g?oB>BdPOZxbEO`}405KW|Qawcl$0r&{W z+m_@iqi9^e001OKv~ZInwebILUOW_w4*3^Jj?eT0`ydB zrSAOfR!z+3dsnWk*$7ULr1QduPC4x(6b1G+Td+Dwoc?FW0)alaB9OA~Vz_YA{ zw6viwY@+RgT4<*aXd!jgI*yIqfUR)r0B)<$354u zhxO%T2uEz&xlmu^J&a1jE{aVFf&cNbn>p%)<4U(^T*-Ax2~DlLzQXgm80~2K_u>*g zzPUZY3v1~Dp~5Wgyn3^U9b*bZox$1iv&19waH?n+sOA%$I+51sv=4j7; zT(wNy(lF_1=U7HbvT(jlIGa|3;#*JdlL5NV}7${L2 zZmN&b+)<1~o5r^whbeeU!x)^is&TGfoQX89VHKx9K?`+6zDb{3Ks%DO3Lw6 zg-)FNU~QnBex}JoCx_`#H?8gjy2VITHb%OU2+x4T{xwjL@MWM00K)? zJM4a)HwPlV<>v+%0njI14pP$&hmD{ZaT4Myt0omAhgy#6ckU&n!&mkP*0w>ZA8*Ry zSeMxL8)ps^hZqWnZ$95N82_cH+aV^=!bFOQ=+@BYu1f`^LgHzHmvV;q0NihuK@?~7 z)Tmw=aW)L)j?(Ca(o;?y&1$#?TA=O2K$a^-M?o=@ieRKKeDVdv`ppaEs2NGO3E};* zZxJ8jHpTQM4_LEblyzgQ?zrmbZ@Mr)+JV8y>Q=GJ^+-5s9+(BO(cvC!UjdqUSf1@ndA|fi31Q zjM2B)6tmIn@#T6Fix5TTN9gKs#xGiThX{e_CuT$Zhv-G*>RWU*u>a6NLteG-bf%R{M ztkv*{m-n#Wd${Ex=#XURW?dEa#iNLrMTzulsF7{*ZbiwK)eMuc6#t3WQkUtv=h^AS zvKy`mIE3Wh7--rE*9T%oy`eTu{IsN_?Wb;Ka$*b;_u}4HNNsoeGZY~ zf@)W((i6&CWAK&EEeWy^EGz{F39i$Mr}k(lfsihoYvg9ztC5SOpJy=i4}=|Im8~M6 zbipN;aL10Di?(x;%ZCdiq)QQ@YoF#TtO$$tmuwKlMc&9ZEL0f2!Yz6C`K$-qNJ9I~ zqCAF;%mUd!P$KV)l6^`h!AB~7COoi+|6%oyXuJ~_;U4Xh5ty(1_yU~x-J#>``)P_< zj}K4|<-C=5^J1R&|L|ii{<*>zI&)9pgMNpKrG1L0%?!3+sr~}U;*Y+s4*`WbKibKl;itQi zBaSH}Nj3hDZOZhRne~-vUS1C==U)cl-fu{ZozsbA-vM$!6a1`%^}F(dpGn6AIig!F zL`6-XpM71Jf)TGRO!DO$-rgw)!ayH{vLairzws!y@O&7wJm-UOd2U_#;l~97_qIBv z6`2)8Y}w93P#V0l=W>9e?}ulyuI-t!kg=_?-Tf1lxzCBS>k46y0!wJs-K_fegKO4eqEa2QF ze`G`rXu|ok_MmR{0i|a|PMnHFMzjw5k-e~2_n)b{YgHkd4i-^SQ3sieAq337z2^y( zMHJ(B43@e1u*G~Z?OnCGm;ba4}a!r$Z z3<@=RkoFzK2c(tLeqvpi!{X_*CcWjSVOt{sFV{Qri9F}|&67*9-#PIfJh3k_{`#cT z>>>p0PaK~Bc?jXe5HtfUx2lHCUZXa$0_I<0l}`iG`2RU*=D44LD>{J4$cKIN5rzaQ zC&Nq#Z;^v#bjF)pfd&)}AT{P`PI|OBcr7}*X9E*_nl3XliAgb9hRq$eoMFq1kx2ML zjk2yPkKUPXGh>WpOK2)2x_Eh;NU5)s-VX@pZ~pbJ;M-|e`4n7FpW$j$02E^-p6J3^ zdEwU&?xLdk4msxuUzqHxsKDiekIS+ww|4-YABx|*#vv8>w5F@Tsq_5j`qSaWJ?NOT zdfO$#S}tU$A>08BEABTK}~7`NC4;oPdL= zFbRfaC|c>AzZr8*pa&Nw4k~Pw>md@8Gkgk=paH5P_(nLevce^qazyJ&<_!Hq{v2YJ z+ufWKuZ~j$C3T-{oBwPRZzR+}2(Mg!^T4g2g6u-<5nt!($>NeS5+ia4KTn7Tm|*3l zL?p~bOi-G9G{75Qf&1&14G$*yL>6`@HXkjhs{Hb{yci;;c4#25IY4wj1Q@l{iY)zn zzj1~DEnY~dO~^tnUUg1?bq%h#$cpzxiVR1u{WY(kCr#13yu2kFf0=$D~>k9x+;vI_da8MzRs|1(<~aKVvbmg3GCtfN8z5Ub)~1Lzrl;f^n=|r=AK9-h1`LtD&e&$b8qjfbZjE^>2^Ye#or%3 zbl-@o*H~xY2RJ<`j0q0llW?{C-#01qh5H`nKO$p5lKp#1hWVl>_WLGazNq^B+eU-? W;V(6hk(4&T&k5aA$I7(Nh5id0I+u [--output-path ] [--token-id ] [excluded-addresses-path ] - -# Migration Airdrop -./bin/run.js generate-airdrop-merkle-tree --db-path [--output-path ] [--token-id ] [--cutoff ] [--whale-cap ] [--airdrop-percent ] [--excluded-addresses-path ] -``` - -## Files - -| Name | Description | Generated By | -| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | -| `/data//accounts.json` | Stores addresses, balances, and multisig details(If any) per account after a snapshot is taken, addresses must be sorted in ascending order. Will be used for MerkleTree computation. | Snapshot | -| `/data//merkle-tree-result-detailed.json` | Stores MerkleRoot, and leaves for each account. Will be used for examination by 3rd Party or public, also used by Claim Backend API. | `./bin/run.js generate-merkle-tree --network=` | -| `/data//merkle-tree-result.json` | Stores MerkleRoot, and leaves for each account. A lightweight version of `merkle-tree-result-detailed.json`. Will be used for testing of Claim Contract. | `./bin/run.js generate-merkle-tree --network=` | -| `/data//merkle-root.json` | Stores MerkleRoot only. Will be used for deployment of Claim Contract. | `./bin/run.js generate-merkle-tree --network=` | - -## Merkle Leaf - -Each leaf will be encoded as ABI-format, structure of Merkle Tree may vary and depends on usage - -### Lisk Token Migration - -``` -LSK_ADDRESS_IN_HEX: bytes20 -BALANCE_IN_BEDDOWS: uint64 -NUMBER_OF_SIGNATURES: uint32 -MANDATORY_KEYS: bytes32[] -OPTIONAL_KEYS: bytes32[] -``` - -If the address is not a multisig address, `NUMBER_OF_SIGNATURES` would be `0,` `MANDATORY_KEYS` and `OPTIONAL_KEYS` be `[]` - -### Migration Airdrop - -``` -LSK_ADDRESS_IN_HEX: bytes20 -BALANCE_IN_WEI: uint256 -``` - -Note that Balance is represented in Wei(2\*\*18) and in `uint256` format. - -## Params - -For both `Lisk Token Migration` and `Migration Airdrop`, a `merkle-root.json` will be generated. - -``` -merkle-root.json: -{ - merkleRoot: string; -} -``` - -For other files, refer to the table below: - -### Lisk Token Migration +- Check Eligibility +- Submit a Claim +- Publish a Multisig claim with completed signatures onchain -``` -accounts.json: -{ - lskAddress: string; - balance: number; - balanceBeddows: number; - numberOfSignatures?: number; - mandatoryKeys?: string[]; - optionalKeys?: string[]; -} +![cli_screenshot.png](../../documentation/Claim_CLI/cli_screenshot.png) -merkle-tree-result-detailed.json: -{ - merkleRoot: string; - leaves: { - lskAddress: string; - address: string; - balance: number; - balanceBeddows: number; - numberOfSignatures: number; - mandatoryKeys: string[]; - optionalKeys: string[]; - hash: string; - proof: string[]; - }[]; -} +## Run -merkle-tree-result.json: -{ - merkleRoot: string; - leaves: { - b32Address: string; - balanceBeddows: number; - mandatoryKeys: string[]; - numberOfSignatures: number; - optionalKeys: string[]; - proof: string[]; - }[]; -} -# `address` is a reserved in solidity, hence `b32Address` here ``` +cd packages/claim-cli -### Migration Airdrop +# Start Claim CLI on mainnet +./bin/run.js start +# Start Claim CLI on testnet +./bin/run.js start --network testnet ``` -accounts.json: -{ - lskAddress: string; - balanceWei: number; -} -merkle-tree-result-detailed.json: -{ - merkleRoot: string; - leaves: { - lskAddress: string; - address: string; - balanceWei: number; - hash: string; - proof: string[]; - }[]; -} +## Workflow -merkle-tree-result.json: -{ - merkleRoot: string; - leaves: { - b32Address: string; - balanceWei: number; - proof: string[]; - }[]; -} -``` +### Check Eligibility -### # Only used at example +![check_eligibility.png](../../documentation/Claim_CLI/check_eligibility.png) -``` -signatures.json: -{ - message: string; - sigs: { - pubKey: string - r: string - s: string - }[]; -}[]; -``` +### Submit a Claim -## _Demo/Testing Purpose Only_ - -``` -./bin/run.js example [--amountOfLeaves ] [--recipient ] - -# FLAGS -# --amountOfLeaves= [default: 100] Amount of leaves in the tree -# --recipient= [default: 0x34A1D3fff3958843C43aD80F30b94c510645C316] Destination address at signing stage. Default is the contract address created by default mnemonic in Anvil/Ganache when nonce=0 -``` +![img_2.png](../../documentation/Claim_CLI/submit_a_claim.png) -By running the command above, it will, at `data/example` folder: +### Publish a Multisig claim with completed signatures onchain -1. Create `key-pairs.json`, which stores public-private key pairs, along with the corresponding address and path -2. Create `accounts.json` using addresses in `key-pairs.json`, with random LSK balance. -3. Create `merkle-root.json`, `merkle-tree-result.json`, `merkle-tree-result-detailed.json` using the accounts above (Equivalent to `./bin/run.js generate-merkle-tree --network=example`). -4. Sign every leaf using the private keys in `key-pairs.json` and output to `signatures.json`. +![submit_multisig.png](../../documentation/Claim_CLI/submit_multisig.png) From 3fc1c496add1f432ac8361289009aaddc432113b Mon Sep 17 00:00:00 2001 From: Franco NG Date: Wed, 26 Jun 2024 13:42:50 +0200 Subject: [PATCH 06/23] Add details to README --- packages/claim-cli/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/claim-cli/README.md b/packages/claim-cli/README.md index 311625e..5ed6e9a 100644 --- a/packages/claim-cli/README.md +++ b/packages/claim-cli/README.md @@ -11,8 +11,12 @@ This library is a tool to Claim Lisk token without the need to UI. This tool can ## Run ``` +# Enter claim-cli package cd packages/claim-cli +# Install dependencies +yarn + # Start Claim CLI on mainnet ./bin/run.js start From 315274efce1a24b5940c9ef1178eb8003a597ee1 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Wed, 26 Jun 2024 13:57:15 +0200 Subject: [PATCH 07/23] Update function structures --- packages/claim-cli/README.md | 6 ++--- packages/claim-cli/package.json | 2 +- .../src/applications/check-eligibility.ts | 12 +++++----- .../applications/publish-multisig-claim.ts | 8 +++---- .../src/applications/submit-claim.ts | 4 ++-- .../src/utils/confirm-send-transaction.ts | 14 +++++------ packages/claim-cli/src/utils/endpoint.ts | 16 ++++++------- .../claim-cli/src/utils/get-private-key.ts | 24 +++++++++---------- packages/claim-cli/src/utils/sign-message.ts | 4 ++-- 9 files changed, 45 insertions(+), 45 deletions(-) diff --git a/packages/claim-cli/README.md b/packages/claim-cli/README.md index 5ed6e9a..7dcde93 100644 --- a/packages/claim-cli/README.md +++ b/packages/claim-cli/README.md @@ -15,13 +15,13 @@ This library is a tool to Claim Lisk token without the need to UI. This tool can cd packages/claim-cli # Install dependencies -yarn +yarn && yarn build # Start Claim CLI on mainnet ./bin/run.js start -# Start Claim CLI on testnet -./bin/run.js start --network testnet +# Or start Claim CLI on testnet +# ./bin/run.js start --network testnet ``` ## Workflow diff --git a/packages/claim-cli/package.json b/packages/claim-cli/package.json index 574cb62..41a5bd9 100644 --- a/packages/claim-cli/package.json +++ b/packages/claim-cli/package.json @@ -29,7 +29,7 @@ "postpack": "shx rm -f oclif.manifest.json", "prepack": "yarn build && oclif manifest && oclif readme", "prepare": "yarn build", - "test": "echo test" + "test": "mocha --forbid-only \"test/**/*.test.ts\"" }, "oclif": { "bin": "claim-cli", diff --git a/packages/claim-cli/src/applications/check-eligibility.ts b/packages/claim-cli/src/applications/check-eligibility.ts index c1b2aa8..6eda549 100644 --- a/packages/claim-cli/src/applications/check-eligibility.ts +++ b/packages/claim-cli/src/applications/check-eligibility.ts @@ -3,12 +3,12 @@ import buildAccountList, { AccountListChoice } from '../utils/build-account-list import { fetchCheckEligibility } from '../utils/endpoint'; import { getInput } from '../utils/get-prompts'; -export default async function checkEligibility(networkParams: Network) { +export default async function checkEligibility(networkParams: Network): Promise { const lskAddress = await getInput({ message: 'Your LSK Address' }); const result = await fetchCheckEligibility(lskAddress, networkParams); if (!result.account && result.multisigAccounts.length === 0) { - console.log(`> No Eligible Claim for Address: ${lskAddress}.`); + console.log(`No Eligible Claim for Address: ${lskAddress}.`); return process.exit(1); } @@ -35,14 +35,14 @@ export default async function checkEligibility(networkParams: Network) { }, ); - console.log(`> Claimed Addresses (${accountGroupedByClaimStatus.claimed.length}):`); + console.log(`Claimed Addresses (${accountGroupedByClaimStatus.claimed.length}):`); for (const [index, account] of accountGroupedByClaimStatus.claimed.entries()) { - console.log(`> ${index + 1}: ${account.name} ${account.claimed}`); + console.log(`${index + 1}: ${account.name} ${account.claimed}`); } - console.log('> =========='); + console.log('=========='); - console.log(`> Eligible Claims (${accountGroupedByClaimStatus.unclaimed.length}):`); + console.log(`Eligible Claims (${accountGroupedByClaimStatus.unclaimed.length}):`); for (const [index, account] of accountGroupedByClaimStatus.unclaimed.entries()) { console.log(`${index + 1}: ${account.name}`); } diff --git a/packages/claim-cli/src/applications/publish-multisig-claim.ts b/packages/claim-cli/src/applications/publish-multisig-claim.ts index 09750ba..10bfa18 100644 --- a/packages/claim-cli/src/applications/publish-multisig-claim.ts +++ b/packages/claim-cli/src/applications/publish-multisig-claim.ts @@ -11,7 +11,7 @@ import { getInput } from '../utils/get-prompts'; export default async function publishMultisigClaim( networkParams: Network, address: string | null = null, -) { +): Promise { const provider = new ethers.JsonRpcProvider(networkParams.rpc); const lskAddress = address || (await getInput({ message: 'Multisig Address to be published' })); const claimContract = new ethers.Contract(networkParams.l2Claim, L2ClaimAbi, provider); @@ -23,19 +23,19 @@ export default async function publishMultisigClaim( } if (!result.account.ready) { - console.log(`> Address ${lskAddress} has insufficient signatures.`); + console.log(`Address ${lskAddress} has insufficient signatures.`); return process.exit(1); } const claimedTo = await claimContract.claimedTo(result.account.address); if (claimedTo !== ethers.ZeroAddress) { - console.log(`> Address ${lskAddress} has already been claimed.`); + console.log(`Address ${lskAddress} has already been claimed.`); return process.exit(1); } const wallet = await getETHWallet(); const walletWithSigner = wallet.connect(provider); - console.log('> Representing LSK L2 Address:', wallet.address); + console.log('Representing LSK L2 Address:', wallet.address); const signaturesGroupByDestinationAddress = result.signatures.reduce( ( diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index 45e4ea2..365c0f4 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -13,7 +13,7 @@ import buildAccountList from '../utils/build-account-list'; import { append0x } from '../utils'; import { getInput } from '../utils/get-prompts'; -export default async function submitClaim(networkParams: Network) { +export default async function submitClaim(networkParams: Network): Promise { const privateKey = await getLSKPrivateKey(); const lskAddressBytes = crypto.address.getAddressFromPrivateKey(privateKey); @@ -35,7 +35,7 @@ export default async function submitClaim(networkParams: Network) { // Regular Claim const wallet = await getETHWallet(); const walletWithSigner = wallet.connect(new ethers.JsonRpcProvider(networkParams.rpc)); - console.log('> Representing LSK L2 Address:', wallet.address); + console.log('Representing LSK L2 Address:', wallet.address); const destinationAddress = await getInput({ message: 'Claim Destination Address', diff --git a/packages/claim-cli/src/utils/confirm-send-transaction.ts b/packages/claim-cli/src/utils/confirm-send-transaction.ts index 691a7db..6d90f6a 100644 --- a/packages/claim-cli/src/utils/confirm-send-transaction.ts +++ b/packages/claim-cli/src/utils/confirm-send-transaction.ts @@ -6,7 +6,7 @@ export default async function confirmSendTransaction( contractMethod: BaseContractMethod, args: unknown[], walletWithSigner: Wallet | HDNodeWallet, -) { +): Promise { const provider = walletWithSigner.provider; if (!provider) { return process.exit(1); @@ -33,15 +33,15 @@ export default async function confirmSendTransaction( const estimatedFee = estimatedGas * maxFeePerGas; const ethBalance = await provider.getBalance(walletWithSigner.address); console.log( - `> Estimated Network Fee (${estimatedGas} * ${maxFeePerGas} wei) = ${ethers.formatUnits(estimatedFee)} ETH.`, + `Estimated Network Fee (${estimatedGas} * ${maxFeePerGas} wei) = ${ethers.formatUnits(estimatedFee)} ETH.`, ); - console.log(`> Your Balance: ${ethers.formatUnits(ethBalance)} ETH`); + console.log(`Your Balance: ${ethers.formatUnits(ethBalance)} ETH`); if (estimatedFee > ethBalance) { - console.log('> Insufficient Balance for the Transaction.'); + console.log('Insufficient Balance for the Transaction.'); return process.exit(1); } if (!(await confirm({ message: 'Confirm to Send Transaction', default: false }))) { - console.log('> User Cancelled Submission.'); + console.log('User Cancelled Submission.'); return process.exit(1); } @@ -49,8 +49,8 @@ export default async function confirmSendTransaction( maxFeePerGas, maxPriorityFeePerGas, }); - console.log(`> Successfully submitted transaction, tx: ${tx.hash}. Waiting for Confirmation ...`); + console.log(`Successfully submitted transaction, tx: ${tx.hash}. Waiting for Confirmation ...`); const receipt = await tx.wait(); - console.log(`> Transaction Confirmed at Block: ${receipt.blockNumber}!`); + console.log(`Transaction Confirmed at Block: ${receipt.blockNumber}!`); } diff --git a/packages/claim-cli/src/utils/endpoint.ts b/packages/claim-cli/src/utils/endpoint.ts index 3e296d5..c027334 100644 --- a/packages/claim-cli/src/utils/endpoint.ts +++ b/packages/claim-cli/src/utils/endpoint.ts @@ -6,10 +6,10 @@ import { } from '../interfaces'; import { Network } from './network'; -export const fetchCheckEligibility = async ( +export async function fetchCheckEligibility ( lskAddress: string, network: Network, -): Promise => { +): Promise { const jsonRPCRequest = { jsonrpc: '2.0', method: 'checkEligibility', @@ -28,7 +28,7 @@ export const fetchCheckEligibility = async ( }); if (response.status !== 200) { - console.log('> Network Error, please try again later.'); + console.log('Network Error, please try again later.'); return process.exit(1); } @@ -36,21 +36,21 @@ export const fetchCheckEligibility = async ( | JSONRPCSuccessResponse | JSONRPCErrorResponse; if (error) { - console.log('> Claim Endpoint returned error:', error.message); + console.log('Claim Endpoint returned error:', error.message); return process.exit(1); } return result; }; -export const fetchSubmitMultisig = async ( +export async function fetchSubmitMultisig ( lskAddress: string, destination: string, publicKey: string, r: string, s: string, network: Network, -): Promise => { +): Promise { const jsonRPCRequest = { jsonrpc: '2.0', method: 'submitMultisig', @@ -73,7 +73,7 @@ export const fetchSubmitMultisig = async ( }); if (response.status !== 200) { - console.log('> Network Error, please try again later.'); + console.log('Network Error, please try again later.'); return process.exit(1); } @@ -81,7 +81,7 @@ export const fetchSubmitMultisig = async ( | JSONRPCSuccessResponse | JSONRPCErrorResponse; if (error) { - console.log('> Claim Endpoint returned error:', error.message); + console.log('Claim Endpoint returned error:', error.message); return process.exit(1); } diff --git a/packages/claim-cli/src/utils/get-private-key.ts b/packages/claim-cli/src/utils/get-private-key.ts index bd32124..e6ecf63 100644 --- a/packages/claim-cli/src/utils/get-private-key.ts +++ b/packages/claim-cli/src/utils/get-private-key.ts @@ -19,19 +19,19 @@ const getSecretType = (wallet: string) => ], }); -export const getLSKPrivateKeyFromMnemonic = async (): Promise => { +export async function getLSKPrivateKeyFromMnemonic (): Promise { const mnemonic = await getInput({ message: 'Your Mnemonic' }); if (!Mnemonic.isValidMnemonic(mnemonic)) { - console.log('> Invalid Mnemonic, please check again.'); + console.log('Invalid Mnemonic, please check again.'); return process.exit(1); } const path = await getInput({ message: 'Path', default: "m/44'/134'/0'" }); return crypto.ed.getPrivateKeyFromPhraseAndPath(mnemonic.trim(), path); -}; +} -export const getLSKPrivateKeyFromString = async (): Promise => { +export async function getLSKPrivateKeyFromString (): Promise { const privKey = await getInput({ message: 'Your Private Key', }); @@ -40,22 +40,22 @@ export const getLSKPrivateKeyFromString = async (): Promise => { if (!privKeyFormatted.match(/^[A-Fa-f0-9]{128}$/)) { console.log( - '> Invalid Private Key, please check again. Private Key should be 128-character long.', + 'Invalid Private Key, please check again. Private Key should be 128-character long.', ); return process.exit(1); } return Buffer.from(privKeyFormatted, 'hex'); -}; +} -export const getLSKPrivateKey = async () => { +export async function getLSKPrivateKey () { const type = await getSecretType('Lisk L1 Wallet'); return [getLSKPrivateKeyFromMnemonic, getLSKPrivateKeyFromString][type](); -}; +} -export const getETHWalletFromMnemonic = async (): Promise => { +export async function getETHWalletFromMnemonic (): Promise { const mnemonic = await getInput({ message: 'Your L2 Mnemonic' }); if (!Mnemonic.isValidMnemonic(mnemonic)) { - console.log('> Invalid Mnemonic, please check again.'); + console.log('Invalid Mnemonic, please check again.'); return process.exit(1); } @@ -63,7 +63,7 @@ export const getETHWalletFromMnemonic = async (): Promise => { const path = await getInput({ message: 'Path', default: "m/44'/60'/0'/0/0" }); return HDNodeWallet.fromPhrase(mnemonic, passphrase, path); -}; +} export const getETHWalletKeyFromString = async (): Promise => { const privKey = await getInput({ @@ -74,7 +74,7 @@ export const getETHWalletKeyFromString = async (): Promise => { if (!privKeyFormatted.match(/^[A-Fa-f0-9]{64}$/)) { console.log( - '> Invalid Private Key, please check again. Private Key should be 64-character long.', + 'Invalid Private Key, please check again. Private Key should be 64-character long.', ); return process.exit(1); } diff --git a/packages/claim-cli/src/utils/sign-message.ts b/packages/claim-cli/src/utils/sign-message.ts index a58fe74..314e1cf 100644 --- a/packages/claim-cli/src/utils/sign-message.ts +++ b/packages/claim-cli/src/utils/sign-message.ts @@ -2,7 +2,7 @@ import * as tweetnacl from 'tweetnacl'; import { BYTES_9, remove0x } from './index'; import { AbiCoder, keccak256 } from 'ethers'; -export const signMessage = (hash: string, destinationAddress: string, privKey: Buffer): string => { +export function signMessage (hash: string, destinationAddress: string, privKey: Buffer): string { const abiCoder = new AbiCoder(); const message = @@ -11,4 +11,4 @@ export const signMessage = (hash: string, destinationAddress: string, privKey: B return Buffer.from( tweetnacl.sign.detached(Buffer.from(remove0x(message), 'hex'), privKey), ).toString('hex'); -}; +} \ No newline at end of file From 2b5947b8c1cd0ae7ccfdda2fb49bdfb3c4b1b26e Mon Sep 17 00:00:00 2001 From: Franco NG Date: Wed, 26 Jun 2024 14:02:05 +0200 Subject: [PATCH 08/23] Update description --- packages/claim-cli/src/commands/start/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/claim-cli/src/commands/start/index.ts b/packages/claim-cli/src/commands/start/index.ts index d2cb6b5..7981de7 100644 --- a/packages/claim-cli/src/commands/start/index.ts +++ b/packages/claim-cli/src/commands/start/index.ts @@ -14,14 +14,14 @@ enum Choice { export default class Start extends Command { static flags = { network: Flags.string({ - description: 'Network ', + description: 'Operating network for the tool to run.', required: false, default: 'mainnet', - options: ['mainnet', 'testnet', 'local'], + options: ['mainnet', 'testnet'], }), }; - static description = 'Start Lisk Migration CLI'; + static description = 'Start Lisk Claim CLI Tool.'; static examples = [`$ oex example`]; From 390ad03b78130fec2cc852e92d719b40177cf0b7 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Wed, 26 Jun 2024 14:12:32 +0200 Subject: [PATCH 09/23] Rename network -> networkParams --- .../src/applications/check-eligibility.ts | 4 ++-- .../src/applications/publish-multisig-claim.ts | 4 ++-- .../claim-cli/src/applications/submit-claim.ts | 4 ++-- packages/claim-cli/src/commands/start/index.ts | 2 +- .../claim-cli/src/utils/build-account-list.ts | 4 ++-- .../src/utils/confirm-send-transaction.ts | 7 +++++-- packages/claim-cli/src/utils/endpoint.ts | 18 +++++++++--------- .../claim-cli/src/utils/get-private-key.ts | 8 ++++---- .../src/utils/{network.ts => networkParams.ts} | 6 +++--- packages/claim-cli/src/utils/sign-message.ts | 4 ++-- 10 files changed, 32 insertions(+), 29 deletions(-) rename packages/claim-cli/src/utils/{network.ts => networkParams.ts} (88%) diff --git a/packages/claim-cli/src/applications/check-eligibility.ts b/packages/claim-cli/src/applications/check-eligibility.ts index 6eda549..c3755f0 100644 --- a/packages/claim-cli/src/applications/check-eligibility.ts +++ b/packages/claim-cli/src/applications/check-eligibility.ts @@ -1,9 +1,9 @@ -import { Network } from '../utils/network'; +import { NetworkParams } from '../utils/networkParams'; import buildAccountList, { AccountListChoice } from '../utils/build-account-list'; import { fetchCheckEligibility } from '../utils/endpoint'; import { getInput } from '../utils/get-prompts'; -export default async function checkEligibility(networkParams: Network): Promise { +export default async function checkEligibility(networkParams: NetworkParams): Promise { const lskAddress = await getInput({ message: 'Your LSK Address' }); const result = await fetchCheckEligibility(lskAddress, networkParams); diff --git a/packages/claim-cli/src/applications/publish-multisig-claim.ts b/packages/claim-cli/src/applications/publish-multisig-claim.ts index 10bfa18..6b94085 100644 --- a/packages/claim-cli/src/applications/publish-multisig-claim.ts +++ b/packages/claim-cli/src/applications/publish-multisig-claim.ts @@ -1,6 +1,6 @@ import { ethers, ZeroHash } from 'ethers'; import { select } from '@inquirer/prompts'; -import { Network } from '../utils/network'; +import { NetworkParams } from '../utils/networkParams'; import { fetchCheckEligibility } from '../utils/endpoint'; import { getETHWallet } from '../utils/get-private-key'; import L2ClaimAbi from '../abi/L2Claim'; @@ -9,7 +9,7 @@ import { printPreview } from '../utils/print-table'; import { getInput } from '../utils/get-prompts'; export default async function publishMultisigClaim( - networkParams: Network, + networkParams: NetworkParams, address: string | null = null, ): Promise { const provider = new ethers.JsonRpcProvider(networkParams.rpc); diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index 365c0f4..4798a6e 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -2,7 +2,7 @@ import { select, confirm } from '@inquirer/prompts'; import * as crypto from '@liskhq/lisk-cryptography'; import { fetchCheckEligibility, fetchSubmitMultisig } from '../utils/endpoint'; import L2ClaimAbi from '../abi/L2Claim'; -import { Network } from '../utils/network'; +import { NetworkParams } from '../utils/networkParams'; import { ethers } from 'ethers'; import { getETHWallet, getLSKPrivateKey } from '../utils/get-private-key'; import { signMessage } from '../utils/sign-message'; @@ -13,7 +13,7 @@ import buildAccountList from '../utils/build-account-list'; import { append0x } from '../utils'; import { getInput } from '../utils/get-prompts'; -export default async function submitClaim(networkParams: Network): Promise { +export default async function submitClaim(networkParams: NetworkParams): Promise { const privateKey = await getLSKPrivateKey(); const lskAddressBytes = crypto.address.getAddressFromPrivateKey(privateKey); diff --git a/packages/claim-cli/src/commands/start/index.ts b/packages/claim-cli/src/commands/start/index.ts index 7981de7..e10e910 100644 --- a/packages/claim-cli/src/commands/start/index.ts +++ b/packages/claim-cli/src/commands/start/index.ts @@ -3,7 +3,7 @@ import { select } from '@inquirer/prompts'; import checkEligibility from '../../applications/check-eligibility'; import submitClaim from '../../applications/submit-claim'; import publishMultisigClaim from '../../applications/publish-multisig-claim'; -import { Mainnet, Testnet } from '../../utils/network'; +import { Mainnet, Testnet } from '../../utils/networkParams'; enum Choice { CHECK_ELIGIBILITY, diff --git a/packages/claim-cli/src/utils/build-account-list.ts b/packages/claim-cli/src/utils/build-account-list.ts index 91bf79e..c7391c6 100644 --- a/packages/claim-cli/src/utils/build-account-list.ts +++ b/packages/claim-cli/src/utils/build-account-list.ts @@ -1,4 +1,4 @@ -import { Network } from '../utils/network'; +import { NetworkParams } from './networkParams'; import { Account, CheckEligibilityResponse } from '../interfaces'; import { ethers } from 'ethers'; import L2ClaimAbi from '../abi/L2Claim'; @@ -11,7 +11,7 @@ export interface AccountListChoice { export default async function buildAccountList( result: CheckEligibilityResponse, - networkParams: Network, + networkParams: NetworkParams, ): Promise { const numOfSigned = result.signatures.reduce( (acc: { [destination: string]: number }, signature) => { diff --git a/packages/claim-cli/src/utils/confirm-send-transaction.ts b/packages/claim-cli/src/utils/confirm-send-transaction.ts index 6d90f6a..06c6031 100644 --- a/packages/claim-cli/src/utils/confirm-send-transaction.ts +++ b/packages/claim-cli/src/utils/confirm-send-transaction.ts @@ -1,11 +1,13 @@ import { BaseContractMethod, ethers, HDNodeWallet, Wallet } from 'ethers'; import { confirm } from '@inquirer/prompts'; import { getInput } from './get-prompts'; +import { NetworkParams } from './networkParams'; export default async function confirmSendTransaction( contractMethod: BaseContractMethod, args: unknown[], walletWithSigner: Wallet | HDNodeWallet, + networkParams: NetworkParams, ): Promise { const provider = walletWithSigner.provider; if (!provider) { @@ -13,7 +15,7 @@ export default async function confirmSendTransaction( } const feeData = await provider.getFeeData(); - const suggestedMaxFeePerGas = feeData.maxFeePerGas ?? BigInt(1); + const suggestedMaxFeePerGas = feeData.maxFeePerGas ?? networkParams.maxFeePerGas; const maxFeePerGas = BigInt( await getInput({ message: `Max Fee Per Gas (wei) (Suggested: ${suggestedMaxFeePerGas.toString()})`, @@ -21,7 +23,8 @@ export default async function confirmSendTransaction( }), ); - const suggestedMaxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? BigInt(1); + const suggestedMaxPriorityFeePerGas = + feeData.maxPriorityFeePerGas ?? networkParams.maxPriorityFeePerGas; const maxPriorityFeePerGas = BigInt( await getInput({ message: `Max Priority Fee Per Gas (wei) (Suggested: ${suggestedMaxPriorityFeePerGas.toString()})`, diff --git a/packages/claim-cli/src/utils/endpoint.ts b/packages/claim-cli/src/utils/endpoint.ts index c027334..3f9b6f1 100644 --- a/packages/claim-cli/src/utils/endpoint.ts +++ b/packages/claim-cli/src/utils/endpoint.ts @@ -4,11 +4,11 @@ import { JSONRPCSuccessResponse, SubmitMultisigResponse, } from '../interfaces'; -import { Network } from './network'; +import { NetworkParams } from './networkParams'; -export async function fetchCheckEligibility ( +export async function fetchCheckEligibility( lskAddress: string, - network: Network, + networkParams: NetworkParams, ): Promise { const jsonRPCRequest = { jsonrpc: '2.0', @@ -19,7 +19,7 @@ export async function fetchCheckEligibility ( id: 1, }; - const response = await fetch(network.api, { + const response = await fetch(networkParams.api, { method: 'POST', headers: { 'content-type': 'application/json', @@ -41,15 +41,15 @@ export async function fetchCheckEligibility ( } return result; -}; +} -export async function fetchSubmitMultisig ( +export async function fetchSubmitMultisig( lskAddress: string, destination: string, publicKey: string, r: string, s: string, - network: Network, + networkParams: NetworkParams, ): Promise { const jsonRPCRequest = { jsonrpc: '2.0', @@ -64,7 +64,7 @@ export async function fetchSubmitMultisig ( id: 1, }; - const response = await fetch(network.api, { + const response = await fetch(networkParams.api, { method: 'POST', headers: { 'content-type': 'application/json', @@ -86,4 +86,4 @@ export async function fetchSubmitMultisig ( } return result; -}; +} diff --git a/packages/claim-cli/src/utils/get-private-key.ts b/packages/claim-cli/src/utils/get-private-key.ts index e6ecf63..9db7275 100644 --- a/packages/claim-cli/src/utils/get-private-key.ts +++ b/packages/claim-cli/src/utils/get-private-key.ts @@ -19,7 +19,7 @@ const getSecretType = (wallet: string) => ], }); -export async function getLSKPrivateKeyFromMnemonic (): Promise { +export async function getLSKPrivateKeyFromMnemonic(): Promise { const mnemonic = await getInput({ message: 'Your Mnemonic' }); if (!Mnemonic.isValidMnemonic(mnemonic)) { console.log('Invalid Mnemonic, please check again.'); @@ -31,7 +31,7 @@ export async function getLSKPrivateKeyFromMnemonic (): Promise { return crypto.ed.getPrivateKeyFromPhraseAndPath(mnemonic.trim(), path); } -export async function getLSKPrivateKeyFromString (): Promise { +export async function getLSKPrivateKeyFromString(): Promise { const privKey = await getInput({ message: 'Your Private Key', }); @@ -47,12 +47,12 @@ export async function getLSKPrivateKeyFromString (): Promise { return Buffer.from(privKeyFormatted, 'hex'); } -export async function getLSKPrivateKey () { +export async function getLSKPrivateKey() { const type = await getSecretType('Lisk L1 Wallet'); return [getLSKPrivateKeyFromMnemonic, getLSKPrivateKeyFromString][type](); } -export async function getETHWalletFromMnemonic (): Promise { +export async function getETHWalletFromMnemonic(): Promise { const mnemonic = await getInput({ message: 'Your L2 Mnemonic' }); if (!Mnemonic.isValidMnemonic(mnemonic)) { console.log('Invalid Mnemonic, please check again.'); diff --git a/packages/claim-cli/src/utils/network.ts b/packages/claim-cli/src/utils/networkParams.ts similarity index 88% rename from packages/claim-cli/src/utils/network.ts rename to packages/claim-cli/src/utils/networkParams.ts index 2613a3c..dd7eecf 100644 --- a/packages/claim-cli/src/utils/network.ts +++ b/packages/claim-cli/src/utils/networkParams.ts @@ -1,4 +1,4 @@ -export interface Network { +export interface NetworkParams { api: string; rpc: string; l2Claim: string; @@ -12,7 +12,7 @@ export const Mainnet = { l2Claim: '0xD7BE2Fd98BfD64c1dfCf6c013fC593eF09219994', maxFeePerGas: 1002060n, maxPriorityFeePerGas: 1000000n, -} as Network; +} as NetworkParams; export const Testnet = { api: 'https://token-claim-api.lisk.com/rpc', @@ -20,4 +20,4 @@ export const Testnet = { l2Claim: '0x3D4190b08E3E30183f5AdE3A116f2534Ee3a4f94', maxFeePerGas: 1002060n, maxPriorityFeePerGas: 1000000n, -} as Network; +} as NetworkParams; diff --git a/packages/claim-cli/src/utils/sign-message.ts b/packages/claim-cli/src/utils/sign-message.ts index 314e1cf..97f653b 100644 --- a/packages/claim-cli/src/utils/sign-message.ts +++ b/packages/claim-cli/src/utils/sign-message.ts @@ -2,7 +2,7 @@ import * as tweetnacl from 'tweetnacl'; import { BYTES_9, remove0x } from './index'; import { AbiCoder, keccak256 } from 'ethers'; -export function signMessage (hash: string, destinationAddress: string, privKey: Buffer): string { +export function signMessage(hash: string, destinationAddress: string, privKey: Buffer): string { const abiCoder = new AbiCoder(); const message = @@ -11,4 +11,4 @@ export function signMessage (hash: string, destinationAddress: string, privKey: return Buffer.from( tweetnacl.sign.detached(Buffer.from(remove0x(message), 'hex'), privKey), ).toString('hex'); -} \ No newline at end of file +} From 599284e7fa04d1c5a341c6665a7dd582f9281c91 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Wed, 26 Jun 2024 17:00:03 +0200 Subject: [PATCH 10/23] Update confirmSendTransaction --- packages/claim-cli/src/applications/publish-multisig-claim.ts | 1 + packages/claim-cli/src/applications/submit-claim.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/claim-cli/src/applications/publish-multisig-claim.ts b/packages/claim-cli/src/applications/publish-multisig-claim.ts index 6b94085..d607fe9 100644 --- a/packages/claim-cli/src/applications/publish-multisig-claim.ts +++ b/packages/claim-cli/src/applications/publish-multisig-claim.ts @@ -134,5 +134,6 @@ export default async function publishMultisigClaim( signatures.map(signature => [signature.r, signature.s]), ], walletWithSigner, + networkParams, ); } diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index 4798a6e..2e310cc 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -62,6 +62,7 @@ export default async function submitClaim(networkParams: NetworkParams): Promise [append0x(signature.substring(0, 64)), append0x(signature.substring(64))], ], walletWithSigner, + networkParams, ); } else { // Multisig Claim From 126fb9bdadd955e71fab1b5dd4040d5c7deac015 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Wed, 26 Jun 2024 19:21:01 +0200 Subject: [PATCH 11/23] Rename networkParams to network-params --- packages/claim-cli/src/applications/check-eligibility.ts | 2 +- packages/claim-cli/src/applications/publish-multisig-claim.ts | 2 +- packages/claim-cli/src/applications/submit-claim.ts | 2 +- packages/claim-cli/src/commands/start/index.ts | 2 +- packages/claim-cli/src/utils/build-account-list.ts | 2 +- packages/claim-cli/src/utils/confirm-send-transaction.ts | 2 +- packages/claim-cli/src/utils/endpoint.ts | 2 +- .../claim-cli/src/utils/{networkParams.ts => network-params.ts} | 0 8 files changed, 7 insertions(+), 7 deletions(-) rename packages/claim-cli/src/utils/{networkParams.ts => network-params.ts} (100%) diff --git a/packages/claim-cli/src/applications/check-eligibility.ts b/packages/claim-cli/src/applications/check-eligibility.ts index c3755f0..f7f9ed5 100644 --- a/packages/claim-cli/src/applications/check-eligibility.ts +++ b/packages/claim-cli/src/applications/check-eligibility.ts @@ -1,4 +1,4 @@ -import { NetworkParams } from '../utils/networkParams'; +import { NetworkParams } from '../utils/network-params'; import buildAccountList, { AccountListChoice } from '../utils/build-account-list'; import { fetchCheckEligibility } from '../utils/endpoint'; import { getInput } from '../utils/get-prompts'; diff --git a/packages/claim-cli/src/applications/publish-multisig-claim.ts b/packages/claim-cli/src/applications/publish-multisig-claim.ts index d607fe9..bfc9568 100644 --- a/packages/claim-cli/src/applications/publish-multisig-claim.ts +++ b/packages/claim-cli/src/applications/publish-multisig-claim.ts @@ -1,6 +1,6 @@ import { ethers, ZeroHash } from 'ethers'; import { select } from '@inquirer/prompts'; -import { NetworkParams } from '../utils/networkParams'; +import { NetworkParams } from '../utils/network-params'; import { fetchCheckEligibility } from '../utils/endpoint'; import { getETHWallet } from '../utils/get-private-key'; import L2ClaimAbi from '../abi/L2Claim'; diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index 2e310cc..b5cd3fb 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -2,7 +2,7 @@ import { select, confirm } from '@inquirer/prompts'; import * as crypto from '@liskhq/lisk-cryptography'; import { fetchCheckEligibility, fetchSubmitMultisig } from '../utils/endpoint'; import L2ClaimAbi from '../abi/L2Claim'; -import { NetworkParams } from '../utils/networkParams'; +import { NetworkParams } from '../utils/network-params'; import { ethers } from 'ethers'; import { getETHWallet, getLSKPrivateKey } from '../utils/get-private-key'; import { signMessage } from '../utils/sign-message'; diff --git a/packages/claim-cli/src/commands/start/index.ts b/packages/claim-cli/src/commands/start/index.ts index e10e910..5d88e94 100644 --- a/packages/claim-cli/src/commands/start/index.ts +++ b/packages/claim-cli/src/commands/start/index.ts @@ -3,7 +3,7 @@ import { select } from '@inquirer/prompts'; import checkEligibility from '../../applications/check-eligibility'; import submitClaim from '../../applications/submit-claim'; import publishMultisigClaim from '../../applications/publish-multisig-claim'; -import { Mainnet, Testnet } from '../../utils/networkParams'; +import { Mainnet, Testnet } from '../../utils/network-params'; enum Choice { CHECK_ELIGIBILITY, diff --git a/packages/claim-cli/src/utils/build-account-list.ts b/packages/claim-cli/src/utils/build-account-list.ts index c7391c6..b3b95b4 100644 --- a/packages/claim-cli/src/utils/build-account-list.ts +++ b/packages/claim-cli/src/utils/build-account-list.ts @@ -1,4 +1,4 @@ -import { NetworkParams } from './networkParams'; +import { NetworkParams } from './network-params'; import { Account, CheckEligibilityResponse } from '../interfaces'; import { ethers } from 'ethers'; import L2ClaimAbi from '../abi/L2Claim'; diff --git a/packages/claim-cli/src/utils/confirm-send-transaction.ts b/packages/claim-cli/src/utils/confirm-send-transaction.ts index 06c6031..98ea58e 100644 --- a/packages/claim-cli/src/utils/confirm-send-transaction.ts +++ b/packages/claim-cli/src/utils/confirm-send-transaction.ts @@ -1,7 +1,7 @@ import { BaseContractMethod, ethers, HDNodeWallet, Wallet } from 'ethers'; import { confirm } from '@inquirer/prompts'; import { getInput } from './get-prompts'; -import { NetworkParams } from './networkParams'; +import { NetworkParams } from './network-params'; export default async function confirmSendTransaction( contractMethod: BaseContractMethod, diff --git a/packages/claim-cli/src/utils/endpoint.ts b/packages/claim-cli/src/utils/endpoint.ts index 3f9b6f1..69c1766 100644 --- a/packages/claim-cli/src/utils/endpoint.ts +++ b/packages/claim-cli/src/utils/endpoint.ts @@ -4,7 +4,7 @@ import { JSONRPCSuccessResponse, SubmitMultisigResponse, } from '../interfaces'; -import { NetworkParams } from './networkParams'; +import { NetworkParams } from './network-params'; export async function fetchCheckEligibility( lskAddress: string, diff --git a/packages/claim-cli/src/utils/networkParams.ts b/packages/claim-cli/src/utils/network-params.ts similarity index 100% rename from packages/claim-cli/src/utils/networkParams.ts rename to packages/claim-cli/src/utils/network-params.ts From 7512da900539a8fd172d080db9b4d9968a97c73d Mon Sep 17 00:00:00 2001 From: Franco NG Date: Wed, 26 Jun 2024 20:33:20 +0200 Subject: [PATCH 12/23] Update success comment --- packages/claim-cli/src/applications/submit-claim.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index b5cd3fb..f4f2467 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -115,7 +115,7 @@ export default async function submitClaim(networkParams: NetworkParams): Promise ); if (submitResult.success) { - console.log('Success Submitted Claim!'); + console.log('Success Submitted Signature to Claim API!'); } if ( From 44531af491694d46b65eca845aca55bedcd7d4d6 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Thu, 27 Jun 2024 10:48:52 +0200 Subject: [PATCH 13/23] Rearrange utils and import order --- .../src/applications/check-eligibility.ts | 11 +++++--- .../applications/publish-multisig-claim.ts | 15 ++++++----- .../src/applications/submit-claim.ts | 25 ++++++++++------- .../claim-cli/src/commands/start/index.ts | 2 +- .../claim-cli/src/utils/build-account-list.ts | 2 +- .../src/utils/confirm-send-transaction.ts | 2 +- .../claim-cli/src/utils/get-private-key.ts | 2 +- packages/claim-cli/src/utils/index.ts | 27 +++++++------------ packages/claim-cli/src/utils/prefix.ts | 18 +++++++++++++ packages/claim-cli/src/utils/sign-message.ts | 2 +- .../test/utils/get-private-key.test.ts | 3 ++- .../claim-cli/test/utils/sign-message.test.ts | 3 +-- 12 files changed, 66 insertions(+), 46 deletions(-) create mode 100644 packages/claim-cli/src/utils/prefix.ts diff --git a/packages/claim-cli/src/applications/check-eligibility.ts b/packages/claim-cli/src/applications/check-eligibility.ts index f7f9ed5..c63954a 100644 --- a/packages/claim-cli/src/applications/check-eligibility.ts +++ b/packages/claim-cli/src/applications/check-eligibility.ts @@ -1,7 +1,10 @@ -import { NetworkParams } from '../utils/network-params'; -import buildAccountList, { AccountListChoice } from '../utils/build-account-list'; -import { fetchCheckEligibility } from '../utils/endpoint'; -import { getInput } from '../utils/get-prompts'; +import { + NetworkParams, + buildAccountList, + AccountListChoice, + fetchCheckEligibility, + getInput, +} from '../utils/'; export default async function checkEligibility(networkParams: NetworkParams): Promise { const lskAddress = await getInput({ message: 'Your LSK Address' }); diff --git a/packages/claim-cli/src/applications/publish-multisig-claim.ts b/packages/claim-cli/src/applications/publish-multisig-claim.ts index bfc9568..f0fbebc 100644 --- a/packages/claim-cli/src/applications/publish-multisig-claim.ts +++ b/packages/claim-cli/src/applications/publish-multisig-claim.ts @@ -1,12 +1,15 @@ import { ethers, ZeroHash } from 'ethers'; import { select } from '@inquirer/prompts'; -import { NetworkParams } from '../utils/network-params'; -import { fetchCheckEligibility } from '../utils/endpoint'; -import { getETHWallet } from '../utils/get-private-key'; + import L2ClaimAbi from '../abi/L2Claim'; -import confirmSendTransaction from '../utils/confirm-send-transaction'; -import { printPreview } from '../utils/print-table'; -import { getInput } from '../utils/get-prompts'; +import { + NetworkParams, + fetchCheckEligibility, + getETHWallet, + confirmSendTransaction, + printPreview, + getInput, +} from '../utils/'; export default async function publishMultisigClaim( networkParams: NetworkParams, diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index f4f2467..8c7d203 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -1,17 +1,22 @@ import { select, confirm } from '@inquirer/prompts'; import * as crypto from '@liskhq/lisk-cryptography'; -import { fetchCheckEligibility, fetchSubmitMultisig } from '../utils/endpoint'; -import L2ClaimAbi from '../abi/L2Claim'; -import { NetworkParams } from '../utils/network-params'; import { ethers } from 'ethers'; -import { getETHWallet, getLSKPrivateKey } from '../utils/get-private-key'; -import { signMessage } from '../utils/sign-message'; -import { printPreview } from '../utils/print-table'; -import confirmSendTransaction from '../utils/confirm-send-transaction'; + +import L2ClaimAbi from '../abi/L2Claim'; import publishMultisigClaim from './publish-multisig-claim'; -import buildAccountList from '../utils/build-account-list'; -import { append0x } from '../utils'; -import { getInput } from '../utils/get-prompts'; +import { + buildAccountList, + fetchCheckEligibility, + fetchSubmitMultisig, + NetworkParams, + getETHWallet, + getLSKPrivateKey, + signMessage, + printPreview, + confirmSendTransaction, + append0x, + getInput, +} from '../utils'; export default async function submitClaim(networkParams: NetworkParams): Promise { const privateKey = await getLSKPrivateKey(); diff --git a/packages/claim-cli/src/commands/start/index.ts b/packages/claim-cli/src/commands/start/index.ts index 5d88e94..6a971fa 100644 --- a/packages/claim-cli/src/commands/start/index.ts +++ b/packages/claim-cli/src/commands/start/index.ts @@ -3,7 +3,7 @@ import { select } from '@inquirer/prompts'; import checkEligibility from '../../applications/check-eligibility'; import submitClaim from '../../applications/submit-claim'; import publishMultisigClaim from '../../applications/publish-multisig-claim'; -import { Mainnet, Testnet } from '../../utils/network-params'; +import { Mainnet, Testnet } from '../../utils/'; enum Choice { CHECK_ELIGIBILITY, diff --git a/packages/claim-cli/src/utils/build-account-list.ts b/packages/claim-cli/src/utils/build-account-list.ts index b3b95b4..ad48707 100644 --- a/packages/claim-cli/src/utils/build-account-list.ts +++ b/packages/claim-cli/src/utils/build-account-list.ts @@ -9,7 +9,7 @@ export interface AccountListChoice { claimed?: string; } -export default async function buildAccountList( +export async function buildAccountList( result: CheckEligibilityResponse, networkParams: NetworkParams, ): Promise { diff --git a/packages/claim-cli/src/utils/confirm-send-transaction.ts b/packages/claim-cli/src/utils/confirm-send-transaction.ts index 98ea58e..d8bde33 100644 --- a/packages/claim-cli/src/utils/confirm-send-transaction.ts +++ b/packages/claim-cli/src/utils/confirm-send-transaction.ts @@ -3,7 +3,7 @@ import { confirm } from '@inquirer/prompts'; import { getInput } from './get-prompts'; import { NetworkParams } from './network-params'; -export default async function confirmSendTransaction( +export async function confirmSendTransaction( contractMethod: BaseContractMethod, args: unknown[], walletWithSigner: Wallet | HDNodeWallet, diff --git a/packages/claim-cli/src/utils/get-private-key.ts b/packages/claim-cli/src/utils/get-private-key.ts index 9db7275..459845d 100644 --- a/packages/claim-cli/src/utils/get-private-key.ts +++ b/packages/claim-cli/src/utils/get-private-key.ts @@ -1,7 +1,7 @@ import { select } from '@inquirer/prompts'; import * as crypto from '@liskhq/lisk-cryptography'; import { Mnemonic, HDNodeWallet, Wallet } from 'ethers'; -import { remove0x } from './index'; +import { remove0x } from './prefix'; import { getInput, getPassword } from './get-prompts'; enum SecretType { diff --git a/packages/claim-cli/src/utils/index.ts b/packages/claim-cli/src/utils/index.ts index 1da1976..0f7bc82 100644 --- a/packages/claim-cli/src/utils/index.ts +++ b/packages/claim-cli/src/utils/index.ts @@ -1,18 +1,9 @@ -export const BYTES_9 = '000000000000000000'; - -export function remove0x(input: string): string { - if (input.substring(0, 2) === '0x') { - return input.substring(2); - } - return input; -} - -export function append0x(input: string | Buffer): string { - if (input instanceof Buffer) { - input = input.toString('hex'); - } - if (input.substring(0, 2) === '0x') { - return input; - } - return '0x' + input; -} +export * from './build-account-list'; +export * from './confirm-send-transaction'; +export * from './endpoint'; +export * from './get-private-key'; +export * from './get-prompts'; +export * from './network-params'; +export * from './prefix'; +export * from './print-table'; +export * from './sign-message'; diff --git a/packages/claim-cli/src/utils/prefix.ts b/packages/claim-cli/src/utils/prefix.ts new file mode 100644 index 0000000..1da1976 --- /dev/null +++ b/packages/claim-cli/src/utils/prefix.ts @@ -0,0 +1,18 @@ +export const BYTES_9 = '000000000000000000'; + +export function remove0x(input: string): string { + if (input.substring(0, 2) === '0x') { + return input.substring(2); + } + return input; +} + +export function append0x(input: string | Buffer): string { + if (input instanceof Buffer) { + input = input.toString('hex'); + } + if (input.substring(0, 2) === '0x') { + return input; + } + return '0x' + input; +} diff --git a/packages/claim-cli/src/utils/sign-message.ts b/packages/claim-cli/src/utils/sign-message.ts index 97f653b..60e6092 100644 --- a/packages/claim-cli/src/utils/sign-message.ts +++ b/packages/claim-cli/src/utils/sign-message.ts @@ -1,5 +1,5 @@ import * as tweetnacl from 'tweetnacl'; -import { BYTES_9, remove0x } from './index'; +import { BYTES_9, remove0x } from './prefix'; import { AbiCoder, keccak256 } from 'ethers'; export function signMessage(hash: string, destinationAddress: string, privKey: Buffer): string { diff --git a/packages/claim-cli/test/utils/get-private-key.test.ts b/packages/claim-cli/test/utils/get-private-key.test.ts index 43f362e..1b46df7 100644 --- a/packages/claim-cli/test/utils/get-private-key.test.ts +++ b/packages/claim-cli/test/utils/get-private-key.test.ts @@ -5,12 +5,13 @@ import * as crypto from '@liskhq/lisk-cryptography'; import { HDNodeWallet, Wallet } from 'ethers'; import * as getPrompts from '../../src/utils/get-prompts'; + import { getETHWalletFromMnemonic, getETHWalletKeyFromString, getLSKPrivateKeyFromMnemonic, getLSKPrivateKeyFromString, -} from '../../src/utils/get-private-key'; +} from '../../src/utils'; describe('getPrivateKey', () => { const { expect } = chai; diff --git a/packages/claim-cli/test/utils/sign-message.test.ts b/packages/claim-cli/test/utils/sign-message.test.ts index 30d5e20..b766b1f 100644 --- a/packages/claim-cli/test/utils/sign-message.test.ts +++ b/packages/claim-cli/test/utils/sign-message.test.ts @@ -1,8 +1,7 @@ import * as tweetnacl from 'tweetnacl'; import { AbiCoder, keccak256 } from 'ethers'; -import { append0x, BYTES_9 } from '../../src/utils'; -import { signMessage } from '../../src/utils/sign-message'; +import { append0x, BYTES_9, signMessage } from '../../src/utils'; const abiCoder = new AbiCoder(); From f10b343b47d1b8207bbb77af54413c228a58d0fd Mon Sep 17 00:00:00 2001 From: Franco NG Date: Mon, 1 Jul 2024 14:12:06 +0200 Subject: [PATCH 14/23] Apply suggestions from code review Co-authored-by: Himanshu Nagda <69150921+nagdahimanshu@users.noreply.github.com> --- packages/claim-cli/src/applications/check-eligibility.ts | 6 +----- .../claim-cli/src/applications/publish-multisig-claim.ts | 3 +-- packages/claim-cli/src/applications/submit-claim.ts | 2 +- packages/claim-cli/src/utils/build-account-list.ts | 2 +- packages/claim-cli/src/utils/sign-message.ts | 2 +- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/claim-cli/src/applications/check-eligibility.ts b/packages/claim-cli/src/applications/check-eligibility.ts index c63954a..bc993d9 100644 --- a/packages/claim-cli/src/applications/check-eligibility.ts +++ b/packages/claim-cli/src/applications/check-eligibility.ts @@ -25,11 +25,7 @@ export default async function checkEligibility(networkParams: NetworkParams): Pr }, account, ) => { - if (account.claimed) { - acc.claimed.push(account); - } else { - acc.unclaimed.push(account); - } + account.claimed ? acc.claimed.push(account) : acc.unclaimed.push(account); return acc; }, { diff --git a/packages/claim-cli/src/applications/publish-multisig-claim.ts b/packages/claim-cli/src/applications/publish-multisig-claim.ts index f0fbebc..d9f5d0f 100644 --- a/packages/claim-cli/src/applications/publish-multisig-claim.ts +++ b/packages/claim-cli/src/applications/publish-multisig-claim.ts @@ -17,8 +17,6 @@ export default async function publishMultisigClaim( ): Promise { const provider = new ethers.JsonRpcProvider(networkParams.rpc); const lskAddress = address || (await getInput({ message: 'Multisig Address to be published' })); - const claimContract = new ethers.Contract(networkParams.l2Claim, L2ClaimAbi, provider); - const result = await fetchCheckEligibility(lskAddress, networkParams); if (!result.account) { console.log(`Address ${lskAddress} has no eligibility.`); @@ -30,6 +28,7 @@ export default async function publishMultisigClaim( return process.exit(1); } + const claimContract = new ethers.Contract(networkParams.l2Claim, L2ClaimAbi, provider); const claimedTo = await claimContract.claimedTo(result.account.address); if (claimedTo !== ethers.ZeroAddress) { console.log(`Address ${lskAddress} has already been claimed.`); diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index 8c7d203..e446b70 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -126,7 +126,7 @@ export default async function submitClaim(networkParams: NetworkParams): Promise if ( submitResult.ready && (await confirm({ - message: `Address: ${claimAccount.lskAddress} has reach sufficient signatures, proceed to publish?`, + message: `Address: ${claimAccount.lskAddress} has reached sufficient signatures, proceed to publish?`, })) ) { await publishMultisigClaim(networkParams, claimAccount.lskAddress); diff --git a/packages/claim-cli/src/utils/build-account-list.ts b/packages/claim-cli/src/utils/build-account-list.ts index ad48707..4566519 100644 --- a/packages/claim-cli/src/utils/build-account-list.ts +++ b/packages/claim-cli/src/utils/build-account-list.ts @@ -1,6 +1,6 @@ +import { ethers } from 'ethers'; import { NetworkParams } from './network-params'; import { Account, CheckEligibilityResponse } from '../interfaces'; -import { ethers } from 'ethers'; import L2ClaimAbi from '../abi/L2Claim'; export interface AccountListChoice { diff --git a/packages/claim-cli/src/utils/sign-message.ts b/packages/claim-cli/src/utils/sign-message.ts index 60e6092..7086fc0 100644 --- a/packages/claim-cli/src/utils/sign-message.ts +++ b/packages/claim-cli/src/utils/sign-message.ts @@ -1,6 +1,6 @@ import * as tweetnacl from 'tweetnacl'; -import { BYTES_9, remove0x } from './prefix'; import { AbiCoder, keccak256 } from 'ethers'; +import { BYTES_9, remove0x } from './prefix'; export function signMessage(hash: string, destinationAddress: string, privKey: Buffer): string { const abiCoder = new AbiCoder(); From 610452777e6b772952d8c189d69ba1d2e40d5e05 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Mon, 1 Jul 2024 14:15:38 +0200 Subject: [PATCH 15/23] Apply suggestions from code review Co-authored-by: Himanshu Nagda <69150921+nagdahimanshu@users.noreply.github.com> --- packages/claim-cli/README.md | 2 +- packages/claim-cli/package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/claim-cli/README.md b/packages/claim-cli/README.md index 7dcde93..cfd6544 100644 --- a/packages/claim-cli/README.md +++ b/packages/claim-cli/README.md @@ -1,6 +1,6 @@ # Claim CLI -This library is a tool to Claim Lisk token without the need to UI. This tool can provider the following services: +This library is a tool to claim Lisk tokens without the need for UI. This tool can provide the following services: - Check Eligibility - Submit a Claim diff --git a/packages/claim-cli/package.json b/packages/claim-cli/package.json index 41a5bd9..e3c266f 100644 --- a/packages/claim-cli/package.json +++ b/packages/claim-cli/package.json @@ -42,9 +42,9 @@ }, "dependencies": { "@inquirer/prompts": "^5.0.4", - "@liskhq/lisk-codec": "^0.5.0", + "@liskhq/lisk-codec": "0.5.0", "@liskhq/lisk-cryptography": "4.1.0", - "@liskhq/lisk-db": "^0.3.10", + "@liskhq/lisk-db": "0.3.10", "@oclif/core": "^3.26.6", "@oclif/plugin-help": "^6", "@openzeppelin/merkle-tree": "^1.0.6", From ffe705a34128337ce60be180dee751e26b850215 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Mon, 1 Jul 2024 14:21:04 +0200 Subject: [PATCH 16/23] Update suggestions from review --- packages/claim-cli/README.md | 2 +- packages/claim-cli/src/applications/check-eligibility.ts | 9 ++------- packages/claim-cli/src/interfaces.ts | 6 ++++++ packages/claim-cli/src/utils/build-account-list.ts | 8 +------- packages/claim-cli/src/utils/network-params.ts | 8 ++++---- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/claim-cli/README.md b/packages/claim-cli/README.md index cfd6544..cb0706d 100644 --- a/packages/claim-cli/README.md +++ b/packages/claim-cli/README.md @@ -11,7 +11,7 @@ This library is a tool to claim Lisk tokens without the need for UI. This tool c ## Run ``` -# Enter claim-cli package +# Navigate to claim-cli package cd packages/claim-cli # Install dependencies diff --git a/packages/claim-cli/src/applications/check-eligibility.ts b/packages/claim-cli/src/applications/check-eligibility.ts index bc993d9..4066e92 100644 --- a/packages/claim-cli/src/applications/check-eligibility.ts +++ b/packages/claim-cli/src/applications/check-eligibility.ts @@ -1,10 +1,5 @@ -import { - NetworkParams, - buildAccountList, - AccountListChoice, - fetchCheckEligibility, - getInput, -} from '../utils/'; +import { NetworkParams, buildAccountList, fetchCheckEligibility, getInput } from '../utils/'; +import { AccountListChoice } from '../interfaces'; export default async function checkEligibility(networkParams: NetworkParams): Promise { const lskAddress = await getInput({ message: 'Your LSK Address' }); diff --git a/packages/claim-cli/src/interfaces.ts b/packages/claim-cli/src/interfaces.ts index 49604a7..fbd655e 100644 --- a/packages/claim-cli/src/interfaces.ts +++ b/packages/claim-cli/src/interfaces.ts @@ -45,3 +45,9 @@ export interface Signature { r: string; s: string; } + +export interface AccountListChoice { + name: string; + value: Account; + claimed?: string; +} diff --git a/packages/claim-cli/src/utils/build-account-list.ts b/packages/claim-cli/src/utils/build-account-list.ts index 4566519..2deec50 100644 --- a/packages/claim-cli/src/utils/build-account-list.ts +++ b/packages/claim-cli/src/utils/build-account-list.ts @@ -1,14 +1,8 @@ import { ethers } from 'ethers'; import { NetworkParams } from './network-params'; -import { Account, CheckEligibilityResponse } from '../interfaces'; +import { AccountListChoice, CheckEligibilityResponse } from '../interfaces'; import L2ClaimAbi from '../abi/L2Claim'; -export interface AccountListChoice { - name: string; - value: Account; - claimed?: string; -} - export async function buildAccountList( result: CheckEligibilityResponse, networkParams: NetworkParams, diff --git a/packages/claim-cli/src/utils/network-params.ts b/packages/claim-cli/src/utils/network-params.ts index dd7eecf..ac2b7fc 100644 --- a/packages/claim-cli/src/utils/network-params.ts +++ b/packages/claim-cli/src/utils/network-params.ts @@ -10,14 +10,14 @@ export const Mainnet = { api: 'https://token-claim-api.lisk.com/rpc', rpc: 'https://rpc.api.lisk.com', l2Claim: '0xD7BE2Fd98BfD64c1dfCf6c013fC593eF09219994', - maxFeePerGas: 1002060n, - maxPriorityFeePerGas: 1000000n, + maxFeePerGas: BigInt(1002060), + maxPriorityFeePerGas: BigInt(1000000), } as NetworkParams; export const Testnet = { api: 'https://token-claim-api.lisk.com/rpc', rpc: 'https://rpc.sepolia-api.lisk.com', l2Claim: '0x3D4190b08E3E30183f5AdE3A116f2534Ee3a4f94', - maxFeePerGas: 1002060n, - maxPriorityFeePerGas: 1000000n, + maxFeePerGas: BigInt(1002060), + maxPriorityFeePerGas: BigInt(1000000), } as NetworkParams; From 50ca98a8f5e512b1f43a674645f2eacd7c70375a Mon Sep 17 00:00:00 2001 From: Franco NG Date: Mon, 1 Jul 2024 14:26:31 +0200 Subject: [PATCH 17/23] Update yarn.lock --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4dcc800..6cb0e8b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1114,7 +1114,7 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@liskhq/lisk-codec@^0.5.0": +"@liskhq/lisk-codec@0.5.0", "@liskhq/lisk-codec@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@liskhq/lisk-codec/-/lisk-codec-0.5.0.tgz#39ae557c30d2cc11b449f6db34054928fcf98e78" integrity sha512-IW8s3s1JvHZMx2SmMh8MJyUE7D9gJGJyJq5s8xxWik4IOTN/zRdNFK2m1qIgL+6T8SchEKS4P8r/Xpnl5KUeiw== @@ -1133,7 +1133,7 @@ hash-wasm "4.9.0" tweetnacl "1.0.3" -"@liskhq/lisk-db@^0.3.10": +"@liskhq/lisk-db@0.3.10", "@liskhq/lisk-db@^0.3.10": version "0.3.10" resolved "https://registry.yarnpkg.com/@liskhq/lisk-db/-/lisk-db-0.3.10.tgz#af0a96925a3a76a6b05bef0a31dacc2b2357bfb0" integrity sha512-1vG6qCCbSw016peckgcl8vxwJMke0kUi9a4OCUmp+OgzcroNmc/EHR/psOVaBqW3Jrmcg7BNpY4d3vGsFy1tsg== From f737a4417ccec72e678838b0e858e30b8f41232c Mon Sep 17 00:00:00 2001 From: Franco NG Date: Mon, 1 Jul 2024 23:04:42 +0200 Subject: [PATCH 18/23] Add Exodus Wallet support --- .../claim-cli/src/utils/get-private-key.ts | 14 +++++-- .../test/utils/get-private-key.test.ts | 37 +++++++++++++------ 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/packages/claim-cli/src/utils/get-private-key.ts b/packages/claim-cli/src/utils/get-private-key.ts index 459845d..4f4e3b1 100644 --- a/packages/claim-cli/src/utils/get-private-key.ts +++ b/packages/claim-cli/src/utils/get-private-key.ts @@ -37,13 +37,21 @@ export async function getLSKPrivateKeyFromString(): Promise { }); const privKeyFormatted = remove0x(privKey); - - if (!privKeyFormatted.match(/^[A-Fa-f0-9]{128}$/)) { + if ( + !privKeyFormatted.match(/^[A-Fa-f0-9]{64}$/) && + !privKeyFormatted.match(/^[A-Fa-f0-9]{128}$/) + ) { console.log( - 'Invalid Private Key, please check again. Private Key should be 128-character long.', + 'Invalid Private Key, please check again. Private Key should be 64 or 128 characters long.', ); return process.exit(1); } + + // Convert 64-character long private key to 128, by constructing public key (For Exodus Wallet) + if (privKeyFormatted.length === 64) { + const pubKey = crypto.ed.getPublicKeyFromPrivateKey(Buffer.from(privKeyFormatted, 'hex')); + return Buffer.concat([Buffer.from(privKeyFormatted, 'hex'), pubKey]); + } return Buffer.from(privKeyFormatted, 'hex'); } diff --git a/packages/claim-cli/test/utils/get-private-key.test.ts b/packages/claim-cli/test/utils/get-private-key.test.ts index 1b46df7..58c7343 100644 --- a/packages/claim-cli/test/utils/get-private-key.test.ts +++ b/packages/claim-cli/test/utils/get-private-key.test.ts @@ -63,32 +63,47 @@ describe('getPrivateKey', () => { }); describe('getLSKPrivateKeyFromString', () => { - const validPrivateKeyString = new Array(128).fill('f').join(''); + const privateKey = crypto.utils.getRandomBytes(32); + const publicKey = crypto.ed.getPublicKeyFromPrivateKey(privateKey); it('should throw when private key has invalid format', async () => { - inputStub.onCall(0).resolves(validPrivateKeyString + 'f'); + inputStub.onCall(0).resolves(privateKey.toString('hex') + 'f'); await getLSKPrivateKeyFromString(); expect( printStub.calledWith( - 'Invalid Private Key, please check again. Private Key should be 128-character long.', + 'Invalid Private Key, please check again. Private Key should be 64 or 128 characters long.', ), ).to.be.true; expect(processExitStub.calledWith(1)).to.be.true; }); - it('should get valid private key with 0x prefix', async () => { - inputStub.onCall(0).resolves('0x' + validPrivateKeyString); + it('should get valid 64-character-long private key with 0x prefix', async () => { + inputStub.onCall(0).resolves('0x' + privateKey.toString('hex')); - const privateKey = await getLSKPrivateKeyFromString(); - expect(privateKey).to.be.deep.eq(Buffer.from(validPrivateKeyString, 'hex')); + const promptPrivateKey = await getLSKPrivateKeyFromString(); + expect(promptPrivateKey).to.be.deep.eq(Buffer.concat([privateKey, publicKey])); }); - it('should get valid private key without 0x', async () => { - inputStub.onCall(0).resolves(validPrivateKeyString); + it('should get valid 128-character-long private key with 0x prefix', async () => { + inputStub.onCall(0).resolves('0x' + privateKey.toString('hex') + publicKey.toString('hex')); + + const promptPrivateKey = await getLSKPrivateKeyFromString(); + expect(promptPrivateKey).to.be.deep.eq(Buffer.concat([privateKey, publicKey])); + }); + + it('should get valid 64-character-long private key without 0x', async () => { + inputStub.onCall(0).resolves(privateKey.toString('hex')); + + const promptPrivateKey = await getLSKPrivateKeyFromString(); + expect(promptPrivateKey).to.be.deep.eq(Buffer.concat([privateKey, publicKey])); + }); + + it('should get valid 128-character-long private key without 0x', async () => { + inputStub.onCall(0).resolves(privateKey.toString('hex') + publicKey.toString('hex')); - const privateKey = await getLSKPrivateKeyFromString(); - expect(privateKey).to.be.deep.eq(Buffer.from(validPrivateKeyString, 'hex')); + const promptPrivateKey = await getLSKPrivateKeyFromString(); + expect(promptPrivateKey).to.be.deep.eq(Buffer.concat([privateKey, publicKey])); }); }); From 6133c3dcadab59ed5432c86fda19c0d6c81535a0 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Tue, 2 Jul 2024 16:13:30 +0200 Subject: [PATCH 19/23] Rename Lisk L1 to Lisk v4 --- packages/claim-cli/src/applications/submit-claim.ts | 2 +- packages/claim-cli/src/utils/get-private-key.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index e446b70..0014488 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -23,7 +23,7 @@ export default async function submitClaim(networkParams: NetworkParams): Promise const lskAddressBytes = crypto.address.getAddressFromPrivateKey(privateKey); const lskAddress = crypto.address.getLisk32AddressFromAddress(lskAddressBytes); - console.log('Representing LSK L1 Address:', lskAddress); + console.log('Representing LSK v4 Address:', lskAddress); const result = await fetchCheckEligibility(lskAddress, networkParams); if (!result.account && result.multisigAccounts.length === 0) { diff --git a/packages/claim-cli/src/utils/get-private-key.ts b/packages/claim-cli/src/utils/get-private-key.ts index 4f4e3b1..d9158a4 100644 --- a/packages/claim-cli/src/utils/get-private-key.ts +++ b/packages/claim-cli/src/utils/get-private-key.ts @@ -56,7 +56,7 @@ export async function getLSKPrivateKeyFromString(): Promise { } export async function getLSKPrivateKey() { - const type = await getSecretType('Lisk L1 Wallet'); + const type = await getSecretType('Lisk v4 Wallet'); return [getLSKPrivateKeyFromMnemonic, getLSKPrivateKeyFromString][type](); } From a5c743559df9575bfc442b466745dc96ef641c52 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Thu, 4 Jul 2024 18:51:56 +0200 Subject: [PATCH 20/23] Update Testnet API endpoint --- packages/claim-cli/src/utils/network-params.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/claim-cli/src/utils/network-params.ts b/packages/claim-cli/src/utils/network-params.ts index ac2b7fc..d2c38a0 100644 --- a/packages/claim-cli/src/utils/network-params.ts +++ b/packages/claim-cli/src/utils/network-params.ts @@ -15,7 +15,7 @@ export const Mainnet = { } as NetworkParams; export const Testnet = { - api: 'https://token-claim-api.lisk.com/rpc', + api: 'https://sepolia-token-claim-api.lisk.com/rpc', rpc: 'https://rpc.sepolia-api.lisk.com', l2Claim: '0x3D4190b08E3E30183f5AdE3A116f2534Ee3a4f94', maxFeePerGas: BigInt(1002060), From 88d3d6ccb964cbb8d438adfd6221645c79d08c54 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Fri, 5 Jul 2024 15:28:50 +0200 Subject: [PATCH 21/23] Update suggestions according to PR review --- packages/claim-cli/package.json | 8 +-- .../src/applications/submit-claim.ts | 20 ++++++- .../claim-cli/src/commands/start/index.ts | 51 ------------------ packages/claim-cli/src/index.ts | 52 ++++++++++++++++++- .../src/utils/confirm-send-transaction.ts | 2 +- .../claim-cli/src/utils/get-private-key.ts | 8 +-- 6 files changed, 79 insertions(+), 62 deletions(-) delete mode 100644 packages/claim-cli/src/commands/start/index.ts diff --git a/packages/claim-cli/package.json b/packages/claim-cli/package.json index e3c266f..fdcecc5 100644 --- a/packages/claim-cli/package.json +++ b/packages/claim-cli/package.json @@ -34,11 +34,13 @@ "oclif": { "bin": "claim-cli", "dirname": "claim-cli", - "commands": "./dist/commands", + "commands": { + "strategy": "single", + "target": "./dist/index.js" + }, "plugins": [ "@oclif/plugin-help" - ], - "topicSeparator": " " + ] }, "dependencies": { "@inquirer/prompts": "^5.0.4", diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index 0014488..8f8c246 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -27,13 +27,29 @@ export default async function submitClaim(networkParams: NetworkParams): Promise const result = await fetchCheckEligibility(lskAddress, networkParams); if (!result.account && result.multisigAccounts.length === 0) { - console.log(`No Eligible Claim for Address: ${lskAddress}`); + console.log(`No Eligible Claim for Address: ${lskAddress}.`); + return process.exit(1); + } + + const choices = await buildAccountList(result, networkParams); + + if ( + choices.reduce((numOfClaimed, choice) => numOfClaimed + (choice.claimed ? 1 : 0), 0) == + choices.length + ) { + for (const [index, choice] of choices.entries()) { + console.log(`${index + 1}: ${choice.value.lskAddress} ${choice.claimed}`); + } + console.log(`All accounts under ${lskAddress} have successfully been claimed.`); return process.exit(1); } const claimAccount = await select({ message: 'Choose Claim Address', - choices: await buildAccountList(result, networkParams), + choices: choices.map(account => ({ + ...account, + disabled: !!account.claimed, + })), }); if (claimAccount.numberOfSignatures === 0) { diff --git a/packages/claim-cli/src/commands/start/index.ts b/packages/claim-cli/src/commands/start/index.ts deleted file mode 100644 index 6a971fa..0000000 --- a/packages/claim-cli/src/commands/start/index.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Flags, Command } from '@oclif/core'; -import { select } from '@inquirer/prompts'; -import checkEligibility from '../../applications/check-eligibility'; -import submitClaim from '../../applications/submit-claim'; -import publishMultisigClaim from '../../applications/publish-multisig-claim'; -import { Mainnet, Testnet } from '../../utils/'; - -enum Choice { - CHECK_ELIGIBILITY, - SUBMIT_CLAIM, - SUBMIT_MULTISIG_CLAIM, -} - -export default class Start extends Command { - static flags = { - network: Flags.string({ - description: 'Operating network for the tool to run.', - required: false, - default: 'mainnet', - options: ['mainnet', 'testnet'], - }), - }; - - static description = 'Start Lisk Claim CLI Tool.'; - - static examples = [`$ oex example`]; - - async run(): Promise { - const { flags } = await this.parse(Start); - const { network } = flags; - - console.log(`Welcome to Lisk Migration CLI (Running on "${network}" Network)`); - const answer = await select({ - message: 'Please enter your choices', - choices: [ - { name: 'Check my Eligibility', value: Choice.CHECK_ELIGIBILITY }, - { name: 'Submit a Claim', value: Choice.SUBMIT_CLAIM }, - { - name: 'Publish a Multisig claim with completed signatures onchain', - value: Choice.SUBMIT_MULTISIG_CLAIM, - }, - ], - }); - - const networkParams = { - mainnet: Mainnet, - testnet: Testnet, - }[network as 'mainnet' | 'testnet']; - await [checkEligibility, submitClaim, publishMultisigClaim][answer](networkParams); - } -} diff --git a/packages/claim-cli/src/index.ts b/packages/claim-cli/src/index.ts index d620e70..ec239bc 100644 --- a/packages/claim-cli/src/index.ts +++ b/packages/claim-cli/src/index.ts @@ -1 +1,51 @@ -export { run } from '@oclif/core'; +import { Flags, Command } from '@oclif/core'; +import { select } from '@inquirer/prompts'; +import checkEligibility from './applications/check-eligibility'; +import submitClaim from './applications/submit-claim'; +import publishMultisigClaim from './applications/publish-multisig-claim'; +import { Mainnet, Testnet } from './utils/'; + +enum Choice { + CHECK_ELIGIBILITY, + SUBMIT_CLAIM, + SUBMIT_MULTISIG_CLAIM, +} + +export default class Start extends Command { + static flags = { + network: Flags.string({ + description: 'Operating network for the tool to run.', + required: false, + default: 'mainnet', + options: ['mainnet', 'testnet'], + }), + }; + + static description = 'Start Lisk Claim CLI Tool.'; + + static examples = [`$ oex claim-cli`]; + + async run(): Promise { + const { flags } = await this.parse(Start); + const { network } = flags; + + console.log(`Welcome to Lisk Migration CLI (Running on "${network}" Network)`); + const answer = await select({ + message: 'Please enter your choices', + choices: [ + { name: 'Check my Eligibility', value: Choice.CHECK_ELIGIBILITY }, + { name: 'Submit a Claim', value: Choice.SUBMIT_CLAIM }, + { + name: 'Publish a Multisig claim with completed signatures onchain', + value: Choice.SUBMIT_MULTISIG_CLAIM, + }, + ], + }); + + const networkParams = { + mainnet: Mainnet, + testnet: Testnet, + }[network as 'mainnet' | 'testnet']; + await [checkEligibility, submitClaim, publishMultisigClaim][answer](networkParams); + } +} diff --git a/packages/claim-cli/src/utils/confirm-send-transaction.ts b/packages/claim-cli/src/utils/confirm-send-transaction.ts index d8bde33..cfac628 100644 --- a/packages/claim-cli/src/utils/confirm-send-transaction.ts +++ b/packages/claim-cli/src/utils/confirm-send-transaction.ts @@ -38,7 +38,7 @@ export async function confirmSendTransaction( console.log( `Estimated Network Fee (${estimatedGas} * ${maxFeePerGas} wei) = ${ethers.formatUnits(estimatedFee)} ETH.`, ); - console.log(`Your Balance: ${ethers.formatUnits(ethBalance)} ETH`); + console.log(`Your Balance: ${ethers.formatUnits(ethBalance)} ETH.`); if (estimatedFee > ethBalance) { console.log('Insufficient Balance for the Transaction.'); return process.exit(1); diff --git a/packages/claim-cli/src/utils/get-private-key.ts b/packages/claim-cli/src/utils/get-private-key.ts index d9158a4..5740442 100644 --- a/packages/claim-cli/src/utils/get-private-key.ts +++ b/packages/claim-cli/src/utils/get-private-key.ts @@ -20,7 +20,7 @@ const getSecretType = (wallet: string) => }); export async function getLSKPrivateKeyFromMnemonic(): Promise { - const mnemonic = await getInput({ message: 'Your Mnemonic' }); + const mnemonic = await getPassword({ message: 'Your Mnemonic' }); if (!Mnemonic.isValidMnemonic(mnemonic)) { console.log('Invalid Mnemonic, please check again.'); return process.exit(1); @@ -32,7 +32,7 @@ export async function getLSKPrivateKeyFromMnemonic(): Promise { } export async function getLSKPrivateKeyFromString(): Promise { - const privKey = await getInput({ + const privKey = await getPassword({ message: 'Your Private Key', }); @@ -61,7 +61,7 @@ export async function getLSKPrivateKey() { } export async function getETHWalletFromMnemonic(): Promise { - const mnemonic = await getInput({ message: 'Your L2 Mnemonic' }); + const mnemonic = await getPassword({ message: 'Your L2 Mnemonic' }); if (!Mnemonic.isValidMnemonic(mnemonic)) { console.log('Invalid Mnemonic, please check again.'); return process.exit(1); @@ -74,7 +74,7 @@ export async function getETHWalletFromMnemonic(): Promise { } export const getETHWalletKeyFromString = async (): Promise => { - const privKey = await getInput({ + const privKey = await getPassword({ message: 'Your Private Key', }); From ff5fce27b64926397572940442245a4cb4c5d343 Mon Sep 17 00:00:00 2001 From: Franco NG Date: Fri, 5 Jul 2024 15:59:41 +0200 Subject: [PATCH 22/23] Update test cases --- .../test/utils/get-private-key.test.ts | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/claim-cli/test/utils/get-private-key.test.ts b/packages/claim-cli/test/utils/get-private-key.test.ts index 58c7343..63eaaaa 100644 --- a/packages/claim-cli/test/utils/get-private-key.test.ts +++ b/packages/claim-cli/test/utils/get-private-key.test.ts @@ -52,8 +52,8 @@ describe('getPrivateKey', () => { }); it('should get valid private key from mnemonic and path', async () => { - inputStub.onCall(0).resolves(correctMnemonic); - inputStub.onCall(1).resolves(lskPath); + passwordStub.onCall(0).resolves(correctMnemonic); + inputStub.onCall(0).resolves(lskPath); const privateKey = await getLSKPrivateKeyFromMnemonic(); expect(privateKey).to.be.deep.eq( @@ -67,7 +67,7 @@ describe('getPrivateKey', () => { const publicKey = crypto.ed.getPublicKeyFromPrivateKey(privateKey); it('should throw when private key has invalid format', async () => { - inputStub.onCall(0).resolves(privateKey.toString('hex') + 'f'); + passwordStub.onCall(0).resolves(privateKey.toString('hex') + 'f'); await getLSKPrivateKeyFromString(); expect( @@ -79,28 +79,30 @@ describe('getPrivateKey', () => { }); it('should get valid 64-character-long private key with 0x prefix', async () => { - inputStub.onCall(0).resolves('0x' + privateKey.toString('hex')); + passwordStub.onCall(0).resolves('0x' + privateKey.toString('hex')); const promptPrivateKey = await getLSKPrivateKeyFromString(); expect(promptPrivateKey).to.be.deep.eq(Buffer.concat([privateKey, publicKey])); }); it('should get valid 128-character-long private key with 0x prefix', async () => { - inputStub.onCall(0).resolves('0x' + privateKey.toString('hex') + publicKey.toString('hex')); + passwordStub + .onCall(0) + .resolves('0x' + privateKey.toString('hex') + publicKey.toString('hex')); const promptPrivateKey = await getLSKPrivateKeyFromString(); expect(promptPrivateKey).to.be.deep.eq(Buffer.concat([privateKey, publicKey])); }); it('should get valid 64-character-long private key without 0x', async () => { - inputStub.onCall(0).resolves(privateKey.toString('hex')); + passwordStub.onCall(0).resolves(privateKey.toString('hex')); const promptPrivateKey = await getLSKPrivateKeyFromString(); expect(promptPrivateKey).to.be.deep.eq(Buffer.concat([privateKey, publicKey])); }); it('should get valid 128-character-long private key without 0x', async () => { - inputStub.onCall(0).resolves(privateKey.toString('hex') + publicKey.toString('hex')); + passwordStub.onCall(0).resolves(privateKey.toString('hex') + publicKey.toString('hex')); const promptPrivateKey = await getLSKPrivateKeyFromString(); expect(promptPrivateKey).to.be.deep.eq(Buffer.concat([privateKey, publicKey])); @@ -109,7 +111,7 @@ describe('getPrivateKey', () => { describe('getETHWalletFromMnemonic', () => { it('should throw when mnemonic is not valid', async () => { - inputStub.onCall(0).resolves(badMnemonic); + passwordStub.onCall(0).resolves(badMnemonic); await getETHWalletFromMnemonic(); expect(printStub.calledWith('Invalid Mnemonic, please check again.')).to.be.true; @@ -119,9 +121,9 @@ describe('getPrivateKey', () => { it('should get valid private key from mnemonic, passphrase and path', async () => { const passphrase = 'foobar'; - inputStub.onCall(0).resolves(correctMnemonic); - passwordStub.onCall(0).resolves(passphrase); - inputStub.onCall(1).resolves(ethPath); + passwordStub.onCall(0).resolves(correctMnemonic); + passwordStub.onCall(1).resolves(passphrase); + inputStub.onCall(0).resolves(ethPath); const wallet = await getETHWalletFromMnemonic(); @@ -134,7 +136,7 @@ describe('getPrivateKey', () => { const validPrivateKeyString = new Array(64).fill('e').join(''); it('should throw when private key has invalid format', async () => { - inputStub.onCall(0).resolves(validPrivateKeyString + 'f'); + passwordStub.onCall(0).resolves(validPrivateKeyString + 'f'); await getETHWalletKeyFromString(); expect( @@ -146,14 +148,14 @@ describe('getPrivateKey', () => { }); it('should get valid private key with 0x prefix', async () => { - inputStub.onCall(0).resolves('0x' + validPrivateKeyString); + passwordStub.onCall(0).resolves('0x' + validPrivateKeyString); const wallet = await getETHWalletKeyFromString(); expect(wallet).to.be.deep.eq(new Wallet(validPrivateKeyString)); }); it('should get valid private key without 0x', async () => { - inputStub.onCall(0).resolves(validPrivateKeyString); + passwordStub.onCall(0).resolves(validPrivateKeyString); const wallet = await getETHWalletKeyFromString(); expect(wallet).to.be.deep.eq(new Wallet(validPrivateKeyString)); From dba51ced12ec1167bc7df8822bdd685d9de4380c Mon Sep 17 00:00:00 2001 From: Franco NG Date: Tue, 9 Jul 2024 15:54:37 +0200 Subject: [PATCH 23/23] Use throw instead of process.exit --- .../src/applications/check-eligibility.ts | 3 +- .../applications/publish-multisig-claim.ts | 11 +++--- .../src/applications/submit-claim.ts | 6 +-- .../src/utils/confirm-send-transaction.ts | 8 ++-- packages/claim-cli/src/utils/endpoint.ts | 12 ++---- .../claim-cli/src/utils/get-private-key.ts | 12 ++---- .../test/utils/get-private-key.test.ts | 39 +++++++------------ 7 files changed, 33 insertions(+), 58 deletions(-) diff --git a/packages/claim-cli/src/applications/check-eligibility.ts b/packages/claim-cli/src/applications/check-eligibility.ts index 4066e92..4040408 100644 --- a/packages/claim-cli/src/applications/check-eligibility.ts +++ b/packages/claim-cli/src/applications/check-eligibility.ts @@ -6,8 +6,7 @@ export default async function checkEligibility(networkParams: NetworkParams): Pr const result = await fetchCheckEligibility(lskAddress, networkParams); if (!result.account && result.multisigAccounts.length === 0) { - console.log(`No Eligible Claim for Address: ${lskAddress}.`); - return process.exit(1); + throw new Error(`No Eligible Claim for Address: ${lskAddress}.`); } const accountList = await buildAccountList(result, networkParams); diff --git a/packages/claim-cli/src/applications/publish-multisig-claim.ts b/packages/claim-cli/src/applications/publish-multisig-claim.ts index d9f5d0f..e4a44e7 100644 --- a/packages/claim-cli/src/applications/publish-multisig-claim.ts +++ b/packages/claim-cli/src/applications/publish-multisig-claim.ts @@ -19,20 +19,19 @@ export default async function publishMultisigClaim( const lskAddress = address || (await getInput({ message: 'Multisig Address to be published' })); const result = await fetchCheckEligibility(lskAddress, networkParams); if (!result.account) { - console.log(`Address ${lskAddress} has no eligibility.`); - return process.exit(1); + throw new Error(`Address ${lskAddress} has no eligibility.`); } if (!result.account.ready) { - console.log(`Address ${lskAddress} has insufficient signatures.`); - return process.exit(1); + throw new Error( + `Address ${lskAddress} is not a Multisig address, or has insufficient signatures.`, + ); } const claimContract = new ethers.Contract(networkParams.l2Claim, L2ClaimAbi, provider); const claimedTo = await claimContract.claimedTo(result.account.address); if (claimedTo !== ethers.ZeroAddress) { - console.log(`Address ${lskAddress} has already been claimed.`); - return process.exit(1); + throw new Error(`Address ${lskAddress} has already been claimed.`); } const wallet = await getETHWallet(); diff --git a/packages/claim-cli/src/applications/submit-claim.ts b/packages/claim-cli/src/applications/submit-claim.ts index 8f8c246..59774f4 100644 --- a/packages/claim-cli/src/applications/submit-claim.ts +++ b/packages/claim-cli/src/applications/submit-claim.ts @@ -27,8 +27,7 @@ export default async function submitClaim(networkParams: NetworkParams): Promise const result = await fetchCheckEligibility(lskAddress, networkParams); if (!result.account && result.multisigAccounts.length === 0) { - console.log(`No Eligible Claim for Address: ${lskAddress}.`); - return process.exit(1); + throw new Error(`No Eligible Claim for Address: ${lskAddress}.`); } const choices = await buildAccountList(result, networkParams); @@ -40,8 +39,7 @@ export default async function submitClaim(networkParams: NetworkParams): Promise for (const [index, choice] of choices.entries()) { console.log(`${index + 1}: ${choice.value.lskAddress} ${choice.claimed}`); } - console.log(`All accounts under ${lskAddress} have successfully been claimed.`); - return process.exit(1); + throw new Error(`All accounts under ${lskAddress} have successfully been claimed.`); } const claimAccount = await select({ diff --git a/packages/claim-cli/src/utils/confirm-send-transaction.ts b/packages/claim-cli/src/utils/confirm-send-transaction.ts index cfac628..0a63dd7 100644 --- a/packages/claim-cli/src/utils/confirm-send-transaction.ts +++ b/packages/claim-cli/src/utils/confirm-send-transaction.ts @@ -11,7 +11,7 @@ export async function confirmSendTransaction( ): Promise { const provider = walletWithSigner.provider; if (!provider) { - return process.exit(1); + throw new Error('Provider not found.'); } const feeData = await provider.getFeeData(); @@ -40,12 +40,10 @@ export async function confirmSendTransaction( ); console.log(`Your Balance: ${ethers.formatUnits(ethBalance)} ETH.`); if (estimatedFee > ethBalance) { - console.log('Insufficient Balance for the Transaction.'); - return process.exit(1); + throw new Error('Insufficient Balance for the Transaction.'); } if (!(await confirm({ message: 'Confirm to Send Transaction', default: false }))) { - console.log('User Cancelled Submission.'); - return process.exit(1); + throw new Error('User Cancelled Submission.'); } const tx = await contractMethod(...args, { diff --git a/packages/claim-cli/src/utils/endpoint.ts b/packages/claim-cli/src/utils/endpoint.ts index 69c1766..afdc6dc 100644 --- a/packages/claim-cli/src/utils/endpoint.ts +++ b/packages/claim-cli/src/utils/endpoint.ts @@ -28,16 +28,14 @@ export async function fetchCheckEligibility( }); if (response.status !== 200) { - console.log('Network Error, please try again later.'); - return process.exit(1); + throw new Error('Network Error, please try again later.'); } const { result, error } = (await response.json()) as | JSONRPCSuccessResponse | JSONRPCErrorResponse; if (error) { - console.log('Claim Endpoint returned error:', error.message); - return process.exit(1); + throw Error(`Claim Endpoint returned error: ${error.message}`); } return result; @@ -73,16 +71,14 @@ export async function fetchSubmitMultisig( }); if (response.status !== 200) { - console.log('Network Error, please try again later.'); - return process.exit(1); + throw new Error('Network Error, please try again later.'); } const { result, error } = (await response.json()) as | JSONRPCSuccessResponse | JSONRPCErrorResponse; if (error) { - console.log('Claim Endpoint returned error:', error.message); - return process.exit(1); + throw new Error(`Claim Endpoint returned error: ${error.message}`); } return result; diff --git a/packages/claim-cli/src/utils/get-private-key.ts b/packages/claim-cli/src/utils/get-private-key.ts index 5740442..99758be 100644 --- a/packages/claim-cli/src/utils/get-private-key.ts +++ b/packages/claim-cli/src/utils/get-private-key.ts @@ -22,8 +22,7 @@ const getSecretType = (wallet: string) => export async function getLSKPrivateKeyFromMnemonic(): Promise { const mnemonic = await getPassword({ message: 'Your Mnemonic' }); if (!Mnemonic.isValidMnemonic(mnemonic)) { - console.log('Invalid Mnemonic, please check again.'); - return process.exit(1); + throw new Error('Invalid Mnemonic, please check again.'); } const path = await getInput({ message: 'Path', default: "m/44'/134'/0'" }); @@ -41,10 +40,9 @@ export async function getLSKPrivateKeyFromString(): Promise { !privKeyFormatted.match(/^[A-Fa-f0-9]{64}$/) && !privKeyFormatted.match(/^[A-Fa-f0-9]{128}$/) ) { - console.log( + throw new Error( 'Invalid Private Key, please check again. Private Key should be 64 or 128 characters long.', ); - return process.exit(1); } // Convert 64-character long private key to 128, by constructing public key (For Exodus Wallet) @@ -63,8 +61,7 @@ export async function getLSKPrivateKey() { export async function getETHWalletFromMnemonic(): Promise { const mnemonic = await getPassword({ message: 'Your L2 Mnemonic' }); if (!Mnemonic.isValidMnemonic(mnemonic)) { - console.log('Invalid Mnemonic, please check again.'); - return process.exit(1); + throw new Error('Invalid Mnemonic, please check again.'); } const passphrase = await getPassword({ message: 'BIP39 Passphrase (Optional)' }); @@ -81,10 +78,9 @@ export const getETHWalletKeyFromString = async (): Promise => { const privKeyFormatted = remove0x(privKey); if (!privKeyFormatted.match(/^[A-Fa-f0-9]{64}$/)) { - console.log( + throw new Error( 'Invalid Private Key, please check again. Private Key should be 64-character long.', ); - return process.exit(1); } return new Wallet(privKey); }; diff --git a/packages/claim-cli/test/utils/get-private-key.test.ts b/packages/claim-cli/test/utils/get-private-key.test.ts index 63eaaaa..5c930c1 100644 --- a/packages/claim-cli/test/utils/get-private-key.test.ts +++ b/packages/claim-cli/test/utils/get-private-key.test.ts @@ -20,7 +20,6 @@ describe('getPrivateKey', () => { let inputStub: sinon.SinonStub; let passwordStub: sinon.SinonStub; let printStub: sinon.SinonStub; - let processExitStub: sinon.SinonStub; // Invalid const badMnemonic = new Array(12).fill('test').join(' '); @@ -32,23 +31,21 @@ describe('getPrivateKey', () => { inputStub = sinon.stub(getPrompts, 'getInput'); passwordStub = sinon.stub(getPrompts, 'getPassword'); printStub = sinon.stub(console, 'log'); - processExitStub = sinon.stub(process, 'exit'); }); afterEach(() => { inputStub.restore(); passwordStub.restore(); printStub.restore(); - processExitStub.restore(); }); describe('getLSKPrivateKeyFromMnemonic', () => { it('should throw when mnemonic is not valid', async () => { inputStub.onCall(0).resolves(badMnemonic); - await getLSKPrivateKeyFromMnemonic(); - expect(printStub.calledWith('Invalid Mnemonic, please check again.')).to.be.true; - expect(processExitStub.calledWith(1)).to.be.true; + await expect(getLSKPrivateKeyFromMnemonic()).to.eventually.be.rejectedWith( + 'Invalid Mnemonic, please check again.', + ); }); it('should get valid private key from mnemonic and path', async () => { @@ -68,14 +65,10 @@ describe('getPrivateKey', () => { it('should throw when private key has invalid format', async () => { passwordStub.onCall(0).resolves(privateKey.toString('hex') + 'f'); - await getLSKPrivateKeyFromString(); - - expect( - printStub.calledWith( - 'Invalid Private Key, please check again. Private Key should be 64 or 128 characters long.', - ), - ).to.be.true; - expect(processExitStub.calledWith(1)).to.be.true; + + await expect(getLSKPrivateKeyFromString()).to.eventually.be.rejectedWith( + 'Invalid Private Key, please check again. Private Key should be 64 or 128 characters long.', + ); }); it('should get valid 64-character-long private key with 0x prefix', async () => { @@ -113,9 +106,9 @@ describe('getPrivateKey', () => { it('should throw when mnemonic is not valid', async () => { passwordStub.onCall(0).resolves(badMnemonic); - await getETHWalletFromMnemonic(); - expect(printStub.calledWith('Invalid Mnemonic, please check again.')).to.be.true; - expect(processExitStub.calledWith(1)).to.be.true; + await expect(getETHWalletFromMnemonic()).to.eventually.be.rejectedWith( + 'Invalid Mnemonic, please check again.', + ); }); it('should get valid private key from mnemonic, passphrase and path', async () => { @@ -137,14 +130,10 @@ describe('getPrivateKey', () => { it('should throw when private key has invalid format', async () => { passwordStub.onCall(0).resolves(validPrivateKeyString + 'f'); - await getETHWalletKeyFromString(); - - expect( - printStub.calledWith( - 'Invalid Private Key, please check again. Private Key should be 64-character long.', - ), - ).to.be.true; - expect(processExitStub.calledWith(1)).to.be.true; + + await expect(getETHWalletKeyFromString()).to.eventually.be.rejectedWith( + 'Invalid Private Key, please check again. Private Key should be 64-character long.', + ); }); it('should get valid private key with 0x prefix', async () => {