const contracts = require("./contracts.json"); const ethers = require("ethers"); const optimismSDK = require("@eth-optimism/sdk"); require("dotenv").config(); const l1Url = `https://eth-goerli.g.alchemy.com/v2/${process.env.GOERLI_ALCHEMY_KEY}`; const l2Url = `https://opt-goerli.g.alchemy.com/v2/${process.env.GOERLI_OPT_ALCHEMY_KEY}`; const mnemonic = process.env.GOERLI_MNEMONIC; const words = mnemonic.match(/[a-zA-Z]+/g).length; validLength = [12, 15, 18, 24]; if (!validLength.includes(words)) { console.log(`The mnemonic (${mnemonic}) is the wrong number of words`); process.exit(-1); } const erc20Addrs = { l1Addr: contracts.goerli.l1.token, l2Addr: contracts.goerli.l2.token, }; const faucetAddr = contracts.goerli.l2.controller; let crossChainMessenger; let l1ERC20, l2ERC20; // OUTb contracts to show ERC-20 let faucet; let ourAddr; // The address of the signer we use. // Get signers on L1 and L2 (for the same address). Note that // this address needs to have ETH on it, both on Optimism and // Optimism Georli const getSigners = async () => { const l1RpcProvider = new ethers.providers.JsonRpcProvider(l1Url); const l2RpcProvider = new ethers.providers.JsonRpcProvider(l2Url); const hdNode = ethers.utils.HDNode.fromMnemonic(mnemonic); const privateKey = hdNode.derivePath(ethers.utils.defaultPath).privateKey; const l1Wallet = new ethers.Wallet(privateKey, l1RpcProvider); const l2Wallet = new ethers.Wallet(privateKey, l2RpcProvider); return [l1Wallet, l2Wallet]; }; // getSigners // The ABI fragment for the contract. We only need to know how to do two things: // 1. Get an account's balance // 2. Call the faucet to get more (only works on L1). Of course, production // ERC-20 tokens tend to be a bit harder to acquire. const erc20ABI = [ // balanceOf { constant: true, inputs: [{ name: "_owner", type: "address" }], name: "balanceOf", outputs: [{ name: "balance", type: "uint256" }], type: "function", }, ]; // erc20ABI const faucetABI = [ // faucet { inputs: [ { internalType: "uint256", name: "_amount", type: "uint256", }, ], name: "mint", outputs: [], stateMutability: "nonpayable", type: "function", }, ]; const setup = async () => { const [l1Signer, l2Signer] = await getSigners(); ourAddr = l1Signer.address; crossChainMessenger = new optimismSDK.CrossChainMessenger({ l1ChainId: 5, // Goerli value, 1 for mainnet l2ChainId: 420, // Goerli value, 10 for mainnet l1SignerOrProvider: l1Signer, l2SignerOrProvider: l2Signer, }); l1ERC20 = new ethers.Contract(erc20Addrs.l1Addr, erc20ABI, l1Signer); l2ERC20 = new ethers.Contract(erc20Addrs.l2Addr, erc20ABI, l2Signer); faucet = new ethers.Contract(faucetAddr, faucetABI, l1Signer); }; const reportERC20Balances = async () => { const l1Balance = (await l1ERC20.balanceOf(ourAddr)).toString().slice(0, -18); const l2Balance = (await l2ERC20.balanceOf(ourAddr)).toString().slice(0, -18); console.log(`OUTb on L1:${l1Balance} OUTb on L2:${l2Balance}`); if (l1Balance != 0) { return; } console.log(`You don't have enough OUTb on L1. Let's call the faucet to fix that`); const tx = await faucet.mint("10000000000000000000000"); console.log(`Faucet tx: ${tx.hash}`); console.log(`\tMore info: https://goerli.etherscan.io/tx/${tx.hash}`); await tx.wait(); const newBalance = (await l1ERC20.balanceOf(ourAddr)).toString().slice(0, -18); console.log(`New L1 OUTb balance: ${newBalance}`); }; const oneToken = BigInt(1e18); const depositERC20 = async () => { console.log("Deposit ERC20"); await reportERC20Balances(); const start = new Date(); // Need the l2 address to know which bridge is responsible const allowanceResponse = await crossChainMessenger.approveERC20(erc20Addrs.l1Addr, erc20Addrs.l2Addr, oneToken); await allowanceResponse.wait(); console.log(`Allowance given by tx ${allowanceResponse.hash}`); console.log(`\tMore info: https://goerli.etherscan.io/tx/${allowanceResponse.hash}`); console.log(`Time so far ${(new Date() - start) / 1000} seconds`); const response = await crossChainMessenger.depositERC20(erc20Addrs.l1Addr, erc20Addrs.l2Addr, oneToken); console.log(`Deposit transaction hash (on L1): ${response.hash}`); console.log(`\tMore info: https://goerli.etherscan.io/tx/${response.hash}`); await response.wait(); console.log("Waiting for status to change to RELAYED"); console.log(`Time so far ${(new Date() - start) / 1000} seconds`); await crossChainMessenger.waitForMessageStatus(response.hash, optimismSDK.MessageStatus.RELAYED); await reportERC20Balances(); console.log(`depositERC20 took ${(new Date() - start) / 1000} seconds\n\n`); }; // depositERC20() const withdrawERC20 = async () => { console.log("Withdraw ERC20"); const start = new Date(); await reportERC20Balances(); const response = await crossChainMessenger.withdrawERC20(erc20Addrs.l1Addr, erc20Addrs.l2Addr, oneToken); console.log(`Transaction hash (on L2): ${response.hash}`); console.log(`\tFor more information: https://goerli-optimism.etherscan.io/tx/${response.hash}`); await response.wait(); console.log("Waiting for status to be READY_TO_PROVE"); console.log(`Time so far ${(new Date() - start) / 1000} seconds`); await crossChainMessenger.waitForMessageStatus(response.hash, optimismSDK.MessageStatus.READY_TO_PROVE); console.log(`Time so far ${(new Date() - start) / 1000} seconds`); await crossChainMessenger.proveMessage(response.hash); console.log("In the challenge period, waiting for status READY_FOR_RELAY"); console.log(`Time so far ${(new Date() - start) / 1000} seconds`); await crossChainMessenger.waitForMessageStatus(response.hash, optimismSDK.MessageStatus.READY_FOR_RELAY); console.log("Ready for relay, finalizing message now"); console.log(`Time so far ${(new Date() - start) / 1000} seconds`); await crossChainMessenger.finalizeMessage(response.hash); console.log("Waiting for status to change to RELAYED"); console.log(`Time so far ${(new Date() - start) / 1000} seconds`); await crossChainMessenger.waitForMessageStatus(response, optimismSDK.MessageStatus.RELAYED); await reportERC20Balances(); console.log(`withdrawERC20 took ${(new Date() - start) / 1000} seconds\n\n\n`); }; // withdrawERC20() const main = async () => { await setup(); await depositERC20(); await withdrawERC20(); }; // main main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });