diff --git a/.env.example b/.env.example index 3fe0299..23b449a 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,18 @@ MNEMONIC="test test test test test test test test test test test junk" -PRIVATE_KEY="" -INFURA_API_KEY="" -ETHERSCAN_API_KEY="" \ No newline at end of file +PRIVATE_KEY= +LEDGER_ACCOUNT= + +INFURA_API_KEY= +ETHEREUM_MAINNET_RPC_URL= +ETHEREUM_SEPOLIA_RPC_URL= + +ETHERSCAN_API_KEY= + +INITIAL_OWNER= +TIMELOCK_ADMIN_ADDRESS= +TIMELOCK_MIN_DELAY= +TIMELOCK_ADDRESS= +TOKEN_PROXY_ADDRESS= +TOKEN_NEW_IMPLEMENTATION_ADDRESS= + +SAFE_API_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index fcf7561..309f477 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ coverage artifacts/ .vscode typechain-types +safe-config*.json \ No newline at end of file diff --git a/README.md b/README.md index 16c1507..bf7157e 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,11 @@ Update `scripts/input.json` with your multisig address: ### 2. Generate Deterministic Deployment Transactions ```bash -npm run generate +npm run generate:deployment:sepolia +npm run generate:deployment:mainnet ``` -This creates `scripts/output.json` with deployment transactions that use CREATE2 for deterministic addresses: +This creates `scripts/deployment/output-deployment.json` with deployment transactions that use CREATE2 for deterministic addresses: - TimelockController: starts with `0x00ad` - Implementation: starts with `0x001b` @@ -64,6 +65,86 @@ This verifies all contracts on Etherscan and validates deployment parameters. 3. **TransparentUpgradeableProxy** - Proxy pointing to implementation 4. **ProxyAdmin** - Owned by TimelockController (deployed by proxy constructor) +## Deploy with Safe Multisig + +1. Create `scripts/safe/safe-config-.json` for the configuration of the Safe account filling the owners, threshold, private key for the sender of the tx, etc. for your new Safe Multisig. + + Example for Ethereum Sepolia: + ``` + { + "chain": { + "id": 11155111, + "rpcUrls": { + "default": { + "http": ["https://eth-sepolia.g.alchemy.com/v2/"] + } + } + }, + "owners": ["ethAddress1", "ethAddress2"], + "privateKeySender": "privateKeySender", + "threshold": 2, + "apiKey": "your_safe_api_key", + "safeAddress": "", + "contractNetworks": { + "11155111": { + "safeProxyFactoryAddress": "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2", + "multiSendAddress": "0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761", + "multiSendCallOnlyAddress": "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D", + "fallbackHandlerAddress": "0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4", + "safeSingletonAddress": "0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552" + } + } + } + ``` + + To get Safe API Key you can get information [here](https://docs.safe.global/core-api/how-to-use-api-keys) + +2. Deploy new Safe. Signer will be first owner by default (safeConfig.privateKeys[0] in `scripts/safe/deploySafe.ts`) + ```bash + npm run safe:deploy:sepolia + ``` +3. Copy the safe address deployed to the `safeAddress` parameter of your `safe-config-.json` +4. Configure `input.json` with `MULTISIG` value properly. Be sure the address is a checksum address. +5. Generate the transactions for the deployment that will be needed to be executed by each owner of the Safe Multisig. + ```bash + npm run generate:deployment:sepolia + ``` + This will generate `output-deployment.json` with the transactions to be proposed, confirmed and executed later. +6. Propose first transaction for the owner configured in your `hardhat.config.ts` (change `PRIVATE_KEY` or `LEDGER_ACCOUNT` from your `.env` for each owner) with `scripts/safe/deployment/proposeTransaction.ts`. The transaction will be confirmed also for this owner. + ``` + const transactionIndex = 0; // Index of the transaction to propose + ``` + + Then execute: + + ```bash + npm run safe:propose:deployment:transaction:sepolia + ``` +7. Confirm the first transaction with the other owners needed for the threshold (change `PRIVATE_KEY` or `LEDGER_ACCOUNT` from your `.env` for each owner) checking `transactionIndex` in `scripts/safe/deployment/confirmTransaction.ts`. + ``` + const transactionIndex = 0; // Index of the transaction to confirm + ``` + + Then execute: + + ```bash + npm run safe:confirm:deployment:transaction:sepolia + ``` +8. Once you have confirmed the first transaction with the different owners you can execute the transaction. Check `transactionIndex` in `scripts/safe/deployment/executeTransaction.ts`. + ``` + const transactionIndex = 0; // Index of the transaction to execute + + ``` + + ```bash + npm run safe:execute:deployment:transaction:sepolia + ``` +9. Once done the process for the first transaction proceed in the same way from the step `6.` but for the second (transactionIndex = 1) and third transaction (transactionIndex = 2) for the deployment in order to complete the deployment with the Safe Multisig. +10. Verify the upgrade + ```bash + npm run verify:deployment:sepolia + ``` + ## Upgrade Process All upgrades go through the Timelock (2-day minimum delay): @@ -73,6 +154,79 @@ All upgrades go through the Timelock (2-day minimum delay): 3. Wait 2 days (minimum delay) 4. Execute upgrade transaction +## Upgrade with Safe Multisig + +1. Having `scripts/safe/safe-config-.json` and `input.json` from deployment, fill `TIMELOCK_PROXY` and `BILLIONS_TOKEN_PROXY` with checksum address in `input.json` and follow next steps. +2. Generate the transactions for the upgrade that will be needed to be executed by each owner of the Safe Multisig. + ```bash + npm run generate:upgrade:sepolia + ``` + This will generate `output-schedule-upgrade.json` and `output-execute-upgrade.json` with the transactions to be proposed, confirmed and executed later. +3. Propose first transaction for the owner configured in your `hardhat.config.ts` (change `PRIVATE_KEY` or `LEDGER_ACCOUNT` from your `.env` for each owner) with `scripts/safe/schedule-upgrade/proposeTransaction.ts`. The transaction will be confirmed also for this owner. + ``` + const transactionIndex = 0; // Index of the transaction to propose + ``` + + Then execute: + + ```bash + npm run safe:propose:schedule:upgrade:transaction:sepolia + ``` +4. Confirm the first transaction with the other owners needed for the threshold (change `PRIVATE_KEY` or `LEDGER_ACCOUNT` from your `.env` for each owner) checking `transactionIndex` in `scripts/safe/schedule-upgrade/confirmTransaction.ts`. + ``` + const transactionIndex = 0; // Index of the transaction to confirm + ``` + + Then execute: + + ```bash + npm run safe:confirm:schedule:upgrade:transaction:sepolia + ``` +5. Once you have confirmed the first transaction with the different owners you can execute the transaction. Check `transactionIndex` in `scripts/safe/schedule-upgrade/executeTransaction.ts`. + ``` + const transactionIndex = 0; // Index of the transaction to execute + + ``` + + ```bash + npm run safe:execute:schedule:upgrade:transaction:sepolia + ``` +6. Once done the process for the first transaction proceed in the same way from the step `3.` but for the second (transactionIndex = 1) for the schedule-upgrade in order to complete the schedule-upgrade with the Safe Multisig. +7. Wait 2 days (minimum delay) for the execution of the upgrade. +8. Propose first transaction for the owner configured in your `hardhat.config.ts` (change `PRIVATE_KEY` or `LEDGER_ACCOUNT` from your `.env` for each owner) with `scripts/safe/execute-upgrade/proposeTransaction.ts`. The transaction will be confirmed also for this owner. + ``` + const transactionIndex = 0; // Index of the transaction to propose + ``` + + Then execute: + + ```bash + npm run safe:propose:execute:upgrade:transaction:sepolia + ``` +9. Confirm the first transaction with the other owners needed for the threshold (change `PRIVATE_KEY` or `LEDGER_ACCOUNT` from your `.env` for each owner) checking `transactionIndex` in `scripts/safe/execute-upgrade/confirmTransaction.ts`. + ``` + const transactionIndex = 0; // Index of the transaction to confirm + ``` + + Then execute: + + ```bash + npm run safe:confirm:execute:upgrade:transaction:sepolia + ``` +10. Once you have confirmed the first transaction with the different owners you can execute the transaction. Check `transactionIndex` in `scripts/safe/execute-upgrade/executeTransaction.ts`. + ``` + const transactionIndex = 0; // Index of the transaction to execute + + ``` + + ```bash + npm run safe:execute:execute:upgradetransaction:sepolia + ``` +11. Verify the upgrade + ```bash + npm run verify:upgrade:sepolia + ``` + ## Architecture ``` @@ -97,3 +251,11 @@ TimelockController (2-day delay) - ProxyAdmin controlled by Timelock - No emergency pause or admin functions on token - All token supply minted at initialization + +## Ethereum Mainnet Deployment +The deployment in Ethereum Mainnet was done with the Safe Multisig `0xa31c18d0e9EBab1cB4d4B96193DCb44058F4bb75`. +| Smart contract | Address | +|:-----------------------:|:------------------------------------------:| +| **TimelockController** | [0x00ad9eb03caf7c5e616ed843d46294e1932cc8ca](https://etherscan.io/address/0x00ad9eb03caf7c5e616ed843d46294e1932cc8ca) | +| **BillionsNetworkToken (proxy)** | [0xb1110919016846972056ab995054d65560d5f05e](https://etherscan.io/address/0xb1110919016846972056ab995054d65560d5f05e) | + diff --git a/hardhat.config.ts b/hardhat.config.ts index a7690c7..4d5e63f 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,10 +1,20 @@ import type { HardhatUserConfig } from 'hardhat/config'; import '@openzeppelin/hardhat-upgrades'; import '@nomicfoundation/hardhat-toolbox'; +import '@nomicfoundation/hardhat-ledger'; import 'dotenv/config'; +import { HttpNetworkUserConfig } from 'hardhat/types'; const PRIVATE_KEY = process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : undefined; const MNEMONIC = process.env.MNEMONIC ? { mnemonic: process.env.MNEMONIC } : undefined; +const LEDGER_ACCOUNT = process.env.LEDGER_ACCOUNT ? [process.env.LEDGER_ACCOUNT] : undefined; + +const sharedNetworkConfig: HttpNetworkUserConfig = {}; +if (LEDGER_ACCOUNT) { + sharedNetworkConfig.ledgerAccounts = LEDGER_ACCOUNT; +} else { + sharedNetworkConfig.accounts = PRIVATE_KEY || MNEMONIC; +} const config: HardhatUserConfig = { solidity: { @@ -18,21 +28,73 @@ const config: HardhatUserConfig = { }, networks: { sepolia: { - url: process.env.SEPOLIA_RPC_URL || `https://sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`, - accounts: PRIVATE_KEY || MNEMONIC, + ...sharedNetworkConfig, + url: process.env.ETHEREUM_SEPOLIA_RPC_URL || `https://sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`, chainId: 11155111, }, mainnet: { - url: process.env.MAINNET_RPC_URL || `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`, - accounts: PRIVATE_KEY || MNEMONIC, + ...sharedNetworkConfig, + url: process.env.ETHEREUM_MAINNET_RPC_URL || `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`, chainId: 1, }, + 'billions-testnet': { + ...sharedNetworkConfig, + url: process.env.BILLIONS_TESTNET_RPC_URL, + chainId: 6913, + }, + 'billions-mainnet': { + ...sharedNetworkConfig, + url: process.env.BILLIONS_MAINNET_RPC_URL, + chainId: 6913, + }, + // hardhat: { + // chainId: 11155111, + // forking: { + // url: `${process.env.ETHEREUM_SEPOLIA_RPC_URL}`, + // }, + // chains: { + // 11155111: { + // hardforkHistory: { + // london: 100000, + // }, + // }, + // }, + // accounts: [ + // { + // privateKey: process.env.PRIVATE_KEY as string, + // balance: "1000000000000000000000000", + // }, + // ], + // }, localhost: { url: 'http://127.0.0.1:8545', }, }, etherscan: { - apiKey: process.env.ETHERSCAN_API_KEY, + apiKey: { + sepolia: `${process.env.ETHERSCAN_API_KEY}`, + mainnet: `${process.env.ETHERSCAN_API_KEY}`, + 'billions-testnet': 'abc', + 'billions-mainnet': 'abc', + }, + customChains: [ + { + network: 'billions-testnet', + chainId: 6913, + urls: { + apiURL: 'https://billions-testnet-blockscout.eu-north-2.gateway.fm/api/', + browserURL: 'https://billions-testnet-blockscout.eu-north-2.gateway.fm', + }, + }, + { + network: 'billions-mainnet', + chainId: 45056, + urls: { + apiURL: 'https://billions-blockscout.eu-north-2.gateway.fm/api/', + browserURL: 'https://billions-blockscout.eu-north-2.gateway.fm', + }, + }, + ], }, paths: { sources: './contracts', diff --git a/helpers/safe.ts b/helpers/safe.ts new file mode 100644 index 0000000..fdc3fc4 --- /dev/null +++ b/helpers/safe.ts @@ -0,0 +1,350 @@ +import hre, { ethers } from 'hardhat'; +import { defineChain } from 'viem'; +import fs from 'fs'; +import path from 'path'; +import SafeApiKit from '@safe-global/api-kit'; +import Safe from '@safe-global/protocol-kit'; +import { MetaTransactionData, OperationType } from '@safe-global/types-kit'; +import { signHash } from './utils'; + +export async function confirmTransaction(transactionIndex: number, transactionsOutputPath: string) { + // Get signer to sign the transaction hash for the confirmation + // This allows to configure LEDGER if we need for signing with hardhat plugin + const [signer] = await ethers.getSigners(); + + const network = hre.network.name; + const safeConfigPath = path.join(__dirname, `../scripts/safe/safe-config-${network}.json`); + + if (!fs.existsSync(safeConfigPath)) { + throw new Error(`Safe config file not found: ${safeConfigPath}`); + } + const safeConfig = JSON.parse(fs.readFileSync(safeConfigPath, 'utf-8')); + if (!safeConfig.chain) { + throw new Error(`chain not found in safe config file: ${safeConfigPath}`); + } + if (!safeConfig.safeAddress) { + throw new Error(`safeAddress not found in safe config file: ${safeConfigPath}`); + } + if (!safeConfig.privateKeySender) { + throw new Error('privateKeySender not set in safe config file'); + } + const chain = defineChain(safeConfig.chain); + + const protocolKit = await Safe.init({ + provider: chain.rpcUrls.default.http[0], + signer: safeConfig.privateKeySender, + safeAddress: safeConfig.safeAddress, + }); + + const safeAddress = await protocolKit.getAddress(); + console.log('Safe Address:', safeAddress); + + // Read transactions from the specified file + const transactions = JSON.parse(fs.readFileSync(transactionsOutputPath, 'utf8')); + if (transactionIndex >= transactions.length) { + throw new Error(`Invalid transaction index: ${transactionIndex}`); + } + + const tx = transactions[transactionIndex]; + const [owner] = await ethers.getSigners(); + + console.log('='.repeat(80)); + console.log( + `Billions Network Token - Confirming transaction ${transactionIndex + 1}/${transactions.length} "${tx.name}"` + ); + console.log('='.repeat(80)); + console.log('\nOwner signer:', owner.address); + console.log('Balance:', ethers.formatEther(await ethers.provider.getBalance(owner.address)), 'ETH'); + + console.log( + `\nFound ${transactions.length} transactions to propose and confirm.\n[${transactions + .map((tx: any) => tx.name) + .join(', ')}]\n` + ); + + const apiKit = new SafeApiKit({ + chainId: BigInt(safeConfig.chain.id), + apiKey: safeConfig.apiKey, + }); + + let pendingTransactions = (await apiKit.getPendingTransactions(safeAddress)).results; + console.log( + 'Pending transactions in Safe Services for this deployment:', + pendingTransactions.map((tx) => tx.safeTxHash) + ); + + console.log('-'.repeat(80)); + console.log(`[${transactionIndex + 1}/${transactions.length}] ${tx.name}`); + console.log(` To: ${tx.to}`); + + // Only check if contract is deployed when using the Factory address for deployment + if (tx.to === '0x4e59b44847b379578588920cA78FbF26c0B4956C') { + console.log(` Predicted Address: ${tx.predictedAddress}`); + + // Check if contract is already deployed + const existingCode = await ethers.provider.getCode(tx.predictedAddress); + if (existingCode !== '0x') { + throw new Error(' ā­ļø Already deployed'); + } + } + + const safeTransactionData: MetaTransactionData = { + to: tx.to, + value: tx.value, + data: tx.data, + operation: OperationType.Call, + }; + + // Generate the Safe transaction to get safeTxHash + const safeTransaction = await protocolKit.createTransaction({ + transactions: [safeTransactionData], + }); + + // Deterministic hash based on transaction parameters + const safeTxHash = await protocolKit.getTransactionHash(safeTransaction); + + const senderSignature = await signHash(signer, safeTxHash); + // Send the transaction + console.log(' šŸ“¤ Confirming Safe transaction...', safeTxHash); + await apiKit.confirmTransaction(safeTxHash, senderSignature); + + pendingTransactions = (await apiKit.getPendingTransactions(safeAddress)).results; + console.log( + '\nPending transactions in Safe Services for this deployment:', + pendingTransactions.map((tx) => tx.safeTxHash) + ); +} + +export async function executeTransaction(transactionIndex: number, transactionsOutputPath: string) { + const network = hre.network.name; + const safeConfigPath = path.join(__dirname, `../scripts/safe/safe-config-${network}.json`); + + if (!fs.existsSync(safeConfigPath)) { + throw new Error(`Safe config file not found: ${safeConfigPath}`); + } + const safeConfig = JSON.parse(fs.readFileSync(safeConfigPath, 'utf-8')); + if (!safeConfig.chain) { + throw new Error(`chain not found in safe config file: ${safeConfigPath}`); + } + if (!safeConfig.safeAddress) { + throw new Error(`safeAddress not found in safe config file: ${safeConfigPath}`); + } + if (!safeConfig.privateKeySender) { + throw new Error('privateKeySender not set in safe config file'); + } + const chain = defineChain(safeConfig.chain); + + const protocolKit = await Safe.init({ + provider: chain.rpcUrls.default.http[0], + signer: safeConfig.privateKeySender, + safeAddress: safeConfig.safeAddress, + }); + + const safeAddress = await protocolKit.getAddress(); + console.log('Safe Address:', safeAddress); + + // Read transactions from the specified file + const transactions = JSON.parse(fs.readFileSync(transactionsOutputPath, 'utf8')); + if (transactionIndex >= transactions.length) { + throw new Error(`Invalid transaction index: ${transactionIndex}`); + } + + const tx = transactions[transactionIndex]; + const [owner] = await ethers.getSigners(); + + console.log('='.repeat(80)); + console.log( + `Billions Network Token - Executing transaction ${transactionIndex + 1}/${transactions.length} "${tx.name}"` + ); + console.log('='.repeat(80)); + console.log('\nOwner signer:', owner.address); + console.log('Balance:', ethers.formatEther(await ethers.provider.getBalance(owner.address)), 'ETH'); + + console.log( + `\nFound ${transactions.length} transactions to propose and confirm.\n[${transactions + .map((tx: any) => tx.name) + .join(', ')}]\n` + ); + + const apiKit = new SafeApiKit({ + chainId: BigInt(safeConfig.chain.id), + apiKey: safeConfig.apiKey, + }); + + let pendingTransactions = (await apiKit.getPendingTransactions(safeAddress)).results; + console.log( + 'Pending transactions in Safe Services for this deployment:', + pendingTransactions.map((tx) => tx.safeTxHash) + ); + + console.log('-'.repeat(80)); + console.log(`[${transactionIndex + 1}/${transactions.length}] ${tx.name}`); + console.log(` To: ${tx.to}`); + + // Only check if contract is deployed when using the Factory address for deployment + if (tx.to === '0x4e59b44847b379578588920cA78FbF26c0B4956C') { + console.log(` Predicted Address: ${tx.predictedAddress}`); + + // Check if contract is already deployed + const existingCode = await ethers.provider.getCode(tx.predictedAddress); + if (existingCode !== '0x') { + throw new Error(' ā­ļø Already deployed'); + } + } + + const safeTransactionData: MetaTransactionData = { + to: tx.to, + value: tx.value, + data: tx.data, + operation: OperationType.Call, + }; + + // Generate the Safe transaction to get safeTxHash + const safeTransaction = await protocolKit.createTransaction({ + transactions: [safeTransactionData], + }); + + // Deterministic hash based on transaction parameters + const safeTxHash = await protocolKit.getTransactionHash(safeTransaction); + + const safeTransactionFromService = await apiKit.getTransaction(safeTxHash); + if (!safeTransactionFromService) { + throw new Error(`Safe transaction not found in Safe Services: ${safeTxHash}`); + } + + if (safeTransactionFromService.isExecuted) { + throw new Error(' ā­ļø Transaction already executed'); + } + const confirmations = safeTransactionFromService.confirmations + ? safeTransactionFromService.confirmations.length + : 0; + if (safeTransactionFromService.confirmationsRequired > confirmations) { + throw new Error( + `Not enough confirmations to execute the transaction. Required: ${safeTransactionFromService.confirmationsRequired}, Current: ${confirmations}` + ); + } + + // Send the transaction + console.log(' šŸš€ Executing Safe transaction...', safeTxHash); + await protocolKit.executeTransaction(safeTransactionFromService); + + pendingTransactions = (await apiKit.getPendingTransactions(safeAddress)).results; + console.log( + '\nPending transactions in Safe Services for this deployment:', + pendingTransactions.map((tx) => tx.safeTxHash) + ); +} + +export async function proposeTransaction(transactionIndex: number, transactionsOutputPath: string) { + const network = hre.network.name; + const safeConfigPath = path.join(__dirname, `../scripts/safe/safe-config-${network}.json`); + + if (!fs.existsSync(safeConfigPath)) { + throw new Error(`Safe config file not found: ${safeConfigPath}`); + } + const safeConfig = JSON.parse(fs.readFileSync(safeConfigPath, 'utf-8')); + if (!safeConfig.chain) { + throw new Error(`chain not found in safe config file: ${safeConfigPath}`); + } + if (!safeConfig.safeAddress) { + throw new Error(`safeAddress not found in safe config file: ${safeConfigPath}`); + } + if (!safeConfig.privateKeySender) { + throw new Error('privateKeySender not set in safe config file'); + } + const chain = defineChain(safeConfig.chain); + + const protocolKit = await Safe.init({ + provider: chain.rpcUrls.default.http[0], + signer: safeConfig.privateKeySender, + safeAddress: safeConfig.safeAddress, + }); + + const safeAddress = await protocolKit.getAddress(); + console.log('Safe Address:', safeAddress); + + // Read transactions from the specified file + const transactions = JSON.parse(fs.readFileSync(transactionsOutputPath, 'utf8')); + if (transactionIndex >= transactions.length) { + throw new Error(`Invalid transaction index: ${transactionIndex}`); + } + + const tx = transactions[transactionIndex]; + const [owner] = await ethers.getSigners(); + + console.log('='.repeat(80)); + console.log( + `Billions Network Token - Proposing transaction ${transactionIndex + 1}/${transactions.length} "${tx.name}"` + ); + console.log('='.repeat(80)); + console.log('\nOwner signer:', owner.address); + console.log('Balance:', ethers.formatEther(await ethers.provider.getBalance(owner.address)), 'ETH'); + + console.log( + `\nFound ${transactions.length} transactions to propose and confirm.\n[${transactions + .map((tx: any) => tx.name) + .join(', ')}]\n` + ); + + const apiKit = new SafeApiKit({ + chainId: BigInt(safeConfig.chain.id), + apiKey: safeConfig.apiKey, + }); + + let pendingTransactions = (await apiKit.getPendingTransactions(safeAddress)).results; + console.log( + 'Pending transactions in Safe Services for this deployment:', + pendingTransactions.map((tx) => tx.safeTxHash) + ); + + console.log('-'.repeat(80)); + console.log(`[${transactionIndex + 1}/${transactions.length}] ${tx.name}`); + console.log(` To: ${tx.to}`); + + // Only check if contract is deployed when using the Factory address for deployment + if (tx.to === '0x4e59b44847b379578588920cA78FbF26c0B4956C') { + console.log(` Predicted Address: ${tx.predictedAddress}`); + + // Check if contract is already deployed + const existingCode = await ethers.provider.getCode(tx.predictedAddress); + if (existingCode !== '0x') { + throw new Error(' ā­ļø Already deployed'); + } + } + + const safeTransactionData: MetaTransactionData = { + to: tx.to, + value: tx.value, + data: tx.data, + operation: OperationType.Call, + }; + + console.log(' šŸ“„ Creating Safe transaction...'); + + const safeTransaction = await protocolKit.createTransaction({ + transactions: [safeTransactionData], + }); + + // Deterministic hash based on transaction parameters + const safeTxHash = await protocolKit.getTransactionHash(safeTransaction); + console.log(' šŸ†” Safe Transaction Hash:', safeTxHash); + + // Sign transaction to verify that the transaction is coming from owner 1 + const senderSignature = await signHash(owner, safeTxHash); + + // Send the transaction + console.log(' šŸ“¤ Proposing Safe transaction...', safeTxHash); + await apiKit.proposeTransaction({ + safeAddress, + safeTransactionData: safeTransaction.data, + safeTxHash, + senderAddress: owner.address, + senderSignature: senderSignature, + }); + + pendingTransactions = (await apiKit.getPendingTransactions(safeAddress)).results; + console.log( + '\nPending transactions in Safe Services for this deployment:', + pendingTransactions.map((tx) => tx.safeTxHash) + ); +} diff --git a/helpers/utils.ts b/helpers/utils.ts new file mode 100644 index 0000000..068bcc7 --- /dev/null +++ b/helpers/utils.ts @@ -0,0 +1,96 @@ +import hre, { ethers, run } from 'hardhat'; + +export async function verifyContract( + contractAddress: any, + opts: { + contract?: string; + constructorArgsProxy?: any[]; + constructorArgsProxyAdmin?: any[]; + constructorArgsImplementation: any[]; + libraries: any; + } +): Promise { + if (hre.network.name === 'localhost') { + return true; + } + // When verifying if the proxy contract is not verified yet we need to pass the arguments + // for the proxy contract first, then for proxy admin and finally for the implementation contract + if (opts.constructorArgsProxy) { + try { + await run('verify:verify', { + address: contractAddress, + contract: opts.contract, + constructorArguments: opts.constructorArgsProxy, + libraries: opts.libraries, + }); + } catch (error) {} + } + + if (opts.constructorArgsProxyAdmin) { + try { + await run('verify:verify', { + address: contractAddress, + contract: opts.contract, + constructorArguments: opts.constructorArgsProxyAdmin, + libraries: opts.libraries, + }); + } catch (error) {} + } + + try { + await run('verify:verify', { + address: contractAddress, + contract: opts.contract, + constructorArguments: opts.constructorArgsImplementation, + libraries: opts.libraries, + }); + console.log(`āœ… Verification successful for ${contractAddress}\n`); + return true; + } catch (error) { + console.log(`āŒ Error verifying ${contractAddress}: ${error}\n`); + } + + return false; +} + +async function isTxHashSignedWithPrefix(txHash: string, signature: string, ownerAddress: string) { + let hasPrefix; + try { + const recoveredAddress = ethers.recoverAddress(txHash, signature); + hasPrefix = !( + !!recoveredAddress && + !!ownerAddress && + recoveredAddress.toLowerCase() === ownerAddress.toLowerCase() + ); + } catch (e) { + hasPrefix = true; + } + return hasPrefix; +} + +async function adjustVInSignature(signature: string, safeTxHash: string, signerAddress: string) { + const ETHEREUM_V_VALUES = [0, 1, 27, 28]; + const MIN_VALID_V_VALUE_FOR_SAFE_ECDSA = 27; + let signatureV = parseInt(signature.slice(-2), 16); + if (!ETHEREUM_V_VALUES.includes(signatureV)) { + throw new Error('Invalid signature'); + } + if (signatureV < MIN_VALID_V_VALUE_FOR_SAFE_ECDSA) { + signatureV += MIN_VALID_V_VALUE_FOR_SAFE_ECDSA; + } + const adjustedSignature = signature.slice(0, -2) + signatureV.toString(16); + const signatureHasPrefix = await isTxHashSignedWithPrefix(safeTxHash, adjustedSignature, signerAddress); + if (signatureHasPrefix) { + signatureV += 4; + } + signature = signature.slice(0, -2) + signatureV.toString(16); + return signature; +} + +export async function signHash(signer: any, safeTxHash: string): Promise { + const signedMessage = await signer.signMessage(ethers.getBytes(safeTxHash)); + // Adjust V in signature if needed (required by Safe API) + const adjustedSignature = await adjustVInSignature(signedMessage, safeTxHash, signer.address); + + return adjustedSignature; +} diff --git a/package-lock.json b/package-lock.json index dec0238..1097066 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,13 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@nomicfoundation/hardhat-ledger": "^1.0.3", "@nomicfoundation/hardhat-toolbox": "^5.0.0", "@openzeppelin/contracts-upgradeable": "^5.4.0", "@openzeppelin/hardhat-upgrades": "^3.9.1", + "@safe-global/api-kit": "^4.0.1", + "@safe-global/protocol-kit": "^6.1.2", + "@safe-global/types-kit": "^3.0.0", "@types/node": "^22.10.2", "dotenv": "^16.4.7", "hardhat": "^2.22.18", @@ -26,8 +30,7 @@ "version": "1.10.1", "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@aws-crypto/crc32": { "version": "5.2.0", @@ -1543,12 +1546,164 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@ledgerhq/cryptoassets": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/cryptoassets/-/cryptoassets-9.13.0.tgz", + "integrity": "sha512-MzGJyc48OGU/FLYGYwEJyfOgbJzlR8XJ9Oo6XpNpNUM1/E5NDqvD72V0D+0uWIJYN3e2NtyqHXShLZDu7P95YA==", + "license": "Apache-2.0", + "dependencies": { + "invariant": "2" + } + }, + "node_modules/@ledgerhq/devices": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.7.0.tgz", + "integrity": "sha512-3pSOULPUhClk2oOxa6UnoyXW8+05FK7r6cpq2WiTey4kyIUjmIraO7AX9lhz9IU73dGVtBMdU+NCPPO2RYJaTQ==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/errors": "^6.27.0", + "@ledgerhq/logs": "^6.13.0", + "rxjs": "^7.8.1", + "semver": "^7.3.5" + } + }, + "node_modules/@ledgerhq/devices/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@ledgerhq/domain-service": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@ledgerhq/domain-service/-/domain-service-1.4.1.tgz", + "integrity": "sha512-ku4Q/d+uiznylCqGTzSfvopzgVeBsGqANkF6CHnIu8tThFwlrU2h4O7D7OAgIUcVo1TXgm8a7p4noprDL7ySvA==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/errors": "^6.27.0", + "@ledgerhq/logs": "^6.13.0", + "@ledgerhq/types-live": "^6.89.0", + "axios": "1.12.2", + "eip55": "^2.1.1", + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@ledgerhq/errors": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.27.0.tgz", + "integrity": "sha512-EE2hATONHdNP3YWFe3rZwwpSEzI5oN+q/xTjOulnjHZo84TLjungegEJ54A/Pzld0woulkkeVA27FbW5SAO1aA==", + "license": "Apache-2.0" + }, + "node_modules/@ledgerhq/hw-app-eth": { + "version": "6.33.6", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-eth/-/hw-app-eth-6.33.6.tgz", + "integrity": "sha512-QzYvr5FNEWWd70Vg04A2i8CY0mtPgJrrX7/KePabjXrR8NjDyJ5Ej8qSQPBTp2dkR4TGiz5Y7+HIcWpdgYzjzg==", + "license": "Apache-2.0", + "dependencies": { + "@ethersproject/abi": "^5.5.0", + "@ethersproject/rlp": "^5.5.0", + "@ledgerhq/cryptoassets": "^9.8.0", + "@ledgerhq/domain-service": "^1.1.4", + "@ledgerhq/errors": "^6.12.6", + "@ledgerhq/hw-transport": "^6.28.4", + "@ledgerhq/hw-transport-mocker": "^6.27.15", + "@ledgerhq/logs": "^6.10.1", + "axios": "^1.3.4", + "bignumber.js": "^9.1.0", + "crypto-js": "^4.1.1" + } + }, + "node_modules/@ledgerhq/hw-transport": { + "version": "6.31.13", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.31.13.tgz", + "integrity": "sha512-MrJRDk74wY980ofiFPRpTHQBbRw1wDuKbdag1zqlO1xtJglymwwY03K2kvBNvkm1RTSCPUp/nAoNG+WThZuuew==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/devices": "8.7.0", + "@ledgerhq/errors": "^6.27.0", + "@ledgerhq/logs": "^6.13.0", + "events": "^3.3.0" + } + }, + "node_modules/@ledgerhq/hw-transport-mocker": { + "version": "6.29.13", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-mocker/-/hw-transport-mocker-6.29.13.tgz", + "integrity": "sha512-yfslV0Xg8tGsvvn0R5ulduIgXBILk0qXg/YrOx8kJyzHGSRV58CeVw2ZhsO/pAXAZJ9ojH1kzdIBo0qFH2M0AA==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/hw-transport": "6.31.13", + "@ledgerhq/logs": "^6.13.0", + "rxjs": "^7.8.1" + } + }, + "node_modules/@ledgerhq/hw-transport-node-hid": { + "version": "6.29.14", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-6.29.14.tgz", + "integrity": "sha512-SqawAnYecZz1tt2VHbhyQMhwadaKhsMjhv9gHu9MmAevLFSVCfgs+6qWmuT+kuxhD1zGneW5TxlW+4B+HcVtWg==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/devices": "8.7.0", + "@ledgerhq/errors": "^6.27.0", + "@ledgerhq/hw-transport": "6.31.13", + "@ledgerhq/hw-transport-node-hid-noevents": "^6.30.14", + "@ledgerhq/logs": "^6.13.0", + "lodash": "^4.17.21", + "node-hid": "2.1.2", + "usb": "2.9.0" + } + }, + "node_modules/@ledgerhq/hw-transport-node-hid-noevents": { + "version": "6.30.14", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-6.30.14.tgz", + "integrity": "sha512-QG5wd6kjJYYXSmGRoZkDAehX1iN9WdjgHYNW4QwWcw9G6QnAgAYLE6v1u4ZZVVLZ6DrmHKJVOzwm9njYJMit2w==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/devices": "8.7.0", + "@ledgerhq/errors": "^6.27.0", + "@ledgerhq/hw-transport": "6.31.13", + "@ledgerhq/logs": "^6.13.0", + "node-hid": "2.1.2" + } + }, + "node_modules/@ledgerhq/logs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-6.13.0.tgz", + "integrity": "sha512-4+qRW2Pc8V+btL0QEmdB2X+uyx0kOWMWE1/LWsq5sZy3Q5tpi4eItJS6mB0XL3wGW59RQ+8bchNQQ1OW/va8Og==", + "license": "Apache-2.0" + }, + "node_modules/@ledgerhq/types-live": { + "version": "6.89.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/types-live/-/types-live-6.89.0.tgz", + "integrity": "sha512-wz+3HiyTjnzGe8yAfzF3gzu0ftt5gYoDBDJaIqRiYsCOZtVkqJ9PG/mvLDBzOCGvR+Tj7dGTHuzRz0UXqSTjRA==", + "license": "Apache-2.0", + "dependencies": { + "bignumber.js": "^9.1.2", + "rxjs": "^7.8.1" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "license": "MIT", - "peer": true, "dependencies": { "@noble/hashes": "1.3.2" }, @@ -1561,7 +1716,6 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 16" }, @@ -1620,90 +1774,90 @@ } }, "node_modules/@nomicfoundation/edr": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.11.3.tgz", - "integrity": "sha512-kqILRkAd455Sd6v8mfP3C1/0tCOynJWY+Ir+k/9Boocu2kObCrsFgG+ZWB7fSBVdd9cPVSNrnhWS+V+PEo637g==", + "version": "0.12.0-next.16", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.12.0-next.16.tgz", + "integrity": "sha512-bBL/nHmQwL1WCveALwg01VhJcpVVklJyunG1d/bhJbHgbjzAn6kohVJc7A6gFZegw+Rx38vdxpBkeCDjAEprzw==", "license": "MIT", "dependencies": { - "@nomicfoundation/edr-darwin-arm64": "0.11.3", - "@nomicfoundation/edr-darwin-x64": "0.11.3", - "@nomicfoundation/edr-linux-arm64-gnu": "0.11.3", - "@nomicfoundation/edr-linux-arm64-musl": "0.11.3", - "@nomicfoundation/edr-linux-x64-gnu": "0.11.3", - "@nomicfoundation/edr-linux-x64-musl": "0.11.3", - "@nomicfoundation/edr-win32-x64-msvc": "0.11.3" + "@nomicfoundation/edr-darwin-arm64": "0.12.0-next.16", + "@nomicfoundation/edr-darwin-x64": "0.12.0-next.16", + "@nomicfoundation/edr-linux-arm64-gnu": "0.12.0-next.16", + "@nomicfoundation/edr-linux-arm64-musl": "0.12.0-next.16", + "@nomicfoundation/edr-linux-x64-gnu": "0.12.0-next.16", + "@nomicfoundation/edr-linux-x64-musl": "0.12.0-next.16", + "@nomicfoundation/edr-win32-x64-msvc": "0.12.0-next.16" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-darwin-arm64": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.11.3.tgz", - "integrity": "sha512-w0tksbdtSxz9nuzHKsfx4c2mwaD0+l5qKL2R290QdnN9gi9AV62p9DHkOgfBdyg6/a6ZlnQqnISi7C9avk/6VA==", + "version": "0.12.0-next.16", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.12.0-next.16.tgz", + "integrity": "sha512-no/8BPVBzVxDGGbDba0zsAxQmVNIq6SLjKzzhCxVKt4tatArXa6+24mr4jXJEmhVBvTNpQsNBO+MMpuEDVaTzQ==", "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-darwin-x64": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.11.3.tgz", - "integrity": "sha512-QR4jAFrPbOcrO7O2z2ESg+eUeIZPe2bPIlQYgiJ04ltbSGW27FblOzdd5+S3RoOD/dsZGKAvvy6dadBEl0NgoA==", + "version": "0.12.0-next.16", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.12.0-next.16.tgz", + "integrity": "sha512-tf36YbcC6po3XYRbi+v0gjwzqg1MvyRqVUujNMXPHgjNWATXNRNOLyjwt2qDn+RD15qtzk70SHVnz9n9mPWzwg==", "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.11.3.tgz", - "integrity": "sha512-Ktjv89RZZiUmOFPspuSBVJ61mBZQ2+HuLmV67InNlh9TSUec/iDjGIwAn59dx0bF/LOSrM7qg5od3KKac4LJDQ==", + "version": "0.12.0-next.16", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.12.0-next.16.tgz", + "integrity": "sha512-Kr6t9icKSaKtPVbb0TjUcbn3XHqXOGIn+KjKKSSpm6542OkL0HyOi06amh6/8CNke9Gf6Lwion8UJ0aGQhnFwA==", "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-linux-arm64-musl": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.11.3.tgz", - "integrity": "sha512-B3sLJx1rL2E9pfdD4mApiwOZSrX0a/KQSBWdlq1uAhFKqkl00yZaY4LejgZndsJAa4iKGQJlGnw4HCGeVt0+jA==", + "version": "0.12.0-next.16", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.12.0-next.16.tgz", + "integrity": "sha512-HaStgfxctSg5PYF+6ooDICL1O59KrgM4XEUsIqoRrjrQax9HnMBXcB8eAj+0O52FWiO9FlchBni2dzh4RjQR2g==", "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-linux-x64-gnu": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.11.3.tgz", - "integrity": "sha512-D/4cFKDXH6UYyKPu6J3Y8TzW11UzeQI0+wS9QcJzjlrrfKj0ENW7g9VihD1O2FvXkdkTjcCZYb6ai8MMTCsaVw==", + "version": "0.12.0-next.16", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.12.0-next.16.tgz", + "integrity": "sha512-8JPTxEZkwOPTgnN4uTWut9ze9R8rp7+T4IfmsKK9i+lDtdbJIxkrFY275YHG2BEYLd7Y5jTa/I4nC74ZpTAvpA==", "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-linux-x64-musl": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.11.3.tgz", - "integrity": "sha512-ergXuIb4nIvmf+TqyiDX5tsE49311DrBky6+jNLgsGDTBaN1GS3OFwFS8I6Ri/GGn6xOaT8sKu3q7/m+WdlFzg==", + "version": "0.12.0-next.16", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.12.0-next.16.tgz", + "integrity": "sha512-KugTrq3iHukbG64DuCYg8uPgiBtrrtX4oZSLba5sjocp0Ul6WWI1FeP1Qule+vClUrHSpJ+wR1G6SE7G0lyS/Q==", "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-win32-x64-msvc": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.11.3.tgz", - "integrity": "sha512-snvEf+WB3OV0wj2A7kQ+ZQqBquMcrozSLXcdnMdEl7Tmn+KDCbmFKBt3Tk0X3qOU4RKQpLPnTxdM07TJNVtung==", + "version": "0.12.0-next.16", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.12.0-next.16.tgz", + "integrity": "sha512-Idy0ZjurxElfSmepUKXh6QdptLbW5vUNeIaydvqNogWoTbkJIM6miqZd9lXUy1TYxY7G4Rx5O50c52xc4pFwXQ==", "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/hardhat-chai-matchers": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-2.1.0.tgz", - "integrity": "sha512-GPhBNafh1fCnVD9Y7BYvoLnblnvfcq3j8YDbO1gGe/1nOFWzGmV7gFu5DkwFXF+IpYsS+t96o9qc/mPu3V3Vfw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-2.1.2.tgz", + "integrity": "sha512-NlUlde/ycXw2bLzA2gWjjbxQaD9xIRbAF30nsoEprAWzH8dXEI1ILZUKZMyux9n9iygEXTzN0SDVjE6zWDZi9g==", "license": "MIT", "peer": true, "dependencies": { @@ -1720,9 +1874,9 @@ } }, "node_modules/@nomicfoundation/hardhat-ethers": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.1.0.tgz", - "integrity": "sha512-jx6fw3Ms7QBwFGT2MU6ICG292z0P81u6g54JjSV105+FbTZOF4FJqPksLfDybxkkOeq28eDxbqq7vpxRYyIlxA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.1.2.tgz", + "integrity": "sha512-7xEaz2X8p47qWIAqtV2z03MmusheHm8bvY2mDlxo9JiT2BgSx59GSdv5+mzwOvsuKDbTij7oqDnwFyYOlHREEQ==", "license": "MIT", "peer": true, "dependencies": { @@ -1735,14 +1889,14 @@ } }, "node_modules/@nomicfoundation/hardhat-ignition": { - "version": "0.15.13", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition/-/hardhat-ignition-0.15.13.tgz", - "integrity": "sha512-G4XGPWvxs9DJhZ6PE1wdvKjHkjErWbsETf4c7YxO6GUz+MJGlw+PtgbnCwhL3tQzSq3oD4MB0LGi+sK0polpUA==", + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition/-/hardhat-ignition-0.15.15.tgz", + "integrity": "sha512-4uLp5MOyaW0gUYGAxiA8GikGIo8SLBijpxakFI3BpofUoeRXnnQdNtRJT9aAKD8ENfvFQrNFin0Z1VlXjXurkA==", "license": "MIT", "peer": true, "dependencies": { - "@nomicfoundation/ignition-core": "^0.15.13", - "@nomicfoundation/ignition-ui": "^0.15.12", + "@nomicfoundation/ignition-core": "^0.15.14", + "@nomicfoundation/ignition-ui": "^0.15.13", "chalk": "^4.0.0", "debug": "^4.3.2", "fs-extra": "^10.0.0", @@ -1755,23 +1909,149 @@ } }, "node_modules/@nomicfoundation/hardhat-ignition-ethers": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition-ethers/-/hardhat-ignition-ethers-0.15.14.tgz", - "integrity": "sha512-eq+5n+c1DW18/Xp8/QrHBBvG5QaKUxYF/byol4f1jrnZ1zAy0OrqEa/oaNFWchhpLalX7d7suk/2EL0PbT0CDQ==", + "version": "0.15.16", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition-ethers/-/hardhat-ignition-ethers-0.15.16.tgz", + "integrity": "sha512-OXC1jLX06YFgzFero9AjHUSwB39CLWPiBAJj5ZoDJiTnAbH1DvnQSt8sUJuZRrrL+WH8sQbcB4hjkPcR0B5hAA==", "license": "MIT", "peer": true, "peerDependencies": { "@nomicfoundation/hardhat-ethers": "^3.1.0", - "@nomicfoundation/hardhat-ignition": "^0.15.13", - "@nomicfoundation/ignition-core": "^0.15.13", + "@nomicfoundation/hardhat-ignition": "^0.15.15", + "@nomicfoundation/ignition-core": "^0.15.14", "ethers": "^6.14.0", "hardhat": "^2.26.0" } }, + "node_modules/@nomicfoundation/hardhat-ledger": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ledger/-/hardhat-ledger-1.2.2.tgz", + "integrity": "sha512-igsPZtqkc22zsV951sfS4fCREB/Rao/9KnLZdm9Q//HiicJtNNvjgbsvl4iikT1zjUUZKCnbTouKIh+05ln1Mg==", + "license": "MIT", + "dependencies": { + "@ethereumjs/util": "^9.1.0", + "@ledgerhq/errors": "^6.12.6", + "@ledgerhq/hw-app-eth": "6.33.6", + "@ledgerhq/hw-transport": "^6.28.4", + "@ledgerhq/hw-transport-node-hid": "^6.27.13", + "chalk": "^2.4.2", + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "ethers": "^6.14.0", + "fs-extra": "^7.0.1", + "io-ts": "1.10.4", + "ora": "^5.4.1" + }, + "peerDependencies": { + "hardhat": "^2.26.0" + } + }, + "node_modules/@nomicfoundation/hardhat-ledger/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nomicfoundation/hardhat-ledger/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nomicfoundation/hardhat-ledger/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@nomicfoundation/hardhat-ledger/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/@nomicfoundation/hardhat-ledger/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@nomicfoundation/hardhat-ledger/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@nomicfoundation/hardhat-ledger/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@nomicfoundation/hardhat-ledger/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@nomicfoundation/hardhat-ledger/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nomicfoundation/hardhat-ledger/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/@nomicfoundation/hardhat-network-helpers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.1.0.tgz", - "integrity": "sha512-ZS+NulZuR99NUHt2VwcgZvgeD6Y63qrbORNRuKO+lTowJxNVsrJ0zbRx1j5De6G3dOno5pVGvuYSq2QVG0qCYg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.1.2.tgz", + "integrity": "sha512-p7HaUVDbLj7ikFivQVNhnfMHUBgiHYMwQWvGn9AriieuopGOELIrwj2KjyM2a6z70zai5YKO264Vwz+3UFJZPQ==", "license": "MIT", "peer": true, "dependencies": { @@ -1808,9 +2088,9 @@ } }, "node_modules/@nomicfoundation/hardhat-verify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.1.1.tgz", - "integrity": "sha512-K1plXIS42xSHDJZRkrE2TZikqxp9T4y6jUMUNI/imLgN5uCcEQokmfU0DlyP9zzHncYK92HlT5IWP35UVCLrPw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.1.3.tgz", + "integrity": "sha512-danbGjPp2WBhLkJdQy9/ARM3WQIK+7vwzE0urNem1qZJjh9f54Kf5f1xuQv8DvqewUAkuPxVt/7q4Grz5WjqSg==", "license": "MIT", "peer": true, "dependencies": { @@ -1829,9 +2109,9 @@ } }, "node_modules/@nomicfoundation/ignition-core": { - "version": "0.15.13", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-core/-/ignition-core-0.15.13.tgz", - "integrity": "sha512-Z4T1WIbw0EqdsN9RxtnHeQXBi7P/piAmCu8bZmReIdDo/2h06qgKWxjDoNfc9VBFZJ0+Dx79tkgQR3ewxMDcpA==", + "version": "0.15.14", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-core/-/ignition-core-0.15.14.tgz", + "integrity": "sha512-BRgNaApHTdmk0NNTVYMltRXUFQGaWKHKnaaOyp9TG/BsUUkW3mH1ds5+rM4UBUIHivIyh3fKFDCOGJIJcQG9aw==", "license": "MIT", "peer": true, "dependencies": { @@ -1884,9 +2164,10 @@ } }, "node_modules/@nomicfoundation/ignition-ui": { - "version": "0.15.12", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-ui/-/ignition-ui-0.15.12.tgz", - "integrity": "sha512-nQl8tusvmt1ANoyIj5RQl9tVSEmG0FnNbtwnWbTim+F8JLm4YLHWS0yEgYUZC+BEO3oS0D8r6V8a02JGZJgqiQ==", + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-ui/-/ignition-ui-0.15.13.tgz", + "integrity": "sha512-HbTszdN1iDHCkUS9hLeooqnLEW2U45FaqFwFEYT8nIno2prFZhG+n68JEERjmfFCB5u0WgbuJwk3CgLoqtSL7Q==", + "license": "MIT", "peer": true }, "node_modules/@nomicfoundation/slang": { @@ -2124,6 +2405,132 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "license": "MIT", + "optional": true, + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/@safe-global/api-kit": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@safe-global/api-kit/-/api-kit-4.0.1.tgz", + "integrity": "sha512-pNtDLgMHlCSr4Hwwe6jsnvMheAu2SZCTqjYlnNe4cKH2pSKINVRTiILoeJ0wOpixrMCH4NlgJ+9N3QruRNcCpQ==", + "license": "MIT", + "dependencies": { + "@safe-global/protocol-kit": "^6.1.2", + "@safe-global/types-kit": "^3.0.0", + "node-fetch": "^2.7.0", + "viem": "^2.21.8" + } + }, + "node_modules/@safe-global/protocol-kit": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@safe-global/protocol-kit/-/protocol-kit-6.1.2.tgz", + "integrity": "sha512-cTpPdUAS2AMfGCkD1T601rQNjT0rtMQLA2TH7L/C+iFPAC6WrrDFop2B9lzeHjczlnVzrRpfFe4cL1bLrJ9NZw==", + "license": "MIT", + "dependencies": { + "@safe-global/safe-deployments": "^1.37.49", + "@safe-global/safe-modules-deployments": "^2.2.21", + "@safe-global/types-kit": "^3.0.0", + "abitype": "^1.0.2", + "semver": "^7.7.2", + "viem": "^2.21.8" + }, + "optionalDependencies": { + "@noble/curves": "^1.6.0", + "@peculiar/asn1-schema": "^2.3.13" + } + }, + "node_modules/@safe-global/protocol-kit/node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@safe-global/protocol-kit/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@safe-global/protocol-kit/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@safe-global/safe-deployments": { + "version": "1.37.49", + "resolved": "https://registry.npmjs.org/@safe-global/safe-deployments/-/safe-deployments-1.37.49.tgz", + "integrity": "sha512-132QgqMY1/HktXqmda/uPp5b+73UXTgKRB00Xgc1kduFqceSw/ZyF1Q9jJjbND9q91hhapnXhYKWN2/HiWkRcg==", + "license": "MIT", + "dependencies": { + "semver": "^7.6.2" + } + }, + "node_modules/@safe-global/safe-deployments/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@safe-global/safe-modules-deployments": { + "version": "2.2.21", + "resolved": "https://registry.npmjs.org/@safe-global/safe-modules-deployments/-/safe-modules-deployments-2.2.21.tgz", + "integrity": "sha512-fveOlRv0ccwsuaZjP1u7ZbXrwCyqMTYYiqETOGo8NdzTaceRUyR9TNzagSWovOSuHPVyUGJ9lnsxizikt/+PiQ==", + "license": "MIT" + }, + "node_modules/@safe-global/types-kit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@safe-global/types-kit/-/types-kit-3.0.0.tgz", + "integrity": "sha512-AZWIlR5MguDPdGiOj7BB4JQPY2afqmWQww1mu8m8Oi16HHBW99G01kFOu4NEHBwEU1cgwWOMY19hsI5KyL4W2w==", + "license": "MIT", + "dependencies": { + "abitype": "^1.0.2" + } + }, "node_modules/@scure/base": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", @@ -3210,6 +3617,12 @@ "@types/node": "*" } }, + "node_modules/@types/w3c-web-usb": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.13.tgz", + "integrity": "sha512-N2nSl3Xsx8mRHZBvMSdNGtzMyeleTvtlEw+ujujgXalPqOjIA6UtrqcB6OzyUjkTbDm3J7P1RNK1lgoO7jxtsw==", + "license": "MIT" + }, "node_modules/abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -3217,6 +3630,27 @@ "license": "ISC", "peer": true }, + "node_modules/abitype": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.0.tgz", + "integrity": "sha512-fD3ROjckUrWsybaSor2AdWxzA0e/DSyV2dA4aYd7bd8orHsoJjl09fOgKfUkTDfk0BsDGBf4NBgu/c7JoS2Npw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -3254,8 +3688,7 @@ "version": "4.0.0-beta.5", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/agent-base": { "version": "6.0.2", @@ -3477,6 +3910,28 @@ "license": "MIT", "peer": true }, + "node_modules/asn1js": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz", + "integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/asn1js/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -3611,11 +4066,55 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, "node_modules/blakejs": { @@ -3940,6 +4439,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, "node_modules/ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -3981,6 +4486,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-table3": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", @@ -4056,6 +4585,15 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4311,12 +4849,16 @@ } }, "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/core-util-is": { @@ -4368,6 +4910,12 @@ "node": "*" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/death": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", @@ -4403,6 +4951,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-eql": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", @@ -4421,7 +4984,6 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", - "peer": true, "engines": { "node": ">=4.0.0" } @@ -4433,6 +4995,18 @@ "license": "MIT", "peer": true }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -4468,6 +5042,15 @@ "node": ">= 0.8" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", @@ -4528,6 +5111,15 @@ "node": ">= 0.4" } }, + "node_modules/eip55": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eip55/-/eip55-2.1.1.tgz", + "integrity": "sha512-WcagVAmNu2Ww2cDUfzuWVntYwFxbvZ5MvIyLZpMjTTkjD6sCvkGOiS86jTppzu9/gWsc8isLHAeMBWK02OnZmA==", + "license": "MIT", + "dependencies": { + "keccak": "^3.0.3" + } + }, "node_modules/elliptic": { "version": "6.6.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", @@ -4555,6 +5147,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enquirer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", @@ -4926,7 +5527,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", @@ -4945,7 +5545,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.19.2" } @@ -4954,8 +5553,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ethjs-unit": { "version": "0.1.6", @@ -4979,6 +5577,21 @@ "license": "MIT", "peer": true }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -4989,6 +5602,15 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/fast-base64-decode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz", @@ -5071,6 +5693,12 @@ "reusify": "^1.0.4" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -5178,6 +5806,12 @@ "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", "license": "MIT" }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -5387,6 +6021,12 @@ "node": ">=4" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -5564,14 +6204,14 @@ } }, "node_modules/hardhat": { - "version": "2.26.3", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.26.3.tgz", - "integrity": "sha512-gBfjbxCCEaRgMCRgTpjo1CEoJwqNPhyGMMVHYZJxoQ3LLftp2erSVf8ZF6hTQC0r2wst4NcqNmLWqMnHg1quTw==", + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.27.1.tgz", + "integrity": "sha512-0+AWlXgXd0fbPUsAJwp9x6kgYwNxFdZtHVE40bVqPO1WIpCZeWldvubxZl2yOGSzbufa6d9s0n+gNj7JSlTYCQ==", "license": "MIT", "dependencies": { "@ethereumjs/util": "^9.1.0", "@ethersproject/abi": "^5.1.2", - "@nomicfoundation/edr": "^0.11.3", + "@nomicfoundation/edr": "0.12.0-next.16", "@nomicfoundation/solidity-analyzer": "^0.1.0", "@sentry/node": "^5.18.1", "adm-zip": "^0.4.16", @@ -6064,8 +6704,7 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/interpret": { "version": "1.4.0", @@ -6077,6 +6716,15 @@ "node": ">= 0.10" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/io-ts": { "version": "1.10.4", "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz", @@ -6151,6 +6799,15 @@ "npm": ">=3" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6219,6 +6876,21 @@ "unfetch": "^4.2.0" } }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/js-cookie": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", @@ -6231,10 +6903,16 @@ "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", "license": "MIT" }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -6417,6 +7095,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -6576,6 +7266,27 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -6622,6 +7333,12 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/mnemonist": { "version": "0.38.5", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", @@ -6723,6 +7440,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, "node_modules/ndjson": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-2.0.0.tgz", @@ -6750,6 +7473,30 @@ "license": "MIT", "peer": true }, + "node_modules/node-abi": { + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-addon-api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", @@ -6797,6 +7544,30 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-hid": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-hid/-/node-hid-2.1.2.tgz", + "integrity": "sha512-qhCyQqrPpP93F/6Wc/xUR7L8mAJW0Z6R7HMQV8jCHHksAxNDe/4z4Un/H9CpLOT+5K39OPyt9tIQlavxWES3lg==", + "hasInstallScript": true, + "license": "(MIT OR X11)", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^3.0.2", + "prebuild-install": "^7.1.1" + }, + "bin": { + "hid-showdevices": "src/show-devices.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-hid/node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "license": "MIT" + }, "node_modules/nofilter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", @@ -6888,6 +7659,21 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -6902,24 +7688,128 @@ "type-check": "~0.3.2", "word-wrap": "~1.2.3" }, - "engines": { - "node": ">= 0.8.0" + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ordinal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ordinal/-/ordinal-1.0.3.tgz", + "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==", + "license": "MIT", + "peer": true + }, + "node_modules/ox": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.6.tgz", + "integrity": "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.0.9", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/ox/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/ordinal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ordinal/-/ordinal-1.0.3.tgz", - "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==", + "node_modules/ox/node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", "license": "MIT", - "peer": true + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "node_modules/ox/node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/p-limit": { @@ -7072,6 +7962,32 @@ "node": ">= 0.4" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -7153,6 +8069,43 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvtsutils/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -7214,6 +8167,55 @@ "node": ">= 0.8" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -7367,6 +8369,19 @@ "node": ">=4" } }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -7436,6 +8451,15 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7538,9 +8562,9 @@ } }, "node_modules/sc-istanbul/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "license": "MIT", "peer": true, "dependencies": { @@ -7598,6 +8622,15 @@ "node": ">=0.8.0" } }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", @@ -7852,6 +8885,51 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -8339,6 +9417,34 @@ "node": ">=8" } }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/then-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", @@ -8443,15 +9549,12 @@ } }, "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, "engines": { - "node": ">=0.6.0" + "node": ">=14.14" } }, "node_modules/to-buffer": { @@ -8585,6 +9688,18 @@ "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", "license": "MIT" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -8840,6 +9955,27 @@ "node": ">= 0.8" } }, + "node_modules/usb": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/usb/-/usb-2.9.0.tgz", + "integrity": "sha512-G0I/fPgfHUzWH8xo2KkDxTTFruUWfppgSFJ+bQxz/kVY2x15EQ/XDB7dqD1G432G4gBG4jYQuF3U7j/orSs5nw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@types/w3c-web-usb": "^1.0.6", + "node-addon-api": "^6.0.0", + "node-gyp-build": "^4.5.0" + }, + "engines": { + "node": ">=10.20.0 <11.x || >=12.17.0 <13.0 || >=14.0.0" + } + }, + "node_modules/usb/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "license": "MIT" + }, "node_modules/utf8": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", @@ -8868,6 +10004,141 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "license": "MIT" }, + "node_modules/viem": { + "version": "2.40.3", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.40.3.tgz", + "integrity": "sha512-feYfEpbgjRkZYQpwcgxqkWzjxHI5LSDAjcGetHHwDRuX9BRQHUdV8ohrCosCYpdEhus/RknD3/bOd4qLYVPPuA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.1.0", + "isows": "1.0.7", + "ox": "0.9.6", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/abitype": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.1.0.tgz", + "integrity": "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/web3-utils": { "version": "1.10.4", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.4.tgz", @@ -9092,7 +10363,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index f533047..d1a6b4a 100644 --- a/package.json +++ b/package.json @@ -7,24 +7,77 @@ "compile": "hardhat compile", "test": "hardhat test", "coverage": "hardhat coverage", - "generate": "hardhat run scripts/generateFromMultisigDeterministic.ts", - "deploy:localhost": "hardhat run scripts/deploy.ts --network localhost", - "deploy:sepolia": "hardhat run scripts/deploy.ts --network sepolia", - "deploy:mainnet": "hardhat run scripts/deploy.ts --network mainnet", - "verify:sepolia": "hardhat run scripts/verifyDeployment.ts --network sepolia", - "verify:mainnet": "hardhat run scripts/verifyDeployment.ts --network mainnet" + "generate:deployment:sepolia": "hardhat run scripts/deployment/deployFromMultisigDeterministic.ts --network sepolia", + "generate:deployment:mainnet": "hardhat run scripts/deployment/deployFromMultisigDeterministic.ts --network mainnet", + "generate:upgrade:sepolia": "hardhat run scripts/upgrade/upgradeFromMultisigDeterministic.ts --network sepolia", + "generate:upgrade:mainnet": "hardhat run scripts/upgrade/upgradeFromMultisigDeterministic.ts --network mainnet", + "deploy:localhost": "hardhat run scripts/deployment/deploy.ts --network localhost", + "deploy:sepolia": "hardhat run scripts/deployment/deploy.ts --network sepolia", + "deploy:mainnet": "hardhat run scripts/deployment/deploy.ts --network mainnet", + "verify:deployment:sepolia": "hardhat run scripts/deployment/verifyDeployment.ts --network sepolia", + "verify:deployment:mainnet": "hardhat run scripts/deployment/verifyDeployment.ts --network mainnet", + "verify:upgrade:sepolia": "hardhat run scripts/upgrade/verifyUpgrade.ts --network sepolia", + "verify:upgrade:mainnet": "hardhat run scripts/upgrade/verifyUpgrade.ts --network mainnet", + "test_deploy:localhost": "hardhat run scripts/test_deployment/deploy.ts --network localhost", + "test_deploy:sepolia": "hardhat run scripts/test_deployment/deploy.ts --network sepolia", + "test_deploy:mainnet": "hardhat run scripts/test_deployment/deploy.ts --network mainnet", + "upgrade:schedule:localhost": "hardhat run scripts/upgrade/schedule.ts --network localhost", + "upgrade:execute:localhost": "hardhat run scripts/upgrade/execute.ts --network localhost", + "upgrade:schedule:sepolia": "hardhat run scripts/upgrade/schedule.ts --network sepolia", + "upgrade:execute:sepolia": "hardhat run scripts/upgrade/execute.ts --network sepolia", + "upgrade:schedule:mainnet": "hardhat run scripts/upgrade/schedule.ts --network mainnet", + "upgrade:execute:mainnet": "hardhat run scripts/upgrade/execute.ts --network mainnet", + "safe:deploy:localhost": "hardhat run scripts/safe/deploySafe.ts --network localhost", + "safe:deploy:sepolia": "hardhat run scripts/safe/deploySafe.ts --network sepolia", + "safe:deploy:mainnet": "hardhat run scripts/safe/deploySafe.ts --network mainnet", + "safe:propose:deployment:transaction:localhost": "hardhat run scripts/safe/deployment/proposeTransaction.ts --network localhost", + "safe:propose:deployment:transaction:sepolia": "hardhat run scripts/safe/deployment/proposeTransaction.ts --network sepolia", + "safe:propose:deployment:transaction:mainnet": "hardhat run scripts/safe/deployment/proposeTransaction.ts --network mainnet", + "safe:confirm:deployment:transaction:localhost": "hardhat run scripts/safe/deployment/confirmTransaction.ts --network localhost", + "safe:confirm:deployment:transaction:sepolia": "hardhat run scripts/safe/deployment/confirmTransaction.ts --network sepolia", + "safe:confirm:deployment:transaction:mainnet": "hardhat run scripts/safe/deployment/confirmTransaction.ts --network mainnet", + "safe:execute:deployment:transaction:localhost": "hardhat run scripts/safe/deployment/executeTransaction.ts --network localhost", + "safe:execute:deployment:transaction:sepolia": "hardhat run scripts/safe/deployment/executeTransaction.ts --network sepolia", + "safe:execute:deployment:transaction:mainnet": "hardhat run scripts/safe/deployment/executeTransaction.ts --network mainnet", + "safe:propose:schedule:upgrade:transaction:localhost": "hardhat run scripts/safe/schedule-upgrade/proposeTransaction.ts --network localhost", + "safe:propose:schedule:upgrade:transaction:sepolia": "hardhat run scripts/safe/schedule-upgrade/proposeTransaction.ts --network sepolia", + "safe:propose:schedule:upgrade:transaction:mainnet": "hardhat run scripts/safe/schedule-upgrade/proposeTransaction.ts --network mainnet", + "safe:confirm:schedule:upgrade:transaction:localhost": "hardhat run scripts/safe/schedule-upgrade/confirmTransaction.ts --network localhost", + "safe:confirm:schedule:upgrade:transaction:sepolia": "hardhat run scripts/safe/schedule-upgrade/confirmTransaction.ts --network sepolia", + "safe:confirm:schedule:upgrade:transaction:mainnet": "hardhat run scripts/safe/schedule-upgrade/confirmTransaction.ts --network mainnet", + "safe:execute:schedule:upgrade:transaction:localhost": "hardhat run scripts/safe/schedule-upgrade/executeTransaction.ts --network localhost", + "safe:execute:schedule:upgrade:transaction:sepolia": "hardhat run scripts/safe/schedule-upgrade/executeTransaction.ts --network sepolia", + "safe:execute:schedule:upgrade:transaction:mainnet": "hardhat run scripts/safe/schedule-upgrade/executeTransaction.ts --network mainnet", + "safe:propose:execute:upgrade:transaction:localhost": "hardhat run scripts/safe/execute-upgrade/proposeTransaction.ts --network localhost", + "safe:propose:execute:upgrade:transaction:sepolia": "hardhat run scripts/safe/execute-upgrade/proposeTransaction.ts --network sepolia", + "safe:propose:execute:upgrade:transaction:mainnet": "hardhat run scripts/safe/execute-upgrade/proposeTransaction.ts --network mainnet", + "safe:confirm:execute:upgrade:transaction:localhost": "hardhat run scripts/safe/execute-upgrade/confirmTransaction.ts --network localhost", + "safe:confirm:execute:upgrade:transaction:sepolia": "hardhat run scripts/safe/execute-upgrade/confirmTransaction.ts --network sepolia", + "safe:confirm:execute:upgrade:transaction:mainnet": "hardhat run scripts/safe/execute-upgrade/confirmTransaction.ts --network mainnet", + "safe:execute:execute:upgrade:transaction:localhost": "hardhat run scripts/safe/execute-upgrade/executeTransaction.ts --network localhost", + "safe:execute:execute:upgrade:transaction:sepolia": "hardhat run scripts/safe/execute-upgrade/executeTransaction.ts --network sepolia", + "safe:execute:execute:upgrade:transaction:mainnet": "hardhat run scripts/safe/execute-upgrade/executeTransaction.ts --network mainnet" + }, "engines": { "node": ">=18.0.0" }, "dependencies": { + "@nomicfoundation/hardhat-ledger": "^1.0.3", "@nomicfoundation/hardhat-toolbox": "^5.0.0", "@openzeppelin/contracts-upgradeable": "^5.4.0", "@openzeppelin/hardhat-upgrades": "^3.9.1", + "@safe-global/api-kit": "^4.0.1", + "@safe-global/protocol-kit": "^6.1.2", + "@safe-global/types-kit": "^3.0.0", "@types/node": "^22.10.2", "dotenv": "^16.4.7", "hardhat": "^2.22.18", "ts-node": "^10.9.2", "typescript": "^5.7.2" + }, + "overrides": { + "cookie": "^1.0.1", + "tmp": "^0.2.5" } } diff --git a/scripts/deploy.ts b/scripts/deployment/deploy.ts similarity index 95% rename from scripts/deploy.ts rename to scripts/deployment/deploy.ts index e3549b8..fc26b6d 100644 --- a/scripts/deploy.ts +++ b/scripts/deployment/deploy.ts @@ -11,8 +11,8 @@ async function main() { console.log('\nDeployer:', deployer.address); console.log('Balance:', ethers.formatEther(await ethers.provider.getBalance(deployer.address)), 'ETH'); - // Read transactions from output.json - const outputPath = path.join(__dirname, './output.json'); + // Read transactions from output-deployment.json + const outputPath = path.join(__dirname, './output-deployment.json'); const transactions = JSON.parse(fs.readFileSync(outputPath, 'utf8')); console.log(`\nFound ${transactions.length} transactions to send\n`); diff --git a/scripts/generateFromMultisigDeterministic.ts b/scripts/deployment/deployFromMultisigDeterministic.ts similarity index 86% rename from scripts/generateFromMultisigDeterministic.ts rename to scripts/deployment/deployFromMultisigDeterministic.ts index 3da2a08..ffa72c4 100644 --- a/scripts/generateFromMultisigDeterministic.ts +++ b/scripts/deployment/deployFromMultisigDeterministic.ts @@ -3,7 +3,7 @@ import fs from 'fs'; import path from 'path'; async function main() { - const inputPath = path.join(__dirname, 'input.json'); + const inputPath = path.join(__dirname, '../input.json'); const input = JSON.parse(fs.readFileSync(inputPath, 'utf8')); const MULTISIG = input.MULTISIG; @@ -19,7 +19,7 @@ async function main() { const MIN_DELAY = 2 * 24 * 60 * 60; // 2 days // Factory address (Arachnid's Deterministic Deployment Proxy) - const FACTORY_ADDRESS = '0x4e59b44847b379578588920ca78fbf26c0b4956c'; + const FACTORY_ADDRESS = '0x4e59b44847b379578588920cA78FbF26c0B4956C'; console.log('Starting deterministic deployment generation...'); console.log('Multisig:', MULTISIG); @@ -38,7 +38,7 @@ async function main() { const timelockCreationCode = timelockDeployTx.data; const timelockInitCodeHash = ethers.keccak256(timelockCreationCode); - const timelockResult = mineSalt(timelockInitCodeHash, FACTORY_ADDRESS, '0x00ad', ''); + const timelockResult = await mineSalt(timelockInitCodeHash, FACTORY_ADDRESS, '0x00ad', ''); console.log(`Found Timelock Salt: ${timelockResult.salt}`); console.log(`Timelock Address: ${timelockResult.address}`); @@ -56,7 +56,7 @@ async function main() { const implCreationCode = (await BillionsNetworkToken.getDeployTransaction()).data; const implInitCodeHash = ethers.keccak256(implCreationCode); - const implResult = mineSalt(implInitCodeHash, FACTORY_ADDRESS, '0x001b', ''); + const implResult = await mineSalt(implInitCodeHash, FACTORY_ADDRESS, '0x001b', ''); console.log(`Found Implementation Salt: ${implResult.salt}`); console.log(`Implementation Address: ${implResult.address}`); @@ -92,7 +92,7 @@ async function main() { ).data; const proxyInitCodeHash = ethers.keccak256(proxyCreationCode); - const proxyResult = mineSalt(proxyInitCodeHash, FACTORY_ADDRESS, '0xb1110', ''); + const proxyResult = await mineSalt(proxyInitCodeHash, FACTORY_ADDRESS, '0xb1110', ''); console.log(`Found Proxy Salt: ${proxyResult.salt}`); console.log(`Proxy Address: ${proxyResult.address}`); @@ -105,17 +105,17 @@ async function main() { }); // Output to file - const outputPath = path.join(__dirname, 'output.json'); + const outputPath = path.join(__dirname, 'output-deployment.json'); fs.writeFileSync(outputPath, JSON.stringify(transactions, null, 2)); - console.log(`\nSaved ${transactions.length} transactions to output.json`); + console.log(`\nSaved ${transactions.length} transactions to output-deployment.json`); } -function mineSalt( +async function mineSalt( initCodeHash: string, factory: string, prefix: string, suffix: string -): { salt: string; address: string } { +): Promise<{ salt: string; address: string }> { let salt = 0n; let address = ''; const prefixLower = prefix.toLowerCase(); @@ -129,7 +129,11 @@ function mineSalt( address = ethers.getCreate2Address(factory, saltHex, initCodeHash).toLowerCase(); if (address.startsWith(prefixLower) && address.endsWith(suffixLower)) { - return { salt: saltHex, address: address }; + // Check if contract is already deployed + const existingCode = await ethers.provider.getCode(address); + if (existingCode === '0x') { + return { salt: saltHex, address: address }; + } } salt++; diff --git a/scripts/output.json b/scripts/deployment/output-deployment.json similarity index 100% rename from scripts/output.json rename to scripts/deployment/output-deployment.json diff --git a/scripts/verifyDeployment.ts b/scripts/deployment/verifyDeployment.ts similarity index 98% rename from scripts/verifyDeployment.ts rename to scripts/deployment/verifyDeployment.ts index b24ccac..7d5c849 100644 --- a/scripts/verifyDeployment.ts +++ b/scripts/deployment/verifyDeployment.ts @@ -10,8 +10,8 @@ async function main() { console.log('='.repeat(60)); // Read input and output files - const inputPath = path.join(__dirname, 'input.json'); - const outputPath = path.join(__dirname, 'output.json'); + const inputPath = path.join(__dirname, '../input.json'); + const outputPath = path.join(__dirname, 'output-deployment.json'); const input = JSON.parse(fs.readFileSync(inputPath, 'utf8')); const output = JSON.parse(fs.readFileSync(outputPath, 'utf8')); diff --git a/scripts/input.json b/scripts/input.json index 77f9018..3cd750e 100644 --- a/scripts/input.json +++ b/scripts/input.json @@ -1,3 +1,5 @@ { - "MULTISIG": "0xa31c18d0e9EBab1cB4d4B96193DCb44058F4bb75" + "MULTISIG": "0xa31c18d0e9EBab1cB4d4B96193DCb44058F4bb75", + "TIMELOCK_PROXY": "0x00AD9eB03CAF7c5E616ed843D46294E1932Cc8cA", + "BILLIONS_TOKEN_PROXY": "0xb1110919016846972056AB995054D65560D5f05E" } diff --git a/scripts/safe/deploySafe.ts b/scripts/safe/deploySafe.ts new file mode 100644 index 0000000..550be2c --- /dev/null +++ b/scripts/safe/deploySafe.ts @@ -0,0 +1,94 @@ +import Safe, { PredictedSafeProps, SafeAccountConfig, SafeDeploymentConfig } from '@safe-global/protocol-kit'; +import { defineChain } from 'viem'; +import { waitForTransactionReceipt } from 'viem/actions'; +import fs from 'fs'; +import path from 'path'; +import hre, { ethers } from 'hardhat'; + +async function main() { + const network = hre.network.name; + const safeConfigPath = path.join(__dirname, `./safe-config-${network}.json`); + + if (!fs.existsSync(safeConfigPath)) { + throw new Error(`Safe config file not found: ${safeConfigPath}`); + } + const safeConfig = JSON.parse(fs.readFileSync(safeConfigPath, 'utf-8')); + if (!safeConfig.chain) { + throw new Error(`chain not found in safe config file: ${safeConfigPath}`); + } + const chain = defineChain(safeConfig.chain); + + const safeAccountConfig: SafeAccountConfig = { + owners: safeConfig.owners, + threshold: Number(safeConfig.threshold), + // More optional properties + to: ethers.ZeroAddress, + data: '0x', + fallbackHandler: + safeConfig.contractNetworks?.[chain.id.toString()]?.fallbackHandlerAddress || ethers.ZeroAddress, + paymentToken: ethers.ZeroAddress, + payment: 0, + paymentReceiver: ethers.ZeroAddress, + }; + + const safeDeploymentConfig: SafeDeploymentConfig = { + saltNonce: '0', + safeVersion: '1.3.0', + deploymentType: 'canonical', + }; + + const predictedSafe: PredictedSafeProps = { + safeAccountConfig, + safeDeploymentConfig + }; + + const protocolKit = await Safe.init({ + provider: chain.rpcUrls.default.http[0], + signer: safeConfig.privateKeySender, + predictedSafe, + contractNetworks: safeConfig.contractNetworks, + }); + + const safeAddress = await protocolKit.getAddress(); + console.log('Predicted Safe Address:', safeAddress); + + const deploymentTransaction = await protocolKit.createSafeDeploymentTransaction(); + + const client = await protocolKit.getSafeProvider().getExternalSigner(); + + if (!client) { + throw new Error('No external signer available'); + } + + const transactionHash = await client.sendTransaction({ + to: deploymentTransaction.to as `0x${string}`, + value: BigInt(deploymentTransaction.value), + data: deploymentTransaction.data as `0x${string}`, + chain: chain as any, + }); + + await waitForTransactionReceipt(client, { hash: transactionHash }); + + console.log(`Transaction mined: ${transactionHash}`); + console.log('Safe deployed successfully!'); + + const newProtocolKit = await protocolKit.connect({ + safeAddress, + }); + + const isSafeDeployed = await newProtocolKit.isSafeDeployed(); // True + const safeAddressDeployed = await newProtocolKit.getAddress(); + const safeOwners = await newProtocolKit.getOwners(); + const safeThreshold = await newProtocolKit.getThreshold(); + console.log('Is Safe Deployed:', isSafeDeployed); + console.log('Safe Address:', safeAddressDeployed); + console.log('Safe Owners:', safeOwners); + console.log('Safe Threshold:', safeThreshold); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/safe/deployment/confirmTransaction.ts b/scripts/safe/deployment/confirmTransaction.ts new file mode 100644 index 0000000..21e927e --- /dev/null +++ b/scripts/safe/deployment/confirmTransaction.ts @@ -0,0 +1,16 @@ +import path from 'path'; +import { confirmTransaction } from '../../../helpers/safe'; + +async function main() { + const transactionIndex = 0; // Index of the transaction to confirm + // Read transactions from output-deployment.json + const transactionsOutputPath = path.join(__dirname, '../../deployment/output-deployment.json'); + await confirmTransaction(transactionIndex, transactionsOutputPath); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error('\nāŒ Deployment failed:', error); + process.exit(1); + }); diff --git a/scripts/safe/deployment/executeTransaction.ts b/scripts/safe/deployment/executeTransaction.ts new file mode 100644 index 0000000..b7a0e71 --- /dev/null +++ b/scripts/safe/deployment/executeTransaction.ts @@ -0,0 +1,16 @@ +import path from 'path'; +import { executeTransaction } from '../../../helpers/safe'; + +async function main() { + const transactionIndex = 0; // Index of the transaction to execute + // Read transactions from output-deployment.json + const transactionsOutputPath = path.join(__dirname, '../../deployment/output-deployment.json'); + await executeTransaction(transactionIndex, transactionsOutputPath); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error('\nāŒ Deployment failed:', error); + process.exit(1); + }); diff --git a/scripts/safe/deployment/proposeTransaction.ts b/scripts/safe/deployment/proposeTransaction.ts new file mode 100644 index 0000000..fd88dd0 --- /dev/null +++ b/scripts/safe/deployment/proposeTransaction.ts @@ -0,0 +1,16 @@ +import path from 'path'; +import { proposeTransaction } from '../../../helpers/safe'; + +async function main() { + const transactionIndex = 0; // Index of the transaction to propose + // Read transactions from output-deployment.json + const transactionsOutputPath = path.join(__dirname, '../../deployment/output-deployment.json'); + await proposeTransaction(transactionIndex, transactionsOutputPath); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error('\nāŒ Deployment failed:', error); + process.exit(1); + }); diff --git a/scripts/safe/execute-upgrade/confirmTransaction.ts b/scripts/safe/execute-upgrade/confirmTransaction.ts new file mode 100644 index 0000000..21d69d7 --- /dev/null +++ b/scripts/safe/execute-upgrade/confirmTransaction.ts @@ -0,0 +1,16 @@ +import path from 'path'; +import { confirmTransaction } from '../../../helpers/safe'; + +async function main() { + const transactionIndex = 0; // Index of the transaction to confirm + // Read transactions from output-execute-upgrade.json + const transactionsOutputPath = path.join(__dirname, '../../upgrade/output-execute-upgrade.json'); + await confirmTransaction(transactionIndex, transactionsOutputPath); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error('\nāŒ Deployment failed:', error); + process.exit(1); + }); diff --git a/scripts/safe/execute-upgrade/executeTransaction.ts b/scripts/safe/execute-upgrade/executeTransaction.ts new file mode 100644 index 0000000..8c9ab5d --- /dev/null +++ b/scripts/safe/execute-upgrade/executeTransaction.ts @@ -0,0 +1,16 @@ +import path from 'path'; +import { executeTransaction } from '../../../helpers/safe'; + +async function main() { + const transactionIndex = 0; // Index of the transaction to execute + // Read transactions from output-execute-upgrade.json + const transactionsOutputPath = path.join(__dirname, '../../upgrade/output-execute-upgrade.json'); + await executeTransaction(transactionIndex, transactionsOutputPath); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error('\nāŒ Deployment failed:', error); + process.exit(1); + }); diff --git a/scripts/safe/execute-upgrade/proposeTransaction.ts b/scripts/safe/execute-upgrade/proposeTransaction.ts new file mode 100644 index 0000000..8c4f670 --- /dev/null +++ b/scripts/safe/execute-upgrade/proposeTransaction.ts @@ -0,0 +1,16 @@ +import path from 'path'; +import { proposeTransaction } from '../../../helpers/safe'; + +async function main() { + const transactionIndex = 0; // Index of the transaction to propose + // Read transactions from output-execute-upgrade.json + const transactionsOutputPath = path.join(__dirname, '../../upgrade/output-execute-upgrade.json'); + await proposeTransaction(transactionIndex, transactionsOutputPath); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error('\nāŒ Deployment failed:', error); + process.exit(1); + }); diff --git a/scripts/safe/schedule-upgrade/confirmTransaction.ts b/scripts/safe/schedule-upgrade/confirmTransaction.ts new file mode 100644 index 0000000..13364ad --- /dev/null +++ b/scripts/safe/schedule-upgrade/confirmTransaction.ts @@ -0,0 +1,16 @@ +import path from 'path'; +import { confirmTransaction } from '../../../helpers/safe'; + +async function main() { + const transactionIndex = 0; // Index of the transaction to confirm + // Read transactions from output-schedule-upgrade.json + const transactionsOutputPath = path.join(__dirname, '../../upgrade/output-schedule-upgrade.json'); + await confirmTransaction(transactionIndex, transactionsOutputPath); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error('\nāŒ Deployment failed:', error); + process.exit(1); + }); diff --git a/scripts/safe/schedule-upgrade/executeTransaction.ts b/scripts/safe/schedule-upgrade/executeTransaction.ts new file mode 100644 index 0000000..021ed6f --- /dev/null +++ b/scripts/safe/schedule-upgrade/executeTransaction.ts @@ -0,0 +1,16 @@ +import path from 'path'; +import { executeTransaction } from '../../../helpers/safe'; + +async function main() { + const transactionIndex = 0; // Index of the transaction to execute + // Read transactions from output-schedule-upgrade.json + const transactionsOutputPath = path.join(__dirname, '../../upgrade/output-schedule-upgrade.json'); + await executeTransaction(transactionIndex, transactionsOutputPath); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error('\nāŒ Deployment failed:', error); + process.exit(1); + }); diff --git a/scripts/safe/schedule-upgrade/proposeTransaction.ts b/scripts/safe/schedule-upgrade/proposeTransaction.ts new file mode 100644 index 0000000..95fa805 --- /dev/null +++ b/scripts/safe/schedule-upgrade/proposeTransaction.ts @@ -0,0 +1,16 @@ +import path from 'path'; +import { proposeTransaction } from '../../../helpers/safe'; + +async function main() { + const transactionIndex = 0; // Index of the transaction to propose + // Read transactions from output-schedule-upgrade.json + const transactionsOutputPath = path.join(__dirname, '../../upgrade/output-schedule-upgrade.json'); + await proposeTransaction(transactionIndex, transactionsOutputPath); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error('\nāŒ Deployment failed:', error); + process.exit(1); + }); diff --git a/scripts/upgrade/execute.ts b/scripts/upgrade/execute.ts new file mode 100644 index 0000000..f68c2d1 --- /dev/null +++ b/scripts/upgrade/execute.ts @@ -0,0 +1,74 @@ +import { ethers, upgrades } from 'hardhat'; +import { TimelockController } from '../../typechain-types'; + +async function main() { + const [deployer] = await ethers.getSigners(); + + // Replace with your deployed proxy address + const TOKEN_PROXY_ADDRESS = process.env.TOKEN_PROXY_ADDRESS; + const TIMELOCK_ADDRESS = process.env.TIMELOCK_ADDRESS; + const TOKEN_NEW_IMPLEMENTATION_ADDRESS = process.env.TOKEN_NEW_IMPLEMENTATION_ADDRESS; + + if (!TOKEN_PROXY_ADDRESS) { + console.error('āŒ Please set TOKEN_PROXY_ADDRESS environment variable'); + console.error('Example: TOKEN_PROXY_ADDRESS=0x... npm run upgrade:sepolia'); + process.exit(1); + } + if (!TIMELOCK_ADDRESS) { + console.error('āŒ Please set TIMELOCK_ADDRESS environment variable'); + console.error('Example: TIMELOCK_ADDRESS=0x... npm run upgrade:sepolia'); + process.exit(1); + } + if (!TOKEN_NEW_IMPLEMENTATION_ADDRESS) { + console.error('āŒ Please set TOKEN_NEW_IMPLEMENTATION_ADDRESS environment variable'); + console.error('Example: TOKEN_NEW_IMPLEMENTATION_ADDRESS=0x... npm run upgrade:sepolia'); + process.exit(1); + } + + console.log('Executing upgrade for BillionsNetworkToken at:', TOKEN_PROXY_ADDRESS); + console.log('Executor account:', deployer.address); + + // As we are working with same proxy the storage is already initialized + const initializeData = '0x'; + + const adminAddress = await upgrades.erc1967.getAdminAddress(TOKEN_PROXY_ADDRESS); + const proxyAdmin = await ethers.getContractAt('ProxyAdmin', adminAddress); + const proxyAdminAddress = await proxyAdmin.getAddress(); + + // Encode upgradeAndCall transaction for the token ProxyAdmin to be executed by Timelock + const upgradeAndCallData = proxyAdmin.interface.encodeFunctionData('upgradeAndCall', [ + TOKEN_PROXY_ADDRESS, + TOKEN_NEW_IMPLEMENTATION_ADDRESS, + initializeData, + ]); + + const timelock = (await ethers.getContractAt( + 'TimelockController', + TIMELOCK_ADDRESS + )) as unknown as TimelockController; + + // Execute the upgrade via timelock by the executor + const executeTx = await timelock + .connect(deployer) + .execute(proxyAdminAddress, 0, upgradeAndCallData, ethers.ZeroHash, ethers.ZeroHash); + await executeTx.wait(); + console.log('tx hash for Timelock execute:', executeTx.hash); + console.log('āœ… Upgrade executed via Timelock!'); + + const upgraded = await ethers.getContractAt('BillionsNetworkToken', TOKEN_PROXY_ADDRESS); + const implementationAddress = await upgrades.erc1967.getImplementationAddress(TOKEN_PROXY_ADDRESS); + console.log('New implementation address:', implementationAddress); + + // Verify state is preserved + console.log('\n--- Verifying State ---'); + console.log('Token name:', await upgraded.name()); + console.log('Token symbol:', await upgraded.symbol()); + console.log('Total supply:', ethers.formatEther(await upgraded.totalSupply())); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/upgrade/schedule.ts b/scripts/upgrade/schedule.ts new file mode 100644 index 0000000..77c4515 --- /dev/null +++ b/scripts/upgrade/schedule.ts @@ -0,0 +1,80 @@ +import { ethers, upgrades } from 'hardhat'; +import { TimelockController } from '../../typechain-types'; +import { verifyContract } from '../../helpers/utils'; + +async function main() { + const [deployer] = await ethers.getSigners(); + + // Replace with your deployed proxy address + const TOKEN_PROXY_ADDRESS = process.env.TOKEN_PROXY_ADDRESS; + const TIMELOCK_ADDRESS = process.env.TIMELOCK_ADDRESS; + const MIN_DELAY = process.env.TIMELOCK_MIN_DELAY ? parseInt(process.env.TIMELOCK_MIN_DELAY) : 2 * 24 * 60 * 60; // Default to 2 days + + if (!TOKEN_PROXY_ADDRESS) { + console.error('āŒ Please set TOKEN_PROXY_ADDRESS environment variable'); + console.error('Example: TOKEN_PROXY_ADDRESS=0x... npm run upgrade:sepolia'); + process.exit(1); + } + if (!TIMELOCK_ADDRESS) { + console.error('āŒ Please set TIMELOCK_ADDRESS environment variable'); + console.error('Example: TIMELOCK_ADDRESS=0x... npm run upgrade:sepolia'); + process.exit(1); + } + + console.log('Scheduling upgrade for BillionsNetworkToken at:', TOKEN_PROXY_ADDRESS); + console.log('Proposer account:', deployer.address); + + // Deploy new implementation + const BillionsNetworkTokenV2 = await ethers.getContractFactory('BillionsNetworkToken'); + + console.log('Deploying new implementation...'); + const newImplementation = await BillionsNetworkTokenV2.deploy(); + await newImplementation.waitForDeployment(); + const newImplementationAddress = await newImplementation.getAddress(); + + console.log('New implementation:', newImplementationAddress); + + // As we are working with same proxy the storage is already initialized + const initializeData = '0x'; + + const adminAddress = await upgrades.erc1967.getAdminAddress(TOKEN_PROXY_ADDRESS); + const proxyAdmin = await ethers.getContractAt('ProxyAdmin', adminAddress); + const proxyAdminAddress = await proxyAdmin.getAddress(); + + // Encode upgradeAndCall transaction for the token ProxyAdmin to be executed by Timelock + const upgradeAndCallData = proxyAdmin.interface.encodeFunctionData('upgradeAndCall', [ + TOKEN_PROXY_ADDRESS, + newImplementationAddress, + initializeData, + ]); + + const timelock = (await ethers.getContractAt( + 'TimelockController', + TIMELOCK_ADDRESS + )) as unknown as TimelockController; + + // propose and execute via timelock by the proposer + const proposeTx = await timelock + .connect(deployer) + .schedule(proxyAdminAddress, 0, upgradeAndCallData, ethers.ZeroHash, ethers.ZeroHash, MIN_DELAY); + await proposeTx.wait(); + console.log('tx hash for Timelock schedule:', proposeTx.hash); + console.log('āœ… Upgrade scheduled via Timelock!'); + + console.log('\nšŸ“ Save these addresses:'); + console.log('----------------------------------'); + console.log('TOKEN_NEW_IMPLEMENTATION_ADDRESS=', newImplementationAddress); + console.log('----------------------------------'); + + await verifyContract(newImplementationAddress, { + constructorArgsImplementation: [], + libraries: {}, + }); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/upgrade/upgradeFromMultisigDeterministic.ts b/scripts/upgrade/upgradeFromMultisigDeterministic.ts new file mode 100644 index 0000000..7ce86bd --- /dev/null +++ b/scripts/upgrade/upgradeFromMultisigDeterministic.ts @@ -0,0 +1,161 @@ +import { ethers, upgrades } from 'hardhat'; +import fs from 'fs'; +import path from 'path'; +import { TimelockController } from '../../typechain-types'; + +async function main() { + const inputPath = path.join(__dirname, '../input.json'); + const input = JSON.parse(fs.readFileSync(inputPath, 'utf8')); + const MULTISIG = input.MULTISIG; + const TIMELOCK_PROXY = input.TIMELOCK_PROXY; + const BILLIONS_TOKEN_PROXY = input.BILLIONS_TOKEN_PROXY; + const MIN_DELAY = process.env.TIMELOCK_MIN_DELAY ? parseInt(process.env.TIMELOCK_MIN_DELAY) : 2 * 24 * 60 * 60; // Default to 2 days + + if (!ethers.isAddress(MULTISIG)) { + throw new Error('Invalid MULTISIG address in input.json'); + } + if (!ethers.isAddress(TIMELOCK_PROXY)) { + throw new Error('Invalid TIMELOCK_PROXY address in input.json'); + } + if (!ethers.isAddress(BILLIONS_TOKEN_PROXY)) { + throw new Error('Invalid BILLIONS_TOKEN_PROXY address in input.json'); + } + + // Factory address (Arachnid's Deterministic Deployment Proxy) + const FACTORY_ADDRESS = '0x4e59b44847b379578588920cA78FbF26c0B4956C'; + + console.log('Starting schedule upgrade generation...'); + console.log('Multisig:', MULTISIG); + + const scheduleTransactions: any[] = []; + const executeTransactions: any[] = []; + + // --- 1. Mine Implementation (Starts with 001b) --- + console.log('\n--- 1. Mining Implementation (Starts with 001b) ---'); + const BillionsNetworkToken = await ethers.getContractFactory('BillionsNetworkToken'); + const implCreationCode = (await BillionsNetworkToken.getDeployTransaction()).data; + const implInitCodeHash = ethers.keccak256(implCreationCode); + + const implResult = await mineSalt(implInitCodeHash, FACTORY_ADDRESS, '0x001b', ''); + console.log(`Found Implementation Salt: ${implResult.salt}`); + console.log(`Implementation Address: ${implResult.address}`); + + scheduleTransactions.push({ + name: 'Deploy new Implementation', + to: FACTORY_ADDRESS, + value: '0', + data: ethers.concat([implResult.salt, implCreationCode]), + predictedAddress: implResult.address, + }); + + // --- 2. Call TimelockController schedule --- + console.log('\n--- 2. Calling TimelockController schedule ---'); + + const adminAddress = await upgrades.erc1967.getAdminAddress(BILLIONS_TOKEN_PROXY); + const proxyAdmin = await ethers.getContractAt('ProxyAdmin', adminAddress); + const proxyAdminAddress = await proxyAdmin.getAddress(); + + const initData = "0x"; // No initialization data for upgrade + + // Encode upgradeAndCall transaction for the token ProxyAdmin to be executed by Timelock + const upgradeAndCallData = proxyAdmin.interface.encodeFunctionData('upgradeAndCall', [ + BILLIONS_TOKEN_PROXY, + implResult.address, + initData, + ]); + + const timelock = (await ethers.getContractAt( + 'TimelockController', + TIMELOCK_PROXY + )) as unknown as TimelockController; + + // schedule via timelock + const encodeScheduleCallData = await timelock.interface.encodeFunctionData('schedule', [ + proxyAdminAddress, + 0, + upgradeAndCallData, + ethers.ZeroHash, + ethers.ZeroHash, + MIN_DELAY, + ]); + + scheduleTransactions.push({ + name: 'Schedule upgrade via Timelock', + to: TIMELOCK_PROXY, + value: '0', + data: encodeScheduleCallData, + predictedAddress: '', + }); + + // Output to file + const outputPath = path.join(__dirname, 'output-schedule-upgrade.json'); + fs.writeFileSync(outputPath, JSON.stringify(scheduleTransactions, null, 2)); + console.log(`\nSaved ${scheduleTransactions.length} transactions to output-schedule-upgrade.json`); + + // execute execute via timelock + const encodeExecuteCallData = await timelock.interface.encodeFunctionData('execute', [ + proxyAdminAddress, + 0, + upgradeAndCallData, + ethers.ZeroHash, + ethers.ZeroHash, + ]); + + executeTransactions.push({ + name: 'Execute upgrade via Timelock', + to: TIMELOCK_PROXY, + value: '0', + data: encodeExecuteCallData, + predictedAddress: '', + }); + + // Output to file + const outputPathExecute = path.join(__dirname, 'output-execute-upgrade.json'); + fs.writeFileSync(outputPathExecute, JSON.stringify(executeTransactions, null, 2)); + console.log(`\nSaved ${executeTransactions.length} transactions to output-execute-upgrade.json`); +} + +async function mineSalt( + initCodeHash: string, + factory: string, + prefix: string, + suffix: string +): Promise<{ salt: string; address: string }> { + let salt = 0n; + let address = ''; + const prefixLower = prefix.toLowerCase(); + const suffixLower = suffix.toLowerCase(); + + const start = Date.now(); + let lastLog = start; + + while (true) { + const saltHex = ethers.zeroPadValue(ethers.toBeHex(salt), 32); + address = ethers.getCreate2Address(factory, saltHex, initCodeHash).toLowerCase(); + + if (address.startsWith(prefixLower) && address.endsWith(suffixLower)) { + // Check if contract is already deployed + const existingCode = await ethers.provider.getCode(address); + if (existingCode === '0x') { + return { salt: saltHex, address: address }; + } + } + + salt++; + + if (salt % 100000n === 0n) { + const now = Date.now(); + if (now - lastLog > 5000) { + console.log(`Mining... Checked ${salt} salts. Current: ${address}`); + lastLog = now; + } + } + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/upgrade/verifyUpgrade.ts b/scripts/upgrade/verifyUpgrade.ts new file mode 100644 index 0000000..ace260e --- /dev/null +++ b/scripts/upgrade/verifyUpgrade.ts @@ -0,0 +1,197 @@ +import { ethers, upgrades } from 'hardhat'; +import hre from 'hardhat'; +import fs from 'fs'; +import path from 'path'; +import { expect } from 'chai'; + +async function main() { + console.log('='.repeat(60)); + console.log('Billions Network Token - Deployment Verification'); + console.log('='.repeat(60)); + + // Read input and output files + const inputPath = path.join(__dirname, '../input.json'); + const outputPath = path.join(__dirname, 'output-schedule-upgrade.json'); + + const input = JSON.parse(fs.readFileSync(inputPath, 'utf8')); + const output = JSON.parse(fs.readFileSync(outputPath, 'utf8')); + + const MULTISIG = input.MULTISIG; + const proxyAddress = input.BILLIONS_TOKEN_PROXY; + const timelockAddress = input.TIMELOCK_PROXY; + + // Extract deployed addresses from output + const implementationAddress = output.find((tx: any) => tx.name === 'Deploy new Implementation')?.predictedAddress; + + console.log('\nšŸ“‹ Configuration:'); + console.log(` Multisig: ${MULTISIG}`); + console.log(` Implementation: ${implementationAddress}`); + + // Expected values + const TOKEN_NAME = 'Billions Network Token'; + const TOKEN_SYMBOL = 'BILL'; + const DECIMALS = 18; + const TOTAL_SUPPLY = ethers.parseUnits('10000000000', DECIMALS); // 10 billion + const MIN_DELAY = 2 * 24 * 60 * 60; // 2 days + + // ============================================================ + // SECTION 1: VERIFY CONTRACTS ON ETHERSCAN + // ============================================================ + console.log('\n' + '='.repeat(60)); + console.log('SECTION 1: ETHERSCAN VERIFICATION'); + console.log('='.repeat(60)); + + // 1.1 Verify Implementation (BillionsNetworkToken) + console.log('\nšŸ” Verifying Implementation (BillionsNetworkToken)...'); + try { + await hre.run('verify:verify', { + address: implementationAddress, + constructorArguments: [], + }); + console.log(' āœ… Implementation verified'); + } catch (error: any) { + if (error.message.toLowerCase().includes('already verified')) { + console.log(' āœ… Implementation already verified'); + } else { + console.log(` āŒ Implementation verification failed: ${error.message}`); + } + } + + // 1.2 Get ProxyAdmin address from proxy + const proxyAdminAddress = await upgrades.erc1967.getAdminAddress(proxyAddress); + console.log(`\nšŸ“‹ ProxyAdmin Address: ${proxyAdminAddress}`); + + // ============================================================ + // SECTION 2: CHECK DEPLOYED PARAMETERS + // ============================================================ + console.log('\n' + '='.repeat(60)); + console.log('SECTION 2: PARAMETER VERIFICATION'); + console.log('='.repeat(60)); + + // Get contract instances + const token = await ethers.getContractAt('BillionsNetworkToken', proxyAddress); + const timelock = await ethers.getContractAt('TimelockController', timelockAddress); + + // 2.1 Check token name and symbol + console.log('\nšŸ“¦ Token Parameters:'); + const name = await token.name(); + const symbol = await token.symbol(); + const decimals = await token.decimals(); + const totalSupply = await token.totalSupply(); + + console.log(` Name: ${name}`); + console.log(` Symbol: ${symbol}`); + console.log(` Decimals: ${decimals}`); + console.log(` Supply: ${ethers.formatUnits(totalSupply, DECIMALS)} tokens`); + + expect(name).to.equal(TOKEN_NAME, 'Token name mismatch'); + console.log(' āœ… Name matches expected value'); + + expect(symbol).to.equal(TOKEN_SYMBOL, 'Token symbol mismatch'); + console.log(' āœ… Symbol matches expected value'); + + expect(decimals).to.equal(DECIMALS, 'Decimals mismatch'); + console.log(' āœ… Decimals matches expected value'); + + expect(totalSupply).to.equal(TOTAL_SUPPLY, 'Total supply mismatch'); + console.log(' āœ… Total supply matches expected value'); + + // 2.2 Check all balance belongs to multisig + console.log('\nšŸ’° Balance Check:'); + const multisigBalance = await token.balanceOf(MULTISIG); + console.log(` Multisig Balance: ${ethers.formatUnits(multisigBalance, DECIMALS)} tokens`); + + expect(multisigBalance).to.equal(TOTAL_SUPPLY, 'Multisig does not hold all tokens'); + console.log(' āœ… Multisig holds 100% of total supply'); + + // 2.3 Check proxy admin address matches + console.log('\nšŸ” Proxy Admin Check:'); + const actualProxyAdmin = await upgrades.erc1967.getAdminAddress(proxyAddress); + console.log(` ProxyAdmin Address: ${actualProxyAdmin}`); + expect(actualProxyAdmin.toLowerCase()).to.equal(proxyAdminAddress.toLowerCase(), 'ProxyAdmin mismatch'); + + // 2.4 Check implementation address + const actualImplementation = await upgrades.erc1967.getImplementationAddress(proxyAddress); + console.log(` Implementation: ${actualImplementation}`); + + expect(actualImplementation.toLowerCase()).to.equal(implementationAddress.toLowerCase(), 'Implementation mismatch'); + console.log(' āœ… Implementation address matches'); + + // 2.5 Check owner of ProxyAdmin is Timelock + console.log('\nšŸ”’ ProxyAdmin Owner Check:'); + const proxyAdmin = await ethers.getContractAt('ProxyAdmin', proxyAdminAddress); + const proxyAdminOwner = await proxyAdmin.owner(); + console.log(` ProxyAdmin Owner: ${proxyAdminOwner}`); + + expect(proxyAdminOwner.toLowerCase()).to.equal( + timelockAddress.toLowerCase(), + 'ProxyAdmin owner should be Timelock' + ); + console.log(' āœ… ProxyAdmin owner is Timelock'); + + // 2.6 Check Timelock roles + console.log('\nā° Timelock Roles Check:'); + + // Role constants from TimelockController + const PROPOSER_ROLE = await timelock.PROPOSER_ROLE(); + const EXECUTOR_ROLE = await timelock.EXECUTOR_ROLE(); + const CANCELLER_ROLE = await timelock.CANCELLER_ROLE(); + const DEFAULT_ADMIN_ROLE = await timelock.DEFAULT_ADMIN_ROLE(); + + console.log(` PROPOSER_ROLE: ${PROPOSER_ROLE}`); + console.log(` EXECUTOR_ROLE: ${EXECUTOR_ROLE}`); + console.log(` CANCELLER_ROLE: ${CANCELLER_ROLE}`); + console.log(` DEFAULT_ADMIN_ROLE: ${DEFAULT_ADMIN_ROLE}`); + + // Check multisig has PROPOSER_ROLE + const hasProposerRole = await timelock.hasRole(PROPOSER_ROLE, MULTISIG); + console.log(`\n Multisig has PROPOSER_ROLE: ${hasProposerRole}`); + expect(hasProposerRole).to.be.true; + console.log(' āœ… Multisig has PROPOSER_ROLE'); + + // Check multisig has EXECUTOR_ROLE + const hasExecutorRole = await timelock.hasRole(EXECUTOR_ROLE, MULTISIG); + console.log(` Multisig has EXECUTOR_ROLE: ${hasExecutorRole}`); + expect(hasExecutorRole).to.be.true; + console.log(' āœ… Multisig has EXECUTOR_ROLE'); + + // Check multisig has CANCELLER_ROLE + const hasCancellerRole = await timelock.hasRole(CANCELLER_ROLE, MULTISIG); + console.log(` Multisig has CANCELLER_ROLE: ${hasCancellerRole}`); + expect(hasCancellerRole).to.be.true; + console.log(' āœ… Multisig has CANCELLER_ROLE'); + + // Check multisig has DEFAULT_ADMIN_ROLE + const hasAdminRole = await timelock.hasRole(DEFAULT_ADMIN_ROLE, MULTISIG); + console.log(` Multisig has ADMIN_ROLE: ${hasAdminRole}`); + expect(hasAdminRole).to.be.true; + console.log(' āœ… Multisig has DEFAULT_ADMIN_ROLE'); + + // Check min delay + console.log('\nā±ļø Timelock Min Delay:'); + const minDelay = await timelock.getMinDelay(); + console.log(` Min Delay: ${minDelay} seconds (${Number(minDelay) / 86400} days)`); + expect(minDelay).to.equal(MIN_DELAY, 'Min delay mismatch'); + console.log(' āœ… Min delay matches expected value (2 days)'); + + // ============================================================ + // SUMMARY + // ============================================================ + console.log('\n' + '='.repeat(60)); + console.log('VERIFICATION SUMMARY'); + console.log('='.repeat(60)); + console.log('\nāœ… All checks passed!'); + console.log('\nDeployed Addresses:'); + console.log(` Token Proxy: ${proxyAddress}`); + console.log(` Implementation: ${implementationAddress}`); + console.log(` ProxyAdmin: ${proxyAdminAddress}`); + console.log(` Timelock: ${timelockAddress}`); + console.log(` Multisig: ${MULTISIG}`); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error('\nāŒ Verification failed:', error); + process.exit(1); + }); diff --git a/test/BillionsNetworkToken.test.ts b/test/BillionsNetworkToken.test.ts index 0337505..26e4919 100644 --- a/test/BillionsNetworkToken.test.ts +++ b/test/BillionsNetworkToken.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { ethers, upgrades } from 'hardhat'; -import { BillionsNetworkToken } from '../typechain-types'; +import { BillionsNetworkToken, TimelockController } from '../typechain-types'; import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; describe('Billions Network Token (BILL)', function () { @@ -8,11 +8,14 @@ describe('Billions Network Token (BILL)', function () { let owner: SignerWithAddress; let user1: SignerWithAddress; let user2: SignerWithAddress; + let timelock: TimelockController; const TOKEN_NAME = 'Billions Network Token'; const TOKEN_SYMBOL = 'BILL'; const DECIMALS = 18; const TOTAL_SUPPLY = ethers.parseUnits('10000000000', DECIMALS); // 10 billion tokens + // Timelock Parameters + const MIN_DELAY = 2 * 24 * 60 * 60; // 172_800 seconds = 2 days beforeEach(async function () { [owner, user1, user2] = await ethers.getSigners(); @@ -28,6 +31,30 @@ describe('Billions Network Token (BILL)', function () { } )) as unknown as BillionsNetworkToken; await token.waitForDeployment(); + + const tokenAddress = await token.getAddress(); + const adminAddress = await upgrades.erc1967.getAdminAddress(tokenAddress); + + const TimelockController = await ethers.getContractFactory('TimelockController'); + + const proposers = [user1.address]; // Addresses that can propose upgrades + const executors = [user2.address]; // Addresses that can execute upgrades + const admin = owner.address; // Admin address (can grant/revoke roles) + + timelock = (await TimelockController.deploy( + MIN_DELAY, + proposers, + executors, + admin + )) as unknown as TimelockController; + await timelock.waitForDeployment(); + + const timelockAddress = await timelock.getAddress(); + + // Transfer ProxyAdmin ownership to Timelock + const proxyAdmin = await ethers.getContractAt('ProxyAdmin', adminAddress); + const transferTx = await proxyAdmin.transferOwnership(timelockAddress); + await transferTx.wait(); }); describe('Deployment', function () { @@ -221,16 +248,55 @@ describe('Billions Network Token (BILL)', function () { }); describe('Upgradability', function () { - it('Should be upgradeable with preserved state', async function () { + it('Should be upgradeable via TimelockController with preserved state', async function () { const proxyAddress = await token.getAddress(); + const adminAddress = await upgrades.erc1967.getAdminAddress(proxyAddress); + const proxyAdmin = await ethers.getContractAt('ProxyAdmin', adminAddress); + const proxyAdminAddress = await proxyAdmin.getAddress(); // Perform some actions const transferAmount = ethers.parseUnits('500000', DECIMALS); await token.transfer(user1.address, transferAmount); - // Upgrade + // Propose and execute upgrade via Timelock + + // Deploy new implementation const BillionsNetworkTokenV2 = await ethers.getContractFactory('BillionsNetworkToken'); - const upgraded = await upgrades.upgradeProxy(proxyAddress, BillionsNetworkTokenV2); + const newImplementation = await BillionsNetworkTokenV2.deploy(); + await newImplementation.waitForDeployment(); + const newImplementationAddress = await newImplementation.getAddress(); + + // As we are working with same proxy the storage is already initialized + const initializeData = '0x'; + + // Encode upgradeAndCall transaction for the token ProxyAdmin to be executed by Timelock + const upgradeAndCallData = proxyAdmin.interface.encodeFunctionData('upgradeAndCall', [ + proxyAddress, + newImplementationAddress, + initializeData, + ]); + + // propose and execute via timelock by the proposer + const proposeTx = await timelock + .connect(user1) + .schedule(proxyAdminAddress, 0, upgradeAndCallData, ethers.ZeroHash, ethers.ZeroHash, MIN_DELAY); + await proposeTx.wait(); + + // Increase time to surpass minimum delay + await ethers.provider.send('evm_increaseTime', [MIN_DELAY + 1]); + await ethers.provider.send('evm_mine', []); + + // Execute the upgrade via timelock by the executor + const executeTx = await timelock + .connect(user2) + .execute(proxyAdminAddress, 0, upgradeAndCallData, ethers.ZeroHash, ethers.ZeroHash); + await executeTx.wait(); + + // Get upgraded contract instance + const upgraded = (await ethers.getContractAt( + 'BillionsNetworkToken', + proxyAddress + )) as unknown as BillionsNetworkToken; // Verify proxy address unchanged expect(await upgraded.getAddress()).to.equal(proxyAddress); @@ -241,5 +307,206 @@ describe('Billions Network Token (BILL)', function () { expect(await upgraded.totalSupply()).to.equal(TOTAL_SUPPLY); expect(await upgraded.balanceOf(user1.address)).to.equal(transferAmount); }); + + it('Should fail if TimelockController proposer is invalid', async function () { + const proxyAddress = await token.getAddress(); + const adminAddress = await upgrades.erc1967.getAdminAddress(proxyAddress); + const proxyAdmin = await ethers.getContractAt('ProxyAdmin', adminAddress); + const proxyAdminAddress = await proxyAdmin.getAddress(); + + // Propose and execute upgrade via Timelock + + // Deploy new implementation + const BillionsNetworkTokenV2 = await ethers.getContractFactory('BillionsNetworkToken'); + const newImplementation = await BillionsNetworkTokenV2.deploy(); + await newImplementation.waitForDeployment(); + const newImplementationAddress = await newImplementation.getAddress(); + + // As we are working with same proxy the storage is already initialized + const initializeData = '0x'; + + // Encode upgradeAndCall transaction for the token ProxyAdmin to be executed by Timelock + const upgradeAndCallData = proxyAdmin.interface.encodeFunctionData('upgradeAndCall', [ + proxyAddress, + newImplementationAddress, + initializeData, + ]); + + // propose and execute via timelock by invalid proposer + await expect( + timelock + .connect(user2) + .schedule(proxyAdminAddress, 0, upgradeAndCallData, ethers.ZeroHash, ethers.ZeroHash, MIN_DELAY) + ).to.be.revertedWithCustomError(timelock, 'AccessControlUnauthorizedAccount'); + }); + + it('Should fail if TimelockController executor is invalid', async function () { + const proxyAddress = await token.getAddress(); + const adminAddress = await upgrades.erc1967.getAdminAddress(proxyAddress); + const proxyAdmin = await ethers.getContractAt('ProxyAdmin', adminAddress); + const proxyAdminAddress = await proxyAdmin.getAddress(); + + // Propose and execute upgrade via Timelock + + // Deploy new implementation + const BillionsNetworkTokenV2 = await ethers.getContractFactory('BillionsNetworkToken'); + const newImplementation = await BillionsNetworkTokenV2.deploy(); + await newImplementation.waitForDeployment(); + const newImplementationAddress = await newImplementation.getAddress(); + + // As we are working with same proxy the storage is already initialized + const initializeData = '0x'; + + // Encode upgradeAndCall transaction for the token ProxyAdmin to be executed by Timelock + const upgradeAndCallData = proxyAdmin.interface.encodeFunctionData('upgradeAndCall', [ + proxyAddress, + newImplementationAddress, + initializeData, + ]); + + // propose and execute via timelock by the proposer + const proposeTx = await timelock + .connect(user1) + .schedule(proxyAdminAddress, 0, upgradeAndCallData, ethers.ZeroHash, ethers.ZeroHash, MIN_DELAY); + await proposeTx.wait(); + + // Increase time to surpass minimum delay + await ethers.provider.send('evm_increaseTime', [MIN_DELAY + 1]); + await ethers.provider.send('evm_mine', []); + + // Execute the upgrade via timelock by the executor + await expect( + timelock + .connect(user1) + .execute(proxyAdminAddress, 0, upgradeAndCallData, ethers.ZeroHash, ethers.ZeroHash) + ).to.be.revertedWithCustomError(timelock, 'AccessControlUnauthorizedAccount'); + }); + + it('Should fail if proposed schedule is less than TimelockController minimum delay', async function () { + const proxyAddress = await token.getAddress(); + const adminAddress = await upgrades.erc1967.getAdminAddress(proxyAddress); + const proxyAdmin = await ethers.getContractAt('ProxyAdmin', adminAddress); + const proxyAdminAddress = await proxyAdmin.getAddress(); + + // Propose and execute upgrade via Timelock + + // Deploy new implementation + const BillionsNetworkTokenV2 = await ethers.getContractFactory('BillionsNetworkToken'); + const newImplementation = await BillionsNetworkTokenV2.deploy(); + await newImplementation.waitForDeployment(); + const newImplementationAddress = await newImplementation.getAddress(); + + // As we are working with same proxy the storage is already initialized + const initializeData = '0x'; + + // Encode upgradeAndCall transaction for the token ProxyAdmin to be executed by Timelock + const upgradeAndCallData = proxyAdmin.interface.encodeFunctionData('upgradeAndCall', [ + proxyAddress, + newImplementationAddress, + initializeData, + ]); + + // propose and execute via timelock by the proposer + await expect( + timelock + .connect(user1) + .schedule(proxyAdminAddress, 0, upgradeAndCallData, ethers.ZeroHash, ethers.ZeroHash, MIN_DELAY - 1) + ).to.be.revertedWithCustomError(timelock, 'TimelockInsufficientDelay'); + }); + + it('Should fail if execution happens before TimelockController delay scheduled', async function () { + const proxyAddress = await token.getAddress(); + const adminAddress = await upgrades.erc1967.getAdminAddress(proxyAddress); + const proxyAdmin = await ethers.getContractAt('ProxyAdmin', adminAddress); + const proxyAdminAddress = await proxyAdmin.getAddress(); + + // Propose and execute upgrade via Timelock + + // Deploy new implementation + const BillionsNetworkTokenV2 = await ethers.getContractFactory('BillionsNetworkToken'); + const newImplementation = await BillionsNetworkTokenV2.deploy(); + await newImplementation.waitForDeployment(); + const newImplementationAddress = await newImplementation.getAddress(); + + // As we are working with same proxy the storage is already initialized + const initializeData = '0x'; + + // Encode upgradeAndCall transaction for the token ProxyAdmin to be executed by Timelock + const upgradeAndCallData = proxyAdmin.interface.encodeFunctionData('upgradeAndCall', [ + proxyAddress, + newImplementationAddress, + initializeData, + ]); + + // propose and execute via timelock by the proposer + const proposeTx = await timelock + .connect(user1) + .schedule(proxyAdminAddress, 0, upgradeAndCallData, ethers.ZeroHash, ethers.ZeroHash, MIN_DELAY); + await proposeTx.wait(); + + // Execute the upgrade via timelock by the executor + await expect( + timelock + .connect(user2) + .execute(proxyAdminAddress, 0, upgradeAndCallData, ethers.ZeroHash, ethers.ZeroHash) + ).to.be.revertedWithCustomError(timelock, 'TimelockUnexpectedOperationState'); + }); + + it('Should fail if scheduled data transaction TimelockController differs from executed data transaction', async function () { + const proxyAddress = await token.getAddress(); + const adminAddress = await upgrades.erc1967.getAdminAddress(proxyAddress); + const proxyAdmin = await ethers.getContractAt('ProxyAdmin', adminAddress); + const proxyAdminAddress = await proxyAdmin.getAddress(); + + // Perform some actions + const transferAmount = ethers.parseUnits('500000', DECIMALS); + await token.transfer(user1.address, transferAmount); + + // Propose and execute upgrade via Timelock + + // Deploy new implementation + const BillionsNetworkTokenV2 = await ethers.getContractFactory('BillionsNetworkToken'); + const newImplementation = await BillionsNetworkTokenV2.deploy(); + await newImplementation.waitForDeployment(); + const newImplementationAddress = await newImplementation.getAddress(); + + // As we are working with same proxy the storage is already initialized + const initializeData = '0x'; + + // Encode upgradeAndCall transaction for the token ProxyAdmin to be executed by Timelock + const upgradeAndCallData = proxyAdmin.interface.encodeFunctionData('upgradeAndCall', [ + proxyAddress, + newImplementationAddress, + initializeData, + ]); + + // propose and execute via timelock by the proposer + const proposeTx = await timelock + .connect(user1) + .schedule(proxyAdminAddress, 0, upgradeAndCallData, ethers.ZeroHash, ethers.ZeroHash, MIN_DELAY); + await proposeTx.wait(); + + // Increase time to surpass minimum delay + await ethers.provider.send('evm_increaseTime', [MIN_DELAY + 1]); + await ethers.provider.send('evm_mine', []); + + // Deploy another new implementation to create different upgrade data for execution + const newImplementation2 = await BillionsNetworkTokenV2.deploy(); + await newImplementation2.waitForDeployment(); + const newImplementation2Address = await newImplementation2.getAddress(); + + const upgradeAndCallData2 = proxyAdmin.interface.encodeFunctionData('upgradeAndCall', [ + proxyAddress, + newImplementation2Address, + initializeData, + ]); + + // Execute the upgrade via timelock by the executor + await expect( + timelock + .connect(user2) + .execute(proxyAdminAddress, 0, upgradeAndCallData2, ethers.ZeroHash, ethers.ZeroHash) + ).to.be.revertedWithCustomError(timelock, 'TimelockUnexpectedOperationState'); + }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 53b905a..8d163af 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ }, "include": [ "./scripts", + "./helpers", "./test", "./hardhat.config.ts" ],