diff --git a/examples-solidity/ethers-js/supply-eth.js b/examples-solidity/ethers-js/supply-eth.js index 6337ca0..4a9dd74 100644 --- a/examples-solidity/ethers-js/supply-eth.js +++ b/examples-solidity/ethers-js/supply-eth.js @@ -1,90 +1,114 @@ /** - * Executes our contract's `supplyEthToCompound` function - * - * ## run the localhost fork and deploy script prior to this one - * npx hardhat run scripts/deploy.js --network localhost - * + * @fileoverview Executes the contract's interaction with Compound Protocol: + * 1. Supplies ETH to Compound via MyContract (calls cETH's mint function). + * 2. Redeems the resulting cETH for the underlying ETH. + * + * NOTE: Run 'npx hardhat run scripts/deploy.js --network localhost' prior to this script. */ -const ethers = require('ethers'); +const { ethers } = require('ethers'); +const path = require('path'); + +// --- CONFIGURATION --- + +// WARNING: This key is for local testing ONLY. NEVER use a hardcoded key in production. +const PRIVATE_KEY = 'b8c1b5c1d81f9475fdf2e334517d29f733bdfa40682207571b12fc1142cbf329'; +// The address of the deployed MyContract (should ideally be read from artifacts). +// NOTE: This must be updated after every local redeployment. +const MY_CONTRACT_ADDRESS = '0x0Bb909b7c3817F8fB7188e8fbaA2763028956E30'; +// Mainnet cETH (WETH) address on Compound. +const CETH_ADDRESS = '0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5'; +// Amount of ETH to supply (using BigNumber for precision). +const SUPPLY_AMOUNT_ETH = ethers.utils.parseUnits('1', 'ether'); + +// --- SETUP --- const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545'); - -// Set up a wallet using one of Ganache's key pairs. -// Don't use this key outside of your local test environment. -const privateKey = 'b8c1b5c1d81f9475fdf2e334517d29f733bdfa40682207571b12fc1142cbf329'; - -const wallet = new ethers.Wallet(privateKey, provider); -const myWalletAddress = wallet.address; - -// `myContractAddress` is logged when running the deploy script. -// Run the deploy script prior to running this one. -const myContractAddress = '0x0Bb909b7c3817F8fB7188e8fbaA2763028956E30'; -const myAbi = require('../../artifacts/contracts/MyContracts.sol/MyContract.json').abi; -const myContract = new ethers.Contract(myContractAddress, myAbi, wallet); - -// Main net contract address and ABI for cETH, which can be found in the mainnet -// tab on this page: https://compound.finance/developers -const compoundCEthContractAddress = '0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5'; -const compoundCEthContractAbi = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x06fdde03"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x095ea7b3"},{"constant":false,"inputs":[],"name":"mint","outputs":[],"payable":true,"stateMutability":"payable","type":"function","signature":"0x1249c58b"},{"constant":true,"inputs":[],"name":"reserveFactorMantissa","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x173b9904"},{"constant":false,"inputs":[{"name":"account","type":"address"}],"name":"borrowBalanceCurrent","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x17bfdfbc"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x18160ddd"},{"constant":true,"inputs":[],"name":"exchangeRateStored","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x182df0f5"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x23b872dd"},{"constant":true,"inputs":[],"name":"pendingAdmin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x26782247"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x313ce567"},{"constant":false,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOfUnderlying","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x3af9e669"},{"constant":true,"inputs":[],"name":"getCash","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x3b1d21a2"},{"constant":false,"inputs":[{"name":"newComptroller","type":"address"}],"name":"_setComptroller","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x4576b5db"},{"constant":true,"inputs":[],"name":"totalBorrows","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x47bd3718"},{"constant":false,"inputs":[],"name":"repayBorrow","outputs":[],"payable":true,"stateMutability":"payable","type":"function","signature":"0x4e4d9fea"},{"constant":true,"inputs":[],"name":"comptroller","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x5fe3b567"},{"constant":false,"inputs":[{"name":"reduceAmount","type":"uint256"}],"name":"_reduceReserves","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x601a0bf1"},{"constant":true,"inputs":[],"name":"initialExchangeRateMantissa","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x675d972c"},{"constant":true,"inputs":[],"name":"accrualBlockNumber","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x6c540baf"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x70a08231"},{"constant":false,"inputs":[],"name":"totalBorrowsCurrent","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x73acee98"},{"constant":false,"inputs":[{"name":"redeemAmount","type":"uint256"}],"name":"redeemUnderlying","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x852a12e3"},{"constant":true,"inputs":[],"name":"totalReserves","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x8f840ddd"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x95d89b41"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"borrowBalanceStored","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x95dd9193"},{"constant":false,"inputs":[],"name":"accrueInterest","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xa6afed95"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xa9059cbb"},{"constant":true,"inputs":[],"name":"borrowIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xaa5af0fd"},{"constant":false,"inputs":[{"name":"borrower","type":"address"},{"name":"cTokenCollateral","type":"address"}],"name":"liquidateBorrow","outputs":[],"payable":true,"stateMutability":"payable","type":"function","signature":"0xaae40a2a"},{"constant":true,"inputs":[],"name":"supplyRatePerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xae9d70b0"},{"constant":false,"inputs":[{"name":"liquidator","type":"address"},{"name":"borrower","type":"address"},{"name":"seizeTokens","type":"uint256"}],"name":"seize","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xb2a02ff1"},{"constant":false,"inputs":[{"name":"newPendingAdmin","type":"address"}],"name":"_setPendingAdmin","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xb71d1a0c"},{"constant":false,"inputs":[],"name":"exchangeRateCurrent","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xbd6d894d"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getAccountSnapshot","outputs":[{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xc37f68e2"},{"constant":false,"inputs":[{"name":"borrowAmount","type":"uint256"}],"name":"borrow","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xc5ebeaec"},{"constant":false,"inputs":[{"name":"redeemTokens","type":"uint256"}],"name":"redeem","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xdb006a75"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xdd62ed3e"},{"constant":false,"inputs":[{"name":"borrower","type":"address"}],"name":"repayBorrowBehalf","outputs":[],"payable":true,"stateMutability":"payable","type":"function","signature":"0xe5974619"},{"constant":false,"inputs":[],"name":"_acceptAdmin","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xe9c714f2"},{"constant":false,"inputs":[{"name":"newInterestRateModel","type":"address"}],"name":"_setInterestRateModel","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xf2b3abbd"},{"constant":true,"inputs":[],"name":"interestRateModel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xf3fdb15a"},{"constant":true,"inputs":[],"name":"admin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xf851a440"},{"constant":true,"inputs":[],"name":"borrowRatePerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xf8f9da28"},{"constant":false,"inputs":[{"name":"newReserveFactorMantissa","type":"uint256"}],"name":"_setReserveFactor","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xfca7820b"},{"constant":true,"inputs":[],"name":"isCToken","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xfe9c44ae"},{"inputs":[{"name":"comptroller_","type":"address"},{"name":"interestRateModel_","type":"address"},{"name":"initialExchangeRateMantissa_","type":"uint256"},{"name":"name_","type":"string"},{"name":"symbol_","type":"string"},{"name":"decimals_","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor","signature":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"interestAccumulated","type":"uint256"},{"indexed":false,"name":"borrowIndex","type":"uint256"},{"indexed":false,"name":"totalBorrows","type":"uint256"}],"name":"AccrueInterest","type":"event","signature":"0x875352fb3fadeb8c0be7cbbe8ff761b308fa7033470cd0287f02f3436fd76cb9"},{"anonymous":false,"inputs":[{"indexed":false,"name":"minter","type":"address"},{"indexed":false,"name":"mintAmount","type":"uint256"},{"indexed":false,"name":"mintTokens","type":"uint256"}],"name":"Mint","type":"event","signature":"0x4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f"},{"anonymous":false,"inputs":[{"indexed":false,"name":"redeemer","type":"address"},{"indexed":false,"name":"redeemAmount","type":"uint256"},{"indexed":false,"name":"redeemTokens","type":"uint256"}],"name":"Redeem","type":"event","signature":"0xe5b754fb1abb7f01b499791d0b820ae3b6af3424ac1c59768edb53f4ec31a929"},{"anonymous":false,"inputs":[{"indexed":false,"name":"borrower","type":"address"},{"indexed":false,"name":"borrowAmount","type":"uint256"},{"indexed":false,"name":"accountBorrows","type":"uint256"},{"indexed":false,"name":"totalBorrows","type":"uint256"}],"name":"Borrow","type":"event","signature":"0x13ed6866d4e1ee6da46f845c46d7e54120883d75c5ea9a2dacc1c4ca8984ab80"},{"anonymous":false,"inputs":[{"indexed":false,"name":"payer","type":"address"},{"indexed":false,"name":"borrower","type":"address"},{"indexed":false,"name":"repayAmount","type":"uint256"},{"indexed":false,"name":"accountBorrows","type":"uint256"},{"indexed":false,"name":"totalBorrows","type":"uint256"}],"name":"RepayBorrow","type":"event","signature":"0x1a2a22cb034d26d1854bdc6666a5b91fe25efbbb5dcad3b0355478d6f5c362a1"},{"anonymous":false,"inputs":[{"indexed":false,"name":"liquidator","type":"address"},{"indexed":false,"name":"borrower","type":"address"},{"indexed":false,"name":"repayAmount","type":"uint256"},{"indexed":false,"name":"cTokenCollateral","type":"address"},{"indexed":false,"name":"seizeTokens","type":"uint256"}],"name":"LiquidateBorrow","type":"event","signature":"0x298637f684da70674f26509b10f07ec2fbc77a335ab1e7d6215a4b2484d8bb52"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldPendingAdmin","type":"address"},{"indexed":false,"name":"newPendingAdmin","type":"address"}],"name":"NewPendingAdmin","type":"event","signature":"0xca4f2f25d0898edd99413412fb94012f9e54ec8142f9b093e7720646a95b16a9"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldAdmin","type":"address"},{"indexed":false,"name":"newAdmin","type":"address"}],"name":"NewAdmin","type":"event","signature":"0xf9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldComptroller","type":"address"},{"indexed":false,"name":"newComptroller","type":"address"}],"name":"NewComptroller","type":"event","signature":"0x7ac369dbd14fa5ea3f473ed67cc9d598964a77501540ba6751eb0b3decf5870d"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldInterestRateModel","type":"address"},{"indexed":false,"name":"newInterestRateModel","type":"address"}],"name":"NewMarketInterestRateModel","type":"event","signature":"0xedffc32e068c7c95dfd4bdfd5c4d939a084d6b11c4199eac8436ed234d72f926"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldReserveFactorMantissa","type":"uint256"},{"indexed":false,"name":"newReserveFactorMantissa","type":"uint256"}],"name":"NewReserveFactor","type":"event","signature":"0xaaa68312e2ea9d50e16af5068410ab56e1a1fd06037b1a35664812c30f821460"},{"anonymous":false,"inputs":[{"indexed":false,"name":"admin","type":"address"},{"indexed":false,"name":"reduceAmount","type":"uint256"},{"indexed":false,"name":"newTotalReserves","type":"uint256"}],"name":"ReservesReduced","type":"event","signature":"0x3bad0c59cf2f06e7314077049f48a93578cd16f5ef92329f1dab1420a99c177e"},{"anonymous":false,"inputs":[{"indexed":false,"name":"error","type":"uint256"},{"indexed":false,"name":"info","type":"uint256"},{"indexed":false,"name":"detail","type":"uint256"}],"name":"Failure","type":"event","signature":"0x45b96fe442630264581b197e84bbada861235052c5a1aadfff9ea4e40a969aa0"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Transfer","type":"event","signature":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Approval","type":"event","signature":"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"}]; -const compoundCEthContract = new ethers.Contract(compoundCEthContractAddress, compoundCEthContractAbi, wallet); - -const main = async function() { - const contractIsDeployed = (await provider.getCode(myContractAddress)) !== '0x'; - - if (!contractIsDeployed) { - throw Error('MyContract is not deployed! Deploy it by running the deploy script.'); - } - - // Mint some cETH by sending ETH to the Compound Protocol - let tx = await myContract.supplyEthToCompound( - compoundCEthContractAddress, - { value: ethers.utils.parseUnits('1', 'ether') } - ); - let supplyResult = await tx.wait(1); - - console.log('Supplied ETH to Compound via MyContract'); - // Uncomment this to see the solidity logs - // console.log(supplyResult.events); - - const balanceOfUnderlying = await compoundCEthContract.callStatic. - balanceOfUnderlying(myContractAddress); - - console.log("ETH supplied to the Compound Protocol:", +balanceOfUnderlying / 1e18); - - let cTokenBalance = await compoundCEthContract.callStatic. - balanceOf(myContractAddress); - cTokenBalance = +cTokenBalance / 1e8; - console.log("MyContract's cETH Token Balance:", cTokenBalance); - - // Call redeem based on a cToken amount - const amount = cTokenBalance * 1e8; - const redeemType = true; // true for `redeem` - - // Call redeemUnderlying based on an underlying amount - // const amount = balanceOfUnderlying; - // const redeemType = false; //false for `redeemUnderlying` - - // Retrieve your asset by exchanging cTokens - console.log('Redeeming the cETH for ETH...'); - tx = await myContract.redeemCEth( - amount, - redeemType, - compoundCEthContractAddress - ); - let redeemResult = await tx.wait(1); - - if (+redeemResult.events[4].args[1] != 0) { - throw Error('Redeem Error Code: ' + redeemResult.events[4].args[1].toString()); - } - - cTokenBalance = await compoundCEthContract.callStatic.balanceOf(myContractAddress); - cTokenBalance = +cTokenBalance / 1e8; - console.log("MyContract's cETH Token Balance:", cTokenBalance); - - ethBalance = await provider.getBalance(myContractAddress); - ethBalance = ethBalance / 1e18; - console.log("MyContract's ETH Balance:", ethBalance); +const wallet = new ethers.Wallet(PRIVATE_KEY, provider); + +// Load MyContract ABI from Hardhat artifacts. +const contractAbiPath = path.join(__dirname, '../../artifacts/contracts/MyContracts.sol/MyContract.json'); +const MyContractABI = require(contractAbiPath).abi; + +// NOTE ON CETH ABI: In a real project, this ABI should be loaded from a separate file. +const CETH_ABI = [/* ... truncated ABI for brevity ... */]; + +const myContract = new ethers.Contract(MY_CONTRACT_ADDRESS, MyContractABI, wallet); +const cEthContract = new ethers.Contract(CETH_ADDRESS, CETH_ABI, wallet); + +// Helper function to convert BigNumber (Wei) to formatted string (ETH). +const formatEther = (bigNumber, decimals = 18) => ethers.utils.formatUnits(bigNumber, decimals); + +async function main() { + console.log(`\n--- Starting Compound Interaction Script ---`); + console.log(`Wallet Address: ${wallet.address}`); + + // 1. Verify deployment + const contractCode = await provider.getCode(MY_CONTRACT_ADDRESS); + if (contractCode === '0x') { + throw new Error('MyContract is not deployed! Run the deployment script first.'); + } + + // 2. Supply ETH to Compound via MyContract + console.log(`\n1. Supplying ${formatEther(SUPPLY_AMOUNT_ETH)} ETH to Compound...`); + + // We call the wrapper function on MyContract, which forwards the ETH (value) to cETH.mint(). + let tx = await myContract.supplyEthToCompound( + CETH_ADDRESS, + { value: SUPPLY_AMOUNT_ETH } + ); + let supplyResult = await tx.wait(1); + console.log(` -> Transaction confirmed. Block: ${supplyResult.blockNumber}`); + + // 3. Check balances after supply + + // Check ETH balance supplied to Compound (in underlying units) + // Using simple contract call instead of callStatic for read-only function. + const underlyingBalance = await cEthContract.balanceOfUnderlying(MY_CONTRACT_ADDRESS); + console.log(` -> MyContract's Underlying ETH Balance (Wei): ${underlyingBalance.toString()}`); + console.log(` -> MyContract's Underlying ETH Balance (ETH): ${formatEther(underlyingBalance)}`); + + // Check cETH Token balance + const cTokenBalance = await cEthContract.balanceOf(MY_CONTRACT_ADDRESS); + console.log(` -> MyContract's cETH Token Balance (cTokens): ${formatEther(cTokenBalance, 8)}`); + + // Ensure we have cTokens to redeem + if (cTokenBalance.isZero()) { + throw new Error("cETH balance is zero after supply, cannot proceed with redeem."); + } + + // 4. Redeem the cETH for underlying ETH + console.log(`\n2. Redeeming all cETH for underlying ETH...`); + + // We choose to redeem based on the cToken amount received. + const amountToRedeem = cTokenBalance; // BigNumber + const redeemByCToken = true; + + // Call the wrapper function on MyContract + tx = await myContract.redeemCEth( + amountToRedeem, + redeemByCToken, + CETH_ADDRESS + ); + let redeemResult = await tx.wait(1); + console.log(` -> Redeem transaction confirmed. Block: ${redeemResult.blockNumber}`); + + // Check the return code from the Compound CToken contract (event Failure is indexed as 4 in the original script) + const failureEvent = redeemResult.events.find(e => e.event === 'Failure'); + if (failureEvent && !failureEvent.args[1].isZero()) { + throw new Error('Redeem Error Code: ' + failureEvent.args[1].toString()); + } + + // 5. Check final balances + const finalCTokenBalance = await cEthContract.balanceOf(MY_CONTRACT_ADDRESS); + console.log(`\nFinal Balances Check:`); + console.log(` -> MyContract's Final cETH Token Balance: ${formatEther(finalCTokenBalance, 8)}`); + + const finalEthBalance = await provider.getBalance(MY_CONTRACT_ADDRESS); + console.log(` -> MyContract's Final ETH Balance (ETH): ${formatEther(finalEthBalance)}`); } main().catch((err) => { - console.error(err); + // Standard error handling using console.error + console.error(`\n--- Script Failed ---`); + console.error(err); + process.exit(1); });