diff --git a/.github/workflows/deploy_and_release.yml b/.github/workflows/deploy_and_release.yml index 04a2822..273494e 100644 --- a/.github/workflows/deploy_and_release.yml +++ b/.github/workflows/deploy_and_release.yml @@ -17,6 +17,8 @@ jobs: - run: npm run test env: MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP: ${{ secrets.PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP }} TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }} QUICKNODE_ETH_MAINNET_API_KEY: ${{ secrets.QUICKNODE_ETH_MAINNET_API_KEY }} QUICKNODE_ETH_HOLESKY_API_KEY: ${{ secrets.QUICKNODE_ETH_HOLESKY_API_KEY }} @@ -40,13 +42,13 @@ jobs: result-encoding: string script: | const tag = process.env.GITHUB_REF_NAME; - const regex = /v.*\-(eth|hteth|matic|tmatic|bsc|tbsc|arbeth|tarbeth|opeth|topeth)$/; + const regex = /v.*\-(eth|hteth|matic|tmatic|bsc|tbsc|arbeth|tarbeth|opeth|topeth|tavaxc|avaxc)$/; const network = tag.match(regex); return network ? network[1] : "hteth"; deploy-to-test: runs-on: ubuntu-latest needs: [lint-and-test, get-network] - if: ${{ (needs.get-network.outputs.network == 'hteth' ) || (needs.get-network.outputs.network == 'tmatic' ) || (needs.get-network.outputs.network == 'tbsc' ) || (needs.get-network.outputs.network == 'tarbeth' ) || (needs.get-network.outputs.network == 'topeth' ) }} + if: ${{ (needs.get-network.outputs.network == 'hteth' ) || (needs.get-network.outputs.network == 'tmatic' ) || (needs.get-network.outputs.network == 'tbsc' ) || (needs.get-network.outputs.network == 'tarbeth' ) || (needs.get-network.outputs.network == 'topeth' ) || (needs.get-network.outputs.network == 'tavaxc' ) }} environment: testnet steps: - uses: actions/checkout@v2 @@ -59,6 +61,8 @@ jobs: - run: npm run deploy-test --network ${{ needs.get-network.outputs.network }} env: MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP: ${{ secrets.PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP }} TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }} QUICKNODE_ETH_MAINNET_API_KEY: ${{ secrets.QUICKNODE_ETH_MAINNET_API_KEY }} QUICKNODE_ETH_HOLESKY_API_KEY: ${{ secrets.QUICKNODE_ETH_HOLESKY_API_KEY }} @@ -99,7 +103,7 @@ jobs: deploy-to-prod: runs-on: ubuntu-latest needs: [lint-and-test, get-network] - if: ${{ (needs.get-network.outputs.network == 'eth' ) || (needs.get-network.outputs.network == 'matic' ) || (needs.get-network.outputs.network == 'bsc' ) || (needs.get-network.outputs.network == 'arbeth' ) || (needs.get-network.outputs.network == 'opeth' ) }} + if: ${{ (needs.get-network.outputs.network == 'eth' ) || (needs.get-network.outputs.network == 'matic' ) || (needs.get-network.outputs.network == 'bsc' ) || (needs.get-network.outputs.network == 'arbeth' ) || (needs.get-network.outputs.network == 'opeth' ) || (needs.get-network.outputs.network == 'avaxc' ) }} environment: mainnet steps: - uses: actions/checkout@v2 @@ -112,6 +116,8 @@ jobs: - run: npm run deploy-prod --network ${{ needs.get-network.outputs.network }} env: MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP: ${{ secrets.PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP }} TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }} QUICKNODE_ETH_MAINNET_API_KEY: ${{ secrets.QUICKNODE_ETH_MAINNET_API_KEY }} QUICKNODE_ETH_HOLESKY_API_KEY: ${{ secrets.QUICKNODE_ETH_HOLESKY_API_KEY }} diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index adacd31..a481ca1 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -25,6 +25,8 @@ jobs: - run: npm run test env: MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP: ${{ secrets.PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP }} TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }} QUICKNODE_ETH_MAINNET_API_KEY: ${{ secrets.QUICKNODE_ETH_MAINNET_API_KEY }} QUICKNODE_ETH_HOLESKY_API_KEY: ${{ secrets.QUICKNODE_ETH_HOLESKY_API_KEY }} diff --git a/contracts/coins/AvaxcWalletSimple.sol b/contracts/coins/AvaxcWalletSimple.sol new file mode 100644 index 0000000..d42e4b7 --- /dev/null +++ b/contracts/coins/AvaxcWalletSimple.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; +import '../Forwarder.sol'; +import '../ERC20Interface.sol'; +import '../WalletSimple.sol'; + +/** + * + * WalletSimple + * ============ + * + * Basic multi-signer wallet designed for use in a co-signing environment where 2 signatures are required to move funds. + * Typically used in a 2-of-3 signing configuration. Uses ecrecover to allow for 2 signatures in a single transaction. + * + * The first signature is created on the operation hash (see Data Formats) and passed to sendMultiSig/sendMultiSigToken + * The signer is determined by verifyMultiSig(). + * + * The second signature is created by the submitter of the transaction and determined by msg.signer. + * + * Data Formats + * ============ + * + * The signature is created with ethereumjs-util.ecsign(operationHash). + * Like the eth_sign RPC call, it packs the values as a 65-byte array of [r, s, v]. + * Unlike eth_sign, the message is not prefixed. + * + * The operationHash the result of keccak256(prefix, toAddress, value, data, expireTime). + * For ether transactions, `prefix` is "ETHER". + * For token transaction, `prefix` is "ERC20" and `data` is the tokenContractAddress. + * + * + */ +contract AvaxcWalletSimple is WalletSimple { + /** + * Get the network identifier that signers must sign over + * This provides protection signatures being replayed on other chains + */ + function getNetworkId() internal override pure returns (string memory) { + return 'AVAX'; + } + + /** + * Get the network identifier that signers must sign over for token transfers + * This provides protection signatures being replayed on other chains + */ + function getTokenNetworkId() internal override pure returns (string memory) { + return 'AVAX-ERC20'; + } + + /** + * Get the network identifier that signers must sign over for batch transfers + * This provides protection signatures being replayed on other chains + */ + function getBatchNetworkId() internal override pure returns (string memory) { + return 'AVAX-Batch'; + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index e45ed1a..3167bc0 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -10,6 +10,8 @@ import 'hardhat-gas-reporter'; import 'solidity-coverage'; const { + PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT, + PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP, MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT, TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT, QUICKNODE_ETH_MAINNET_API_KEY, @@ -51,8 +53,11 @@ const config: HardhatUserConfig = { accounts: [`${MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT}`] }, hteth: { - url: `https://boldest-cosmological-mountain.ethereum-holesky.quiknode.pro/${QUICKNODE_ETH_HOLESKY_API_KEY}`, - accounts: [`${TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT}`] + url: `https://rpc.holesky.ethpandaops.io/`, + accounts: [ + `${PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT}`, + `${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP}` + ] }, matic: { url: `https://polygon-mainnet.g.alchemyapi.io/v2/${ALCHEMY_POLYGON_API_KEY}`, @@ -78,6 +83,17 @@ const config: HardhatUserConfig = { topeth: { url: `${QUICKNODE_OPTIMISM_SEPOLIA_API_KEY}`, accounts: [`${TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT}`] + }, + tavaxc: { + url: 'https://api.avax-test.network/ext/C/rpc', + accounts: [ + `${PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT}`, + `${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP}` + ] + }, + avaxc: { + url: 'https://api.avax.network/ext/bc/C/rpc', + accounts: [`${MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT}`] } }, gasReporter: { @@ -100,7 +116,10 @@ const config: HardhatUserConfig = { arbitrumSepolia: `${ARBISCAN_API_KEY}`, // optimism optimisticEthereum: `${OPTIMISTIC_ETHERSCAN_API_KEY}`, - optimisticSepolia: `${OPTIMISTIC_ETHERSCAN_API_KEY}` + optimisticSepolia: `${OPTIMISTIC_ETHERSCAN_API_KEY}`, + // there is free api key for avaxc, so make use of 2 req/sec + avaxc: 'sampleapikey', + avaxcTestnet: 'sampleapikey' }, customChains: [ { @@ -126,6 +145,24 @@ const config: HardhatUserConfig = { apiURL: 'https://api-sepolia-optimistic.etherscan.io/api', browserURL: 'https://sepolia-optimism.etherscan.io' } + }, + { + network: 'avaxc', + chainId: 43114, + urls: { + apiURL: + 'https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan/api', + browserURL: 'https://snowtrace.io/' + } + }, + { + network: 'avaxcTestnet', + chainId: 43113, + urls: { + apiURL: + 'https://api.routescan.io/v2/network/testnet/evm/43113/etherscan/api', + browserURL: 'https://testnet.snowtrace.io/' + } } ] }, diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 4f37420..13c4369 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -10,12 +10,19 @@ async function main() { forwarderFactory: '' }; + const feeData = await ethers.provider.getFeeData(); + + const gasParams = { + gasPrice: feeData.gasPrice!.mul('2'), + gasLimit: 4500000 + }; + const [deployer] = await ethers.getSigners(); let walletImplementationContractName = ''; let walletFactoryContractName = 'WalletFactory'; const chainId = await deployer.getChainId(); - switch (await deployer.getChainId()) { + switch (chainId) { // https://chainlist.org/ //eth case 1: @@ -48,36 +55,47 @@ async function main() { case 11155420: walletImplementationContractName = 'OpethWalletSimple'; break; + //avaxc + case 43114: + //tavaxc + case 43113: + walletImplementationContractName = 'AvaxcWalletSimple'; } console.log( 'Deployed wallet contract called: ' + walletImplementationContractName ); - const WalletSimple = await ethers.getContractFactory( - walletImplementationContractName - ); - const walletSimple = await WalletSimple.deploy(); - await walletSimple.deployed(); - output.walletImplementation = walletSimple.address; - console.log('WalletSimple deployed at ' + walletSimple.address); + // const WalletSimple = await ethers.getContractFactory( + // walletImplementationContractName + // ); + // const walletSimple = await WalletSimple.deploy(gasParams); + // await walletSimple.deployed(); + // output.walletImplementation = walletSimple.address; + // console.log('WalletSimple deployed at ' + walletSimple.address); const WalletFactory = await ethers.getContractFactory( walletFactoryContractName ); - const walletFactory = await WalletFactory.deploy(walletSimple.address); + const walletFactory = await WalletFactory.deploy( + '0xe5DcdC13B628c2df813DB1080367E929c1507Ca0', + gasParams + ); await walletFactory.deployed(); output.walletFactory = walletFactory.address; console.log('WalletFactory deployed at ' + walletFactory.address); const Forwarder = await ethers.getContractFactory('Forwarder'); - const forwarder = await Forwarder.deploy(); + const forwarder = await Forwarder.deploy(gasParams); await forwarder.deployed(); output.forwarderImplementation = forwarder.address; console.log('Forwarder deployed at ' + forwarder.address); const ForwarderFactory = await ethers.getContractFactory('ForwarderFactory'); - const forwarderFactory = await ForwarderFactory.deploy(forwarder.address); + const forwarderFactory = await ForwarderFactory.deploy( + forwarder.address, + gasParams + ); await forwarderFactory.deployed(); output.forwarderFactory = forwarderFactory.address; console.log('ForwarderFactory deployed at ' + forwarderFactory.address); @@ -89,19 +107,19 @@ async function main() { await new Promise((r) => setTimeout(r, 1000 * 300)); // We have to wait for a minimum of 10 block confirmations before we can call the etherscan api to verify - await walletSimple.deployTransaction.wait(10); + //await walletSimple.deployTransaction.wait(10); await walletFactory.deployTransaction.wait(10); await forwarder.deployTransaction.wait(10); await forwarderFactory.deployTransaction.wait(10); console.log('Done waiting, verifying'); - await verifyContract( - walletImplementationContractName, - walletSimple.address, - [] - ); + // await verifyContract( + // walletImplementationContractName, + // walletSimple.address, + // [] + // ); await verifyContract('WalletFactory', walletFactory.address, [ - walletSimple.address + '0xe5DcdC13B628c2df813DB1080367E929c1507Ca0' ]); await verifyContract('Forwarder', forwarder.address, []); await verifyContract('ForwarderFactory', forwarderFactory.address, [ diff --git a/scripts/deployV1FactoryContracts.ts b/scripts/deployV1FactoryContracts.ts new file mode 100644 index 0000000..1cf2efd --- /dev/null +++ b/scripts/deployV1FactoryContracts.ts @@ -0,0 +1,187 @@ +import { ethers } from 'hardhat'; +import { BigNumber } from 'ethers'; +const hre = require('hardhat'); +const fs = require('fs'); + +async function main() { + const output = { + walletImplementation: '', + walletFactory: '', + forwarderImplementation: '', + forwarderFactory: '' + }; + + const feeData = await ethers.provider.getFeeData(); + + const [walletDeployer, forwarderDeployer] = await ethers.getSigners(); + + const gasParams = { + gasPrice: feeData.gasPrice!.mul('2'), + gasLimit: 5000000 + }; + const walletTxCount = await walletDeployer.getTransactionCount(); + + console.log('Deploying wallet contracts....'); + console.log('Wallet Tx Count: ', walletTxCount); + const walletSelfTransactions = 2 - walletTxCount; + for (let i = 0; i < walletSelfTransactions; i++) { + const tx = await walletDeployer.sendTransaction({ + to: walletDeployer.address, + value: ethers.utils.parseEther('0'), + gasPrice: gasParams.gasPrice + }); + await tx.wait(); + console.log(`Self transaction with nonce: ${i} complete`); + } + + const walletImplementationContractName = 'AvaxcWalletSimple'; + const walletFactoryContractName = 'WalletFactory'; + + const WalletImplementation = await ethers.getContractFactory( + walletImplementationContractName, + walletDeployer + ); + const walletImplementation = await WalletImplementation.deploy(gasParams); + await walletImplementation.deployed(); + output.walletImplementation = walletImplementation.address; + console.log( + `${walletImplementationContractName} deployed at ` + + walletImplementation.address + ); + + const WalletFactory = await ethers.getContractFactory( + walletFactoryContractName, + walletDeployer + ); + const walletFactory = await WalletFactory.deploy( + walletImplementation.address, + gasParams + ); + await walletFactory.deployed(); + output.walletFactory = walletFactory.address; + console.log( + `${walletFactoryContractName} deployed at ` + walletFactory.address + ); + + const forwarderTxCount = await forwarderDeployer.getTransactionCount(); + + console.log('Deploying forwarder contracts....'); + console.log('Forwarder Tx Count: ', forwarderTxCount); + const forwarderSelfTransactions = 234 - forwarderTxCount; + + for (let i = 0; i < forwarderSelfTransactions; i++) { + const tx = await forwarderDeployer.sendTransaction({ + to: forwarderDeployer.address, + value: ethers.utils.parseEther('0'), + gasPrice: gasParams.gasPrice + }); + await tx.wait(); + console.log(`Self transaction with nonce: ${i} complete`); + } + + const forwarderImplementationContractName = 'Forwarder'; + const forwarderFactoryContractName = 'ForwarderFactory'; + + const ForwarderImplementation = await ethers.getContractFactory( + forwarderImplementationContractName, + forwarderDeployer + ); + + const forwarderImplementation = await ForwarderImplementation.deploy( + gasParams + ); + await forwarderImplementation.deployed(); + output.forwarderImplementation = forwarderImplementation.address; + + console.log( + `${forwarderImplementationContractName} deployed at ` + + forwarderImplementation.address + ); + + const ForwarderFactory = await ethers.getContractFactory( + forwarderFactoryContractName, + forwarderDeployer + ); + + const forwarderFactory = await ForwarderFactory.deploy( + forwarderImplementation.address, + gasParams + ); + + await forwarderFactory.deployed(); + output.forwarderFactory = forwarderFactory.address; + console.log( + `${forwarderFactoryContractName} deployed at ` + forwarderFactory.address + ); + + fs.writeFileSync('output.json', JSON.stringify(output)); + + // Wait 5 minutes. It takes some time for the etherscan backend to index the transaction and store the contract. + console.log('Waiting for 5 minutes before verifying....'); + await new Promise((r) => setTimeout(r, 1000 * 300)); + + // We have to wait for a minimum of 10 block confirmations before we can call the etherscan api to verify + + await walletImplementation.deployTransaction.wait(10); + await walletFactory.deployTransaction.wait(10); + await forwarderImplementation.deployTransaction.wait(10); + await forwarderFactory.deployTransaction.wait(10); + + console.log('Done waiting, verifying'); + await verifyContract( + walletImplementationContractName, + walletImplementation.address, + [] + ); + await verifyContract('WalletFactory', walletFactory.address, [ + walletImplementation.address + ]); + + await verifyContract( + forwarderImplementationContractName, + forwarderImplementation.address, + [] + ); + + await verifyContract('ForwarderFactory', forwarderFactory.address, [ + forwarderImplementation.address + ]); + + console.log('Contracts verified'); +} + +async function verifyContract( + contractName: string, + contractAddress: string, + constructorArguments: string[], + contract?: string +) { + try { + const verifyContractArgs: { + address: string; + constructorArguments: string[]; + contract?: string; + } = { + address: contractAddress, + constructorArguments: constructorArguments + }; + + if (contract) { + verifyContractArgs.contract = contract; + } + + await hre.run('verify:verify', verifyContractArgs); + } catch (e) { + // @ts-ignore + // We get a failure API response if the source code has already been uploaded, don't throw in this case. + if (!e.message.includes('Reason: Already Verified')) { + throw e; + } + } + console.log(`Verified ${contractName} on Etherscan!`); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +});