├── .github ├── CODEOWNERS └── ISSUE_TEMPLATE │ └── issues.md ├── .gitignore ├── .gitmodules ├── README.md ├── cross-dom-bridge-erc20 ├── .env.example ├── README.md ├── index.js ├── package.json └── yarn.lock ├── cross-dom-bridge-eth ├── .env.example ├── README.md ├── index.js ├── package.json └── yarn.lock ├── cross-dom-comm ├── .gitignore ├── README.md ├── foundry │ ├── foundry.toml │ ├── lib │ │ ├── package.json │ │ └── yarn.lock │ ├── remappings.txt │ └── src └── hardhat │ ├── .env.example │ ├── contracts │ ├── FromL1_ControlL2Greeter.sol │ ├── FromL2_ControlL1Greeter.sol │ └── Greeter.sol │ ├── hardhat.config.js │ ├── package.json │ ├── test │ └── sample-test.js │ └── yarn.lock ├── ecosystem ├── alchemy │ ├── README.md │ └── assets │ │ ├── copykey.png │ │ ├── create-app-l2-testnet.png │ │ ├── lets-build.png │ │ ├── payment-details.png │ │ ├── select-op.png │ │ ├── signup.png │ │ ├── team-form.png │ │ └── viewkey.png ├── attestation-station │ ├── README.md │ └── contract-access │ │ ├── .env.example │ │ ├── AttestationStation.json │ │ ├── README.md │ │ ├── attest.mjs │ │ ├── contracts │ │ ├── AttestationProxy.sol │ │ └── AttestationStation.sol │ │ ├── hardhat.config.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── read-events.mjs ├── opengsn │ ├── .gitignore │ ├── README.md │ ├── contracts │ │ ├── Greeter.sol │ │ └── SingleRecipientPaymaster.sol │ ├── hardhat.config.js │ ├── package.json │ ├── scripts │ │ └── use-gsn.js │ └── yarn.lock └── tenderly │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── assets │ ├── debugger.png │ ├── func-trace.png │ ├── stack-trace.png │ └── state-changes.png │ ├── contracts │ └── Fail.sol │ ├── hardhat.config.js │ ├── package.json │ ├── scripts │ └── sample-script.js │ ├── test │ └── sample-test.js │ └── yarn.lock ├── first-contract ├── README.md └── assets │ ├── remix-compiler-icon.png │ ├── remix-console.png │ ├── remix-files-icon.png │ ├── remix-query-2.png │ ├── remix-query.png │ ├── remix-run-icon.png │ └── remix-tx.png ├── getting-started ├── README.md ├── apeworx │ ├── .gitignore │ ├── .hypothesis │ │ └── unicode_data │ │ │ └── 12.1.0 │ │ │ └── charmap.json.gz │ ├── ape-config.yaml │ └── contracts │ │ └── Greeter.sol ├── assets │ ├── remix-connect.png │ ├── remix-deploy.png │ ├── remix-env.png │ ├── remix-files-icon.png │ ├── remix-query.png │ ├── remix-run-icon.png │ ├── remix-tx.png │ └── remix-upload-icon.png ├── brownie │ ├── .env.example │ ├── .gitattributes │ ├── .gitignore │ └── contracts │ │ └── Greeter.sol ├── foundry │ ├── .gitignore │ ├── foundry.toml │ ├── lib │ │ ├── forge-std │ │ │ ├── .github │ │ │ │ └── workflows │ │ │ │ │ └── tests.yml │ │ │ ├── .gitignore │ │ │ ├── .gitmodules │ │ │ ├── LICENSE-APACHE │ │ │ ├── LICENSE-MIT │ │ │ ├── README.md │ │ │ ├── lib │ │ │ │ └── ds-test │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── Makefile │ │ │ │ │ ├── default.nix │ │ │ │ │ ├── demo │ │ │ │ │ └── demo.sol │ │ │ │ │ └── src │ │ │ │ │ └── test.sol │ │ │ └── src │ │ │ │ ├── Script.sol │ │ │ │ ├── Test.sol │ │ │ │ ├── Vm.sol │ │ │ │ ├── console.sol │ │ │ │ ├── console2.sol │ │ │ │ └── test │ │ │ │ ├── StdAssertions.t.sol │ │ │ │ ├── StdCheats.t.sol │ │ │ │ ├── StdError.t.sol │ │ │ │ ├── StdMath.t.sol │ │ │ │ └── StdStorage.t.sol │ │ ├── package.json │ │ └── yarn.lock │ ├── remappings.txt │ ├── script │ │ └── Contract.s.sol │ ├── src │ │ └── Greeter.sol │ └── test │ │ └── Greeter.t.sol ├── hardhat │ ├── .env.example │ ├── .gitignore │ ├── contracts │ │ └── Greeter.sol │ ├── hardhat.config.js │ ├── package.json │ ├── scripts │ │ └── deploy-script.js │ ├── test │ │ └── sample-test.js │ └── yarn.lock ├── truffle │ ├── .env.example │ ├── .gitignore │ ├── contracts │ │ ├── Greeter.sol │ │ └── Migrations.sol │ ├── migrations │ │ ├── 1_initial_migration.js │ │ └── 2_deploy_contracts.js │ ├── package.json │ ├── test │ │ ├── .gitkeep │ │ └── greeter-test.js │ ├── truffle-config.js │ └── yarn.lock └── waffle │ ├── .gitignore │ ├── contracts │ └── MockContract.sol │ ├── package.json │ ├── test │ └── mock-contract.test.ts │ ├── tsconfig.json │ ├── waffle.config.json │ └── yarn.lock ├── op-stack └── forced-withdrawal │ ├── .env.example │ ├── .gitignore │ ├── contracts │ └── Greeter.sol │ ├── hardhat.config.js │ ├── package-lock.json │ ├── package.json │ ├── scripts │ └── sample-script.js │ └── test │ └── sample-test.js ├── sdk-estimate-gas ├── .env.example ├── .gitignore ├── Greeter.json ├── README.md ├── gas.js ├── package.json └── yarn.lock ├── sdk-trace-tx ├── .env.example ├── README.md ├── hardhat.config.js ├── package.json └── yarn.lock ├── sdk-view-tx ├── .env.example ├── README.md ├── package.json ├── view-tx.js └── yarn.lock ├── standard-bridge-custom-token ├── .env.example ├── README.md ├── contracts │ ├── L2CustomERC20.sol │ └── OptimismUselessToken.sol ├── hardhat.config.js ├── package.json └── yarn.lock └── standard-bridge-standard-token ├── .env.example ├── README.md ├── contracts └── OptimismUselessToken.sol ├── hardhat.config.js ├── package.json └── yarn.lock /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Nicca42 @smartcontracts @OPMattie 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issues.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Tutorial Issue 3 | about: Report incorrect or missing information from https://github.com/ethereum-optimism/optimism-tutorial 4 | 5 | --- 6 | 7 | 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project specific 2 | deployments 3 | optimism 4 | 5 | # environment variables 6 | .env 7 | .env.local 8 | *.delme 9 | 10 | # Packages 11 | node_modules/ 12 | 13 | # Hardhat build outputs 14 | artifacts/ 15 | cache/ 16 | build 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cross-dom-comm/f2/lib/forge-std"] 2 | path = cross-dom-comm/foundry/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [DEPRECATED] OP Mainnet Tutorials 2 | 3 | This documentation is deprecated. Please use our new [technical documentation](https://docs.optimism.io). 4 | 5 | [![Discord](https://img.shields.io/discord/667044843901681675.svg?color=768AD4&label=discord&logo=https%3A%2F%2Fdiscordapp.com%2Fassets%2F8c9701b98ad4372b58f13fd9f65f966e.svg)](https://discord-gateway.optimism.io) 6 | [![Twitter Follow](https://img.shields.io/twitter/follow/optimismFND.svg?label=optimismFND&style=social)](https://twitter.com/optimismFND) 7 | 8 | ## Getting Started 9 | 10 | * [Getting started developing for OP Mainnet](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/getting-started) 11 | * [Writing your first contract on OP Mainnet (or OP Goerli)](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/first-contract) - if you are just getting started with blockchain development 12 | 13 | 14 | ## Cross Domain 15 | 16 | * [Communication between contracts on OP Mainnet and Ethereum](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/cross-dom-comm) 17 | * [Bridging ETH with the Optimism SDK](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/cross-dom-bridge-eth) 18 | * [Bridging ERC-20 tokens with the Optimism SDK](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/cross-dom-bridge-erc20) 19 | * [View transactions between layers](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/sdk-view-tx) 20 | * [Creating an ERC20 Token on L2 to represent one on L1](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/standard-bridge-standard-token) 21 | * [Registering a Custom ERC20 Token on L2](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/standard-bridge-custom-token) 22 | 23 | 24 | 25 | ## Misc. 26 | 27 | * [Estimate the costs of an OP Mainnet transaction](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/sdk-estimate-gas) 28 | 29 | 30 | ## Ecosystem 31 | 32 | - [AttestationStation (Reputation primitive on OP Mainnet)](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/ecosystem/attestation-station) 33 | - [Tenderly](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/ecosystem/tenderly) 34 | -------------------------------------------------------------------------------- /cross-dom-bridge-erc20/.env.example: -------------------------------------------------------------------------------- 1 | # Put the mnemonic for an account on Optimism here 2 | MNEMONIC="test test test test test test test test test test test junk" 3 | GOERLI_ALCHEMY_KEY= <> 4 | OP_GOERLI_ALCHEMY_KEY= <> 5 | 6 | -------------------------------------------------------------------------------- /cross-dom-bridge-erc20/index.js: -------------------------------------------------------------------------------- 1 | #! /usr/local/bin/node 2 | 3 | // ERC-20 transfers between L1 and L2 using the Optimism SDK 4 | 5 | const ethers = require("ethers") 6 | const optimismSDK = require("@eth-optimism/sdk") 7 | require('dotenv').config() 8 | 9 | 10 | const mnemonic = process.env.MNEMONIC 11 | 12 | const words = process.env.MNEMONIC.match(/[a-zA-Z]+/g).length 13 | validLength = [12, 15, 18, 24] 14 | if (!validLength.includes(words)) { 15 | console.log(`The mnemonic (${process.env.MNEMONIC}) is the wrong number of words`) 16 | process.exit(-1) 17 | } 18 | 19 | const l1Url = `https://eth-goerli.g.alchemy.com/v2/${process.env.GOERLI_ALCHEMY_KEY}` 20 | const l2Url = `https://opt-goerli.g.alchemy.com/v2/${process.env.OP_GOERLI_ALCHEMY_KEY}` 21 | 22 | 23 | // Contract addresses for OPTb tokens, taken 24 | // from https://github.com/ethereum-optimism/ethereum-optimism.github.io/blob/master/data/OUTb/data.json 25 | const erc20Addrs = { 26 | l1Addr: "0x32B3b2281717dA83463414af4E8CfB1970E56287", 27 | l2Addr: "0x3e7eF8f50246f725885102E8238CBba33F276747" 28 | } // erc20Addrs 29 | 30 | // To learn how to deploy an L2 equivalent to an L1 ERC-20 contract, 31 | // see here: 32 | // https://github.com/ethereum-optimism/optimism-tutorial/tree/main/standard-bridge-standard-token 33 | 34 | 35 | // Global variable because we need them almost everywhere 36 | let crossChainMessenger 37 | let l1ERC20, l2ERC20 // OUTb contracts to show ERC-20 transfers 38 | let ourAddr // The address of the signer we use. 39 | 40 | 41 | // Get signers on L1 and L2 (for the same address). Note that 42 | // this address needs to have ETH on it, both on Optimism and 43 | // Optimism Georli 44 | const getSigners = async () => { 45 | const l1RpcProvider = new ethers.providers.JsonRpcProvider(l1Url) 46 | const l2RpcProvider = new ethers.providers.JsonRpcProvider(l2Url) 47 | const hdNode = ethers.utils.HDNode.fromMnemonic(mnemonic) 48 | const privateKey = hdNode.derivePath(ethers.utils.defaultPath).privateKey 49 | const l1Wallet = new ethers.Wallet(privateKey, l1RpcProvider) 50 | const l2Wallet = new ethers.Wallet(privateKey, l2RpcProvider) 51 | 52 | return [l1Wallet, l2Wallet] 53 | } // getSigners 54 | 55 | 56 | 57 | // The ABI fragment for the contract. We only need to know how to do two things: 58 | // 1. Get an account's balance 59 | // 2. Call the faucet to get more (only works on L1). Of course, production 60 | // ERC-20 tokens tend to be a bit harder to acquire. 61 | const erc20ABI = [ 62 | // balanceOf 63 | { 64 | constant: true, 65 | inputs: [{ name: "_owner", type: "address" }], 66 | name: "balanceOf", 67 | outputs: [{ name: "balance", type: "uint256" }], 68 | type: "function", 69 | }, 70 | // faucet 71 | { 72 | inputs: [], 73 | name: "faucet", 74 | outputs: [], 75 | stateMutability: "nonpayable", 76 | type: "function" 77 | } 78 | ] // erc20ABI 79 | 80 | 81 | 82 | const setup = async() => { 83 | const [l1Signer, l2Signer] = await getSigners() 84 | ourAddr = l1Signer.address 85 | crossChainMessenger = new optimismSDK.CrossChainMessenger({ 86 | l1ChainId: 5, // Goerli value, 1 for mainnet 87 | l2ChainId: 420, // Goerli value, 10 for mainnet 88 | l1SignerOrProvider: l1Signer, 89 | l2SignerOrProvider: l2Signer, 90 | }) 91 | l1ERC20 = new ethers.Contract(erc20Addrs.l1Addr, erc20ABI, l1Signer) 92 | l2ERC20 = new ethers.Contract(erc20Addrs.l2Addr, erc20ABI, l2Signer) 93 | } // setup 94 | 95 | 96 | 97 | const reportERC20Balances = async () => { 98 | const l1Balance = (await l1ERC20.balanceOf(ourAddr)).toString().slice(0,-18) 99 | const l2Balance = (await l2ERC20.balanceOf(ourAddr)).toString().slice(0,-18) 100 | console.log(`OUTb on L1:${l1Balance} OUTb on L2:${l2Balance}`) 101 | 102 | if (l1Balance != 0) { 103 | return 104 | } 105 | 106 | console.log(`You don't have enough OUTb on L1. Let's call the faucet to fix that`) 107 | const tx = (await l1ERC20.faucet()) 108 | console.log(`Faucet tx: ${tx.hash}`) 109 | console.log(`\tMore info: https://goerli.etherscan.io/tx/${tx.hash}`) 110 | await tx.wait() 111 | const newBalance = (await l1ERC20.balanceOf(ourAddr)).toString().slice(0,-18) 112 | console.log(`New L1 OUTb balance: ${newBalance}`) 113 | } // reportERC20Balances 114 | 115 | 116 | 117 | 118 | const oneToken = BigInt(1e18) 119 | 120 | 121 | const depositERC20 = async () => { 122 | 123 | console.log("Deposit ERC20") 124 | await reportERC20Balances() 125 | const start = new Date() 126 | 127 | // Need the l2 address to know which bridge is responsible 128 | const allowanceResponse = await crossChainMessenger.approveERC20( 129 | erc20Addrs.l1Addr, erc20Addrs.l2Addr, oneToken) 130 | await allowanceResponse.wait() 131 | console.log(`Allowance given by tx ${allowanceResponse.hash}`) 132 | console.log(`\tMore info: https://goerli.etherscan.io/tx/${allowanceResponse.hash}`) 133 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 134 | 135 | const response = await crossChainMessenger.depositERC20( 136 | erc20Addrs.l1Addr, erc20Addrs.l2Addr, oneToken) 137 | console.log(`Deposit transaction hash (on L1): ${response.hash}`) 138 | console.log(`\tMore info: https://goerli.etherscan.io/tx/${response.hash}`) 139 | await response.wait() 140 | console.log("Waiting for status to change to RELAYED") 141 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 142 | await crossChainMessenger.waitForMessageStatus(response.hash, 143 | optimismSDK.MessageStatus.RELAYED) 144 | 145 | await reportERC20Balances() 146 | console.log(`depositERC20 took ${(new Date()-start)/1000} seconds\n\n`) 147 | } // depositERC20() 148 | 149 | 150 | 151 | const withdrawERC20 = async () => { 152 | 153 | console.log("Withdraw ERC20") 154 | const start = new Date() 155 | await reportERC20Balances() 156 | 157 | const response = await crossChainMessenger.withdrawERC20( 158 | erc20Addrs.l1Addr, erc20Addrs.l2Addr, oneToken) 159 | console.log(`Transaction hash (on L2): ${response.hash}`) 160 | console.log(`\tFor more information: https://goerli-optimism.etherscan.io/tx/${response.hash}`) 161 | await response.wait() 162 | 163 | console.log("Waiting for status to be READY_TO_PROVE") 164 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 165 | await crossChainMessenger.waitForMessageStatus(response.hash, 166 | optimismSDK.MessageStatus.READY_TO_PROVE) 167 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 168 | await crossChainMessenger.proveMessage(response.hash) 169 | 170 | 171 | console.log("In the challenge period, waiting for status READY_FOR_RELAY") 172 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 173 | await crossChainMessenger.waitForMessageStatus(response.hash, 174 | optimismSDK.MessageStatus.READY_FOR_RELAY) 175 | console.log("Ready for relay, finalizing message now") 176 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 177 | await crossChainMessenger.finalizeMessage(response.hash) 178 | 179 | console.log("Waiting for status to change to RELAYED") 180 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 181 | await crossChainMessenger.waitForMessageStatus(response, 182 | optimismSDK.MessageStatus.RELAYED) 183 | await reportERC20Balances() 184 | console.log(`withdrawERC20 took ${(new Date()-start)/1000} seconds\n\n\n`) 185 | } // withdrawERC20() 186 | 187 | 188 | 189 | 190 | const main = async () => { 191 | await setup() 192 | await depositERC20() 193 | await withdrawERC20() 194 | } // main 195 | 196 | 197 | 198 | main().then(() => process.exit(0)) 199 | .catch((error) => { 200 | console.error(error) 201 | process.exit(1) 202 | }) 203 | 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /cross-dom-bridge-erc20/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cross-dom-bridge-erc20", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "@eth-optimism/sdk": "^3.0.0", 8 | "dotenv": "^16.0.0" 9 | }, 10 | "scripts": { 11 | "script": "node index.js" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cross-dom-bridge-eth/.env.example: -------------------------------------------------------------------------------- 1 | # Put the mnemonic for an account on Optimism here 2 | MNEMONIC="test test test test test test test test test test test junk" 3 | GOERLI_ALCHEMY_KEY= <> 4 | OP_GOERLI_ALCHEMY_KEY= <> 5 | 6 | -------------------------------------------------------------------------------- /cross-dom-bridge-eth/index.js: -------------------------------------------------------------------------------- 1 | #! /usr/local/bin/node 2 | 3 | // Transfers between L1 and L2 using the Optimism SDK 4 | 5 | const ethers = require("ethers") 6 | const optimismSDK = require("@eth-optimism/sdk") 7 | require('dotenv').config() 8 | 9 | 10 | const mnemonic = process.env.MNEMONIC 11 | 12 | const words = process.env.MNEMONIC.match(/[a-zA-Z]+/g).length 13 | validLength = [12, 15, 18, 24] 14 | if (!validLength.includes(words)) { 15 | console.log(`The mnemonic (${process.env.MNEMONIC}) is the wrong number of words`) 16 | process.exit(-1) 17 | } 18 | 19 | const l1Url = `https://eth-goerli.g.alchemy.com/v2/${process.env.GOERLI_ALCHEMY_KEY}` 20 | const l2Url = `https://opt-goerli.g.alchemy.com/v2/${process.env.OP_GOERLI_ALCHEMY_KEY}` 21 | 22 | 23 | // Global variable because we need them almost everywhere 24 | let crossChainMessenger 25 | let addr // Our address 26 | 27 | const getSigners = async () => { 28 | const l1RpcProvider = new ethers.providers.JsonRpcProvider(l1Url) 29 | const l2RpcProvider = new ethers.providers.JsonRpcProvider(l2Url) 30 | const hdNode = ethers.utils.HDNode.fromMnemonic(mnemonic) 31 | const privateKey = hdNode.derivePath(ethers.utils.defaultPath).privateKey 32 | const l1Wallet = new ethers.Wallet(privateKey, l1RpcProvider) 33 | const l2Wallet = new ethers.Wallet(privateKey, l2RpcProvider) 34 | 35 | return [l1Wallet, l2Wallet] 36 | } // getSigners 37 | 38 | 39 | const setup = async() => { 40 | const [l1Signer, l2Signer] = await getSigners() 41 | addr = l1Signer.address 42 | crossChainMessenger = new optimismSDK.CrossChainMessenger({ 43 | l1ChainId: 5, // Goerli value, 1 for mainnet 44 | l2ChainId: 420, // Goerli value, 10 for mainnet 45 | l1SignerOrProvider: l1Signer, 46 | l2SignerOrProvider: l2Signer, 47 | }) 48 | } // setup 49 | 50 | 51 | 52 | const gwei = BigInt(1e9) 53 | const eth = gwei * gwei // 10^18 54 | const centieth = eth/100n 55 | 56 | 57 | const reportBalances = async () => { 58 | const l1Balance = (await crossChainMessenger.l1Signer.getBalance()).toString().slice(0,-9) 59 | const l2Balance = (await crossChainMessenger.l2Signer.getBalance()).toString().slice(0,-9) 60 | 61 | console.log(`On L1:${l1Balance} Gwei On L2:${l2Balance} Gwei`) 62 | } // reportBalances 63 | 64 | 65 | const depositETH = async () => { 66 | 67 | console.log("Deposit ETH") 68 | await reportBalances() 69 | const start = new Date() 70 | 71 | const response = await crossChainMessenger.depositETH(1000n * gwei) 72 | console.log(`Transaction hash (on L1): ${response.hash}`) 73 | await response.wait() 74 | console.log("Waiting for status to change to RELAYED") 75 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 76 | await crossChainMessenger.waitForMessageStatus(response.hash, 77 | optimismSDK.MessageStatus.RELAYED) 78 | 79 | await reportBalances() 80 | console.log(`depositETH took ${(new Date()-start)/1000} seconds\n\n`) 81 | } // depositETH() 82 | 83 | 84 | 85 | 86 | 87 | const withdrawETH = async () => { 88 | 89 | console.log("Withdraw ETH") 90 | const start = new Date() 91 | await reportBalances() 92 | 93 | const response = await crossChainMessenger.withdrawETH(centieth) 94 | console.log(`Transaction hash (on L2): ${response.hash}`) 95 | console.log(`\tFor more information: https://goerli-optimism.etherscan.io/tx/${response.hash}`) 96 | await response.wait() 97 | 98 | console.log("Waiting for status to be READY_TO_PROVE") 99 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 100 | await crossChainMessenger.waitForMessageStatus(response.hash, 101 | optimismSDK.MessageStatus.READY_TO_PROVE) 102 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 103 | await crossChainMessenger.proveMessage(response.hash) 104 | 105 | 106 | console.log("In the challenge period, waiting for status READY_FOR_RELAY") 107 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 108 | await crossChainMessenger.waitForMessageStatus(response.hash, 109 | optimismSDK.MessageStatus.READY_FOR_RELAY) 110 | console.log("Ready for relay, finalizing message now") 111 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 112 | await crossChainMessenger.finalizeMessage(response.hash) 113 | 114 | console.log("Waiting for status to change to RELAYED") 115 | console.log(`Time so far ${(new Date()-start)/1000} seconds`) 116 | await crossChainMessenger.waitForMessageStatus(response, 117 | optimismSDK.MessageStatus.RELAYED) 118 | 119 | await reportBalances() 120 | console.log(`withdrawETH took ${(new Date()-start)/1000} seconds\n\n\n`) 121 | } // withdrawETH() 122 | 123 | 124 | const main = async () => { 125 | await setup() 126 | await depositETH() 127 | await withdrawETH() 128 | // await depositERC20() 129 | // await withdrawERC20() 130 | } // main 131 | 132 | 133 | 134 | main().then(() => process.exit(0)) 135 | .catch((error) => { 136 | console.error(error) 137 | process.exit(1) 138 | }) 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /cross-dom-bridge-eth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "l1-to-l2-comm", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "@eth-optimism/sdk": "^3.0.0", 8 | "dotenv": "^16.0.0" 9 | }, 10 | "scripts": { 11 | "script": "node index.js" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cross-dom-comm/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | #Hardhat files 4 | cache 5 | artifacts 6 | 7 | 8 | # Foundry files 9 | out 10 | 11 | #We don't want the mnemonic file 12 | mnem.delme 13 | 14 | -------------------------------------------------------------------------------- /cross-dom-comm/foundry/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /cross-dom-comm/foundry/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@eth-optimism/sdk": "^3.0.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /cross-dom-comm/foundry/remappings.txt: -------------------------------------------------------------------------------- 1 | @eth-optimism/=lib/node_modules/@eth-optimism/ 2 | 3 | -------------------------------------------------------------------------------- /cross-dom-comm/foundry/src: -------------------------------------------------------------------------------- 1 | ../hardhat/contracts -------------------------------------------------------------------------------- /cross-dom-comm/hardhat/.env.example: -------------------------------------------------------------------------------- 1 | # Put the mnemonic for an account on Optimism here 2 | MNEMONIC="test test test test test test test test test test test junk" 3 | GOERLI_ALCHEMY_KEY= <> 4 | OP_GOERLI_ALCHEMY_KEY= <> 5 | 6 | -------------------------------------------------------------------------------- /cross-dom-comm/hardhat/contracts/FromL1_ControlL2Greeter.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | // This contracts runs on L1, and controls a Greeter on L2. 3 | // The addresses are specific to Optimistic Goerli. 4 | pragma solidity ^0.8.0; 5 | 6 | import { ICrossDomainMessenger } from 7 | "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; 8 | 9 | contract FromL1_ControlL2Greeter { 10 | // Taken from https://community.optimism.io/docs/useful-tools/networks/#optimism-goerli 11 | 12 | address crossDomainMessengerAddr = 0x5086d1eEF304eb5284A0f6720f79403b4e9bE294; 13 | 14 | address greeterL2Addr = 0xE8B462EEF7Cbd4C855Ea4B65De65a5c5Bab650A9; 15 | 16 | function setGreeting(string calldata _greeting) public { 17 | bytes memory message; 18 | 19 | message = abi.encodeWithSignature("setGreeting(string,address)", 20 | _greeting, msg.sender); 21 | 22 | ICrossDomainMessenger(crossDomainMessengerAddr).sendMessage( 23 | greeterL2Addr, 24 | message, 25 | 1000000 // within the free gas limit amount 26 | ); 27 | } // function setGreeting 28 | 29 | } // contract FromL1_ControlL2Greeter 30 | -------------------------------------------------------------------------------- /cross-dom-comm/hardhat/contracts/FromL2_ControlL1Greeter.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | // This contracts runs on L2, and controls a Greeter on L1. 3 | // The greeter address is specific to Goerli. 4 | pragma solidity ^0.8.0; 5 | 6 | import { ICrossDomainMessenger } from 7 | "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; 8 | 9 | contract FromL2_ControlL1Greeter { 10 | // Taken from https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/deployments/goerli#layer-2-contracts 11 | // Should be the same on all Optimism networks 12 | address crossDomainMessengerAddr = 0x4200000000000000000000000000000000000007; 13 | 14 | address greeterL1Addr = 0x4d0fcc1Bedd933dA4121240C2955c3Ceb68AAE84; 15 | 16 | function setGreeting(string calldata _greeting) public { 17 | bytes memory message; 18 | 19 | message = abi.encodeWithSignature("setGreeting(string,address)", 20 | _greeting, msg.sender); 21 | 22 | ICrossDomainMessenger(crossDomainMessengerAddr).sendMessage( 23 | greeterL1Addr, 24 | message, 25 | 1000000 // irrelevant here 26 | ); 27 | } // function setGreeting 28 | 29 | } // contract FromL2_ControlL1Greeter 30 | -------------------------------------------------------------------------------- /cross-dom-comm/hardhat/contracts/Greeter.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.0; 3 | 4 | // For cross domain messages' origin 5 | import { ICrossDomainMessenger } from 6 | "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; 7 | 8 | contract Greeter { 9 | string greeting; 10 | 11 | event SetGreeting( 12 | address sender, // msg.sender 13 | address origin, // tx.origin 14 | address xorigin, // cross domain origin, if any 15 | address user, // user address, if given 16 | string greeting // The greeting 17 | ); 18 | 19 | 20 | constructor(string memory _greeting) { 21 | greeting = _greeting; 22 | } 23 | 24 | function greet() public view returns (string memory) { 25 | return greeting; 26 | } 27 | 28 | function setGreeting(string memory _greeting, address _user) public { 29 | greeting = _greeting; 30 | emit SetGreeting(msg.sender, tx.origin, getXorig(), _user, _greeting); 31 | } 32 | 33 | 34 | function setGreeting(string memory _greeting) public { 35 | greeting = _greeting; 36 | emit SetGreeting(msg.sender, tx.origin, getXorig(), address(0), _greeting); 37 | } 38 | 39 | // Get the cross domain origin, if any 40 | function getXorig() private view returns (address) { 41 | // Get the cross domain messenger's address each time. 42 | // This is less resource intensive than writing to storage. 43 | address cdmAddr = address(0); 44 | 45 | // Mainnet 46 | if (block.chainid == 1) 47 | cdmAddr = 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1; 48 | 49 | // Goerli 50 | if (block.chainid == 5) 51 | cdmAddr = 0x5086d1eEF304eb5284A0f6720f79403b4e9bE294; 52 | 53 | // L2 (same address on every network) 54 | if (block.chainid == 10 || block.chainid == 420) 55 | cdmAddr = 0x4200000000000000000000000000000000000007; 56 | 57 | // If this isn't a cross domain message 58 | if (msg.sender != cdmAddr) 59 | return address(0); 60 | 61 | // If it is a cross domain message, find out where it is from 62 | return ICrossDomainMessenger(cdmAddr).xDomainMessageSender(); 63 | } // getXorig() 64 | } // contract Greeter 65 | -------------------------------------------------------------------------------- /cross-dom-comm/hardhat/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomiclabs/hardhat-waffle"); 2 | require('dotenv').config() 3 | 4 | // This is a sample Hardhat task. To learn how to create your own go to 5 | // https://hardhat.org/guides/create-task.html 6 | task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { 7 | const accounts = await hre.ethers.getSigners(); 8 | 9 | for (const account of accounts) { 10 | console.log(account.address); 11 | } 12 | }); 13 | 14 | 15 | // You need to export an object to set up your config 16 | // Go to https://hardhat.org/config/ to learn more 17 | 18 | const words = process.env.MNEMONIC.match(/[a-zA-Z]+/g).length 19 | validLength = [12, 15, 18, 24] 20 | if (!validLength.includes(words)) { 21 | console.log(`The mnemonic (${process.env.MNEMONIC}) is the wrong number of words`) 22 | process.exit(-1) 23 | } 24 | 25 | /** 26 | * @type import('hardhat/config').HardhatUserConfig 27 | */ 28 | module.exports = { 29 | solidity: "0.8.4", 30 | networks: { 31 | "op-goerli": { 32 | url: `https://opt-goerli.g.alchemy.com/v2/${process.env.OP_GOERLI_ALCHEMY_KEY}`, 33 | accounts: { mnemonic: process.env.MNEMONIC } 34 | }, 35 | "goerli": { 36 | url: `https://eth-goerli.g.alchemy.com/v2/${process.env.GOERLI_ALCHEMY_KEY}`, 37 | accounts: { mnemonic: process.env.MNEMONIC } 38 | } 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /cross-dom-comm/hardhat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-project", 3 | "devDependencies": { 4 | "@nomiclabs/hardhat-ethers": "^2.0.5", 5 | "@nomiclabs/hardhat-waffle": "^2.0.2", 6 | "chai": "^4.3.6", 7 | "ethereum-waffle": "^3.4.0", 8 | "ethers": "^5.5.4", 9 | "hardhat": "^2.8.4" 10 | }, 11 | "dependencies": { 12 | "@eth-optimism/contracts": "^0.5.40", 13 | "@eth-optimism/sdk": "^3.0.0", 14 | "dotenv": "^16.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cross-dom-comm/hardhat/test/sample-test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | 3 | describe("Greeter", function () { 4 | it("Should return the new greeting once it's changed", async function () { 5 | const Greeter = await ethers.getContractFactory("Greeter"); 6 | const greeter = await Greeter.deploy("Hello, world!"); 7 | await greeter.deployed(); 8 | console.log(`Testing with greeter at ${greeter.address}`) 9 | 10 | expect(await greeter.greet()).to.equal("Hello, world!"); 11 | 12 | const setGreetingTx = await greeter.setGreeting("Hola, mundo!"); 13 | 14 | // wait until the transaction is mined 15 | await setGreetingTx.wait(); 16 | 17 | expect(await greeter.greet()).to.equal("Hola, mundo!"); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /ecosystem/alchemy/README.md: -------------------------------------------------------------------------------- 1 | # Getting your API keys from Alchemy 2 | 3 | [![Discord](https://img.shields.io/discord/667044843901681675.svg?color=768AD4&label=discord&logo=https%3A%2F%2Fdiscordapp.com%2Fassets%2F8c9701b98ad4372b58f13fd9f65f966e.svg)](https://discord-gateway.optimism.io) 4 | [![Twitter Follow](https://img.shields.io/twitter/follow/optimismFND.svg?label=optimismFND&style=social)](https://twitter.com/optimismFND) 5 | 6 | This tutorial assumes you are completely new to Alchemy, but that you want to get applications and API keys to use with OP Mainnet or OP Goerli. 7 | 8 | 9 | ## Register 10 | 11 | The first step is to register for a free Alchemy account. 12 | 13 | 1. Browse [to Alchemy](https://auth.alchemyapi.io/signup?redirectUrl=https%3A%2F%2Fdashboard.alchemyapi.io%2Fsignup%2F%3Freferrer_origin%3DDIRECT), enter user information, and click **Sign up**. 14 | 15 | 16 | 17 | 1. Go to your e-mail and click **VERIFY EMAIL**. 18 | 19 | 1. Fill up the requested information (team, type of project, etc.). 20 | 21 | 22 | 23 | 1. When asked for chain select **Optimism** and click **Next**. 24 | 25 | 26 | 27 | 1. Select the free plan and click **Next**. 28 | 29 | 1. Either enter payment details or click **Skip for now**. 30 | 31 | 32 | 33 | 1. Enter where you heard about Alchemy and click **Let's Build!**. 34 | 35 | 36 | 37 | 38 | ## Create new applications 39 | 40 | The next step is to create applications and get their API keys. 41 | 42 | ### Create a new OP Goerli application 43 | 44 | 1. Browse to the [Alchemy dashboard](https://dashboard.alchemyapi.io/). 45 | 46 | 1. Click **+CREATE APP**. 47 | 48 | 1. Specify a name, select the chain **Optimism** and the network **Optimism Goerli**. 49 | 50 | 51 | 52 | 1. Click **CREATE APP**. 53 | 54 | 1. Click **VIEW KEY** in the Testnet-L2 line 55 | 56 | 57 | 58 | 1. Copy the API Key. 59 | 60 | 61 | 62 | Alternatively, if you need a URL, copy the HTTPS definition. 63 | 64 | ### Create a new Goerli application 65 | 66 | Follow the same instructions as for OP Goerli, but select the chain **Ethereum** and the network **Goerli**. -------------------------------------------------------------------------------- /ecosystem/alchemy/assets/copykey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum-optimism/optimism-tutorial/5cf1f84e4c55dcff50469af3f89a05d82b9f6681/ecosystem/alchemy/assets/copykey.png -------------------------------------------------------------------------------- /ecosystem/alchemy/assets/create-app-l2-testnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum-optimism/optimism-tutorial/5cf1f84e4c55dcff50469af3f89a05d82b9f6681/ecosystem/alchemy/assets/create-app-l2-testnet.png -------------------------------------------------------------------------------- /ecosystem/alchemy/assets/lets-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum-optimism/optimism-tutorial/5cf1f84e4c55dcff50469af3f89a05d82b9f6681/ecosystem/alchemy/assets/lets-build.png -------------------------------------------------------------------------------- /ecosystem/alchemy/assets/payment-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum-optimism/optimism-tutorial/5cf1f84e4c55dcff50469af3f89a05d82b9f6681/ecosystem/alchemy/assets/payment-details.png -------------------------------------------------------------------------------- /ecosystem/alchemy/assets/select-op.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum-optimism/optimism-tutorial/5cf1f84e4c55dcff50469af3f89a05d82b9f6681/ecosystem/alchemy/assets/select-op.png -------------------------------------------------------------------------------- /ecosystem/alchemy/assets/signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum-optimism/optimism-tutorial/5cf1f84e4c55dcff50469af3f89a05d82b9f6681/ecosystem/alchemy/assets/signup.png -------------------------------------------------------------------------------- /ecosystem/alchemy/assets/team-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum-optimism/optimism-tutorial/5cf1f84e4c55dcff50469af3f89a05d82b9f6681/ecosystem/alchemy/assets/team-form.png -------------------------------------------------------------------------------- /ecosystem/alchemy/assets/viewkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum-optimism/optimism-tutorial/5cf1f84e4c55dcff50469af3f89a05d82b9f6681/ecosystem/alchemy/assets/viewkey.png -------------------------------------------------------------------------------- /ecosystem/attestation-station/README.md: -------------------------------------------------------------------------------- 1 | # AttestationStation 2 | 3 | [![Discord](https://img.shields.io/discord/667044843901681675.svg?color=768AD4&label=discord&logo=https%3A%2F%2Fdiscordapp.com%2Fassets%2F8c9701b98ad4372b58f13fd9f65f966e.svg)](https://discord-gateway.optimism.io) 4 | [![Twitter Follow](https://img.shields.io/twitter/follow/optimismFND.svg?label=optimismFND&style=social)](https://twitter.com/optimismFND) 5 | 6 | The [AttestationStation smart contract](https://github.com/ethereum-optimism/optimism/blob/8b392e9b613ea4ca0270c2dca24d3485b7454954/packages/contracts-periphery/contracts/universal/op-nft/AttestationStation.sol) contains a public `attestations` mapping that anyone can write to and read from. 7 | For more context on the AttestationStation visit the [overview in our developer documentation](https://community.optimism.io/docs/identity/). 8 | In this tutorial you learn how to read, interpret, and write those attestations. 9 | 10 | The contract we'll be using is on the Optimism Goerli network, at address [`0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77`](https://goerli-explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77). 11 | On the production Optimism network the contract is [at the same address](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77) 12 | 13 | 14 | ## Table of content 15 | 16 | - [AttestationStation: Direct contract access](contract-access/README.md) 17 | 18 | 19 | -------------------------------------------------------------------------------- /ecosystem/attestation-station/contract-access/.env.example: -------------------------------------------------------------------------------- 1 | # Put the mnemonic for an account on Optimism here 2 | MNEMONIC=test test test test test test test test test test test junk 3 | 4 | # API KEY for Alchemy 5 | ALCHEMY_API_KEY= 6 | 7 | # URL to access Optimism Goerli (if not using Alchemy) 8 | OPTIMISM_GOERLI_URL= 9 | -------------------------------------------------------------------------------- /ecosystem/attestation-station/contract-access/attest.mjs: -------------------------------------------------------------------------------- 1 | #! /usr/local/bin/node 2 | 3 | 4 | import dotenv from "dotenv" 5 | dotenv.config() 6 | 7 | import ethers from "ethers" 8 | import fs from "fs" 9 | 10 | 11 | const words = process.env.MNEMONIC.match(/[a-zA-Z]+/g).length 12 | const validLength = [12, 15, 18, 24] 13 | if (!validLength.includes(words)) { 14 | console.log(`The mnemonic (${process.env.MNEMONIC}) is the wrong number of words`) 15 | process.exit(-1) 16 | } 17 | 18 | const endpointUrl = process.env.ALCHEMY_API_KEY ? 19 | `https://opt-goerli.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}` : 20 | process.env.OPTIMISM_GOERLI_URL 21 | 22 | 23 | const provider = new ethers.providers.JsonRpcProvider(endpointUrl) 24 | const wallet = ethers.Wallet.fromMnemonic(process.env.MNEMONIC). 25 | connect(provider) 26 | 27 | const ATST_JSON = JSON.parse(fs.readFileSync("AttestationStation.json")) 28 | 29 | const AttestationStation = new ethers.ContractFactory(ATST_JSON.abi, ATST_JSON.bytecode, wallet) 30 | const attestationStation = AttestationStation.attach('0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77') 31 | 32 | 33 | const encodeRawKey = rawKey => { 34 | if (rawKey.length<32) 35 | return ethers.utils.formatBytes32String(rawKey) 36 | 37 | const hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(rawKey)) 38 | return hash.slice(0,64)+'ff' 39 | } 40 | 41 | 42 | const main = async () => { 43 | 44 | console.log("Write an attestation") 45 | 46 | const goatAddr = '0x00000000000000000000000000000000000060A7' 47 | const attendedKey = encodeRawKey("animalfarm.school.attended") 48 | const attestation = { 49 | about: goatAddr, 50 | key: attendedKey, 51 | val: 1 // for true 52 | } 53 | 54 | const attestTx = await attestationStation.functions['attest(address,bytes32,bytes)']( 55 | attestation.about, 56 | attestation.key, 57 | attestation.val) 58 | console.log("Transaction sent") 59 | const attestRcpt = await attestTx.wait() 60 | 61 | console.log(`Attestation transaction: https://goerli-explorer.optimism.io/tx/${attestRcpt.transactionHash}`) 62 | 63 | console.log("\nReading the attestation") 64 | const myAddr = wallet.address 65 | console.log(`Key: ${await attestationStation.attestations(myAddr, goatAddr, attendedKey)}`) 66 | 67 | console.log("\nReading another attestation (that isn't there)") 68 | const notGoatAddr = '0x000000000000000000000000000000000000BEEF' 69 | console.log(`Key: ${await attestationStation.attestations(myAddr, notGoatAddr, attendedKey)}`) 70 | 71 | console.log("\nReading an ASCII attestation") 72 | const historyKey = encodeRawKey("animal-farm.school.grades.history") 73 | const hex = await attestationStation.attestations('0xBCf86Fd70a0183433763ab0c14E7a760194f3a9F', 74 | goatAddr, historyKey) 75 | console.log(`Goat's grade in history: ${ethers.utils.toUtf8String(hex)}`) 76 | 77 | 78 | } // main 79 | 80 | 81 | main().then(() => process.exit(0)) 82 | .catch((error) => { 83 | console.error(error) 84 | process.exit(1) 85 | }) 86 | 87 | -------------------------------------------------------------------------------- /ecosystem/attestation-station/contract-access/contracts/AttestationProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | // Copied from FlipsideCrypto's 5 | // https://github.com/FlipsideCrypto/user_metrics/blob/main/apps/optimism/attestation_contracts/contracts/FlipsideAttestation.sol 6 | // with small changes. 7 | 8 | 9 | // To verify signatures 10 | import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 11 | 12 | // The owner is the signer 13 | import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; 14 | 15 | 16 | interface IAttestationStation { 17 | struct AttestationData { 18 | address about; 19 | bytes32 key; 20 | bytes val; 21 | } 22 | 23 | event AttestationCreated( 24 | address indexed creator, 25 | address indexed about, 26 | bytes32 indexed key, 27 | bytes val 28 | ); 29 | 30 | function attest(AttestationData[] memory _attestations) external; 31 | } 32 | 33 | 34 | contract AttestationProxy is Ownable { 35 | using ECDSA for bytes32; 36 | 37 | /// @dev The interface for OP's Attestation Station. 38 | IAttestationStation public attestationStation; 39 | 40 | constructor( 41 | address _attestationStation 42 | ) { 43 | attestationStation = IAttestationStation(_attestationStation); 44 | } 45 | 46 | /** 47 | * @notice Allows the owner to change the AttestationStation implementation. 48 | * @param _attestationStation The address of the new AttestationStation implementation. 49 | * 50 | * Requirements: 51 | * - The caller must be the current owner. 52 | */ 53 | function setAttestationStation(address _attestationStation) public 54 | onlyOwner 55 | { 56 | attestationStation = IAttestationStation(_attestationStation); 57 | } 58 | 59 | /** 60 | * @notice Attest data 61 | * @param _about The address of the account to be attested. 62 | * @param _key The key of the attestation. 63 | * @param _val The value of the attestation. 64 | * @param _signature The signature of the attestation. 65 | */ 66 | function attest( 67 | address _about 68 | , bytes32 _key 69 | , bytes memory _val 70 | , bytes memory _signature 71 | ) 72 | public 73 | { 74 | _verifySignature( 75 | _about 76 | , _key 77 | , _val 78 | , _signature 79 | ); 80 | 81 | // Send the attestation to the Attestation Station. 82 | IAttestationStation.AttestationData[] memory attestations = new IAttestationStation.AttestationData[](1); 83 | attestations[0] = IAttestationStation.AttestationData({ 84 | about: _about 85 | , key: _key 86 | , val: _val 87 | }); 88 | attestationStation.attest(attestations); 89 | } 90 | 91 | /** 92 | * @notice Verifies the attestation data before calling the OP AttestationStation attest. 93 | * @param _about The address of the account to be attested. 94 | * @param _key The key of the attestation. 95 | * @param _val The value of the attestation. 96 | * @param _signature The signer's signed message of the attestation. 97 | * 98 | * Requirements: 99 | * - The signature must resolve to the signer. 100 | */ 101 | function _verifySignature( 102 | address _about 103 | , bytes32 _key 104 | , bytes memory _val 105 | , bytes memory _signature 106 | ) 107 | internal 108 | view 109 | { 110 | bytes32 messageHash = keccak256( 111 | abi.encodePacked( 112 | _about 113 | , _key 114 | , _val 115 | ) 116 | ); 117 | 118 | require(messageHash.toEthSignedMessageHash().recover(_signature) == owner(), 119 | "AttestationProxy: Invalid signature"); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /ecosystem/attestation-station/contract-access/contracts/AttestationStation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.15; 3 | 4 | import { Semver } from "@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol"; 5 | 6 | /** 7 | * @title AttestationStation 8 | * @author Optimism Collective 9 | * @author Gitcoin 10 | * @notice Where attestations live. 11 | */ 12 | contract AttestationStation is Semver { 13 | /** 14 | * @notice Struct representing data that is being attested. 15 | * 16 | * @custom:field about Address for which the attestation is about. 17 | * @custom:field key A bytes32 key for the attestation. 18 | * @custom:field val The attestation as arbitrary bytes. 19 | */ 20 | struct AttestationData { 21 | address about; 22 | bytes32 key; 23 | bytes val; 24 | } 25 | 26 | /** 27 | * @notice Maps addresses to attestations. Creator => About => Key => Value. 28 | */ 29 | mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations; 30 | 31 | /** 32 | * @notice Emitted when Attestation is created. 33 | * 34 | * @param creator Address that made the attestation. 35 | * @param about Address attestation is about. 36 | * @param key Key of the attestation. 37 | * @param val Value of the attestation. 38 | */ 39 | event AttestationCreated( 40 | address indexed creator, 41 | address indexed about, 42 | bytes32 indexed key, 43 | bytes val 44 | ); 45 | 46 | /** 47 | * @custom:semver 1.1.0 48 | */ 49 | constructor() Semver(1, 1, 0) {} 50 | 51 | /** 52 | * @notice Allows anyone to create an attestation. 53 | * 54 | * @param _about Address that the attestation is about. 55 | * @param _key A key used to namespace the attestation. 56 | * @param _val An arbitrary value stored as part of the attestation. 57 | */ 58 | function attest( 59 | address _about, 60 | bytes32 _key, 61 | bytes memory _val 62 | ) public { 63 | attestations[msg.sender][_about][_key] = _val; 64 | 65 | emit AttestationCreated(msg.sender, _about, _key, _val); 66 | } 67 | 68 | /** 69 | * @notice Allows anyone to create attestations. 70 | * 71 | * @param _attestations An array of AttestationData structs. 72 | */ 73 | function attest(AttestationData[] calldata _attestations) external { 74 | uint256 length = _attestations.length; 75 | for (uint256 i = 0; i < length; ) { 76 | AttestationData memory attestation = _attestations[i]; 77 | 78 | attest(attestation.about, attestation.key, attestation.val); 79 | 80 | unchecked { 81 | ++i; 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ecosystem/attestation-station/contract-access/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomiclabs/hardhat-waffle"); 2 | require('dotenv').config() 3 | 4 | // This is a sample Hardhat task. To learn how to create your own go to 5 | // https://hardhat.org/guides/create-task.html 6 | task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { 7 | const accounts = await hre.ethers.getSigners(); 8 | 9 | for (const account of accounts) { 10 | console.log(account.address); 11 | } 12 | }); 13 | 14 | // You need to export an object to set up your config 15 | // Go to https://hardhat.org/config/ to learn more 16 | 17 | /** 18 | * @type import('hardhat/config').HardhatUserConfig 19 | */ 20 | 21 | const optimismGoerliUrl = 22 | process.env.ALCHEMY_API_KEY ? 23 | `https://opt-goerli.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}` : 24 | process.env.OPTIMISM_GOERLI_URL 25 | 26 | const words = process.env.MNEMONIC.match(/[a-zA-Z]+/g).length 27 | validLength = [12, 15, 18, 24] 28 | if (!validLength.includes(words)) { 29 | console.log(`The mnemonic (${process.env.MNEMONIC}) is the wrong number of words`) 30 | process.exit(-1) 31 | } 32 | 33 | module.exports = { 34 | solidity: "0.8.15", 35 | networks: { 36 | "local-devnode": { 37 | url: "http://localhost:8545", 38 | accounts: { mnemonic: "test test test test test test test test test test test junk" } 39 | }, 40 | "optimism-goerli": { 41 | url: optimismGoerliUrl, 42 | accounts: { mnemonic: process.env.MNEMONIC } 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /ecosystem/attestation-station/contract-access/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-project", 3 | "devDependencies": { 4 | "@nomiclabs/hardhat-ethers": "^2.0.5", 5 | "@nomiclabs/hardhat-waffle": "^2.0.2", 6 | "chai": "^4.3.6", 7 | "ethereum-waffle": "^3.4.0", 8 | "ethers": "^5.5.4", 9 | "hardhat": "^2.8.4" 10 | }, 11 | "dependencies": { 12 | "@eth-optimism/contracts-bedrock": "^0.13.2", 13 | "@openzeppelin/contracts": "^4.8.1", 14 | "dotenv": "^16.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ecosystem/attestation-station/contract-access/read-events.mjs: -------------------------------------------------------------------------------- 1 | #! /usr/local/bin/node 2 | 3 | 4 | import dotenv from "dotenv" 5 | dotenv.config() 6 | 7 | import ethers from "ethers" 8 | import fs from "fs" 9 | 10 | 11 | const endpointUrl = process.env.ALCHEMY_API_KEY ? 12 | `https://opt-goerli.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}` : 13 | process.env.OPTIMISM_GOERLI_URL 14 | 15 | 16 | const provider = new ethers.providers.JsonRpcProvider(endpointUrl) 17 | const ATST_JSON = JSON.parse(fs.readFileSync("AttestationStation.json")) 18 | 19 | const attestationStation = new ethers.Contract( 20 | '0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77', ATST_JSON.abi, provider) 21 | 22 | 23 | const event2key = e => `${e.args.key}-${e.args.creator}` 24 | const update2Latest = (history, event) => { 25 | const key = event2key(event) 26 | if ((history[key] == null) || (history[key].blockNumber < event.blockNumber)) { 27 | history[key] = event 28 | return history // including this event 29 | } 30 | return history // without this event 31 | } 32 | 33 | 34 | const main = async () => { 35 | console.log("All AttestationCreated events about 60A7") 36 | 37 | const goatAddr = '0x00000000000000000000000000000000000060A7' 38 | const goatFilter = attestationStation.filters.AttestationCreated(null,goatAddr,null,null) 39 | const goatEvents = await attestationStation.queryFilter(goatFilter) 40 | console.log(goatEvents.map( 41 | entry => `${entry.args.creator}: ${entry.args.key} -> ${entry.args.val}` 42 | )) 43 | 44 | console.log(`\n Only relevant events (attestations not overwritten)`) 45 | 46 | const attestedHistory = goatEvents.reduce(update2Latest, {}) 47 | const relevantEvents = Object.keys(attestedHistory).map(key => attestedHistory[key]) 48 | console.log(relevantEvents.map( 49 | entry => `${entry.args.creator}: ${entry.args.key} -> ${entry.args.val}` 50 | )) 51 | 52 | console.log(`\nTotal events: ${goatEvents.length}`) 53 | console.log(`\Relevant events: ${relevantEvents.length}`) 54 | } // main 55 | 56 | 57 | main().then(() => process.exit(0)) 58 | .catch((error) => { 59 | console.error(error) 60 | process.exit(1) 61 | }) 62 | 63 | -------------------------------------------------------------------------------- /ecosystem/opengsn/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | #Hardhat files 4 | cache 5 | artifacts 6 | -------------------------------------------------------------------------------- /ecosystem/opengsn/contracts/Greeter.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.0; 3 | 4 | import "@opengsn/contracts/src/BaseRelayRecipient.sol"; 5 | 6 | contract Greeter is BaseRelayRecipient { 7 | string greeting; 8 | address lastGreeter; 9 | 10 | event LogEntry(address indexed _soliditySender, 11 | address _gsnSender, 12 | address _trustedForwarder, 13 | address _lastGreeter, 14 | bytes _data); 15 | 16 | 17 | constructor(string memory _greeting, address _trustedForwarderAddr) { 18 | greeting = _greeting; 19 | _setTrustedForwarder(_trustedForwarderAddr); 20 | lastGreeter = _msgSender(); 21 | } 22 | 23 | function versionRecipient() external virtual view override returns (string memory) { 24 | return "v. 1.0.0"; 25 | } 26 | 27 | function greet() public view returns (string memory) { 28 | return greeting; 29 | } 30 | 31 | function lastGreeterAddr() public view returns (address) { 32 | return lastGreeter; 33 | } 34 | 35 | function setGreeting(string memory _greeting) public { 36 | 37 | emit LogEntry(msg.sender, 38 | _msgSender(), 39 | trustedForwarder(), 40 | lastGreeter, 41 | msg.data ); 42 | 43 | greeting = _greeting; 44 | lastGreeter = _msgSender(); 45 | } 46 | 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /ecosystem/opengsn/contracts/SingleRecipientPaymaster.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "@opengsn/contracts/src/BasePaymaster.sol"; 6 | 7 | /** 8 | * a paymaster for a single recipient contract. 9 | * - reject requests if destination is not the target contract. 10 | * - reject any request if the target contract reverts. 11 | */ 12 | contract SingleRecipientPaymaster is BasePaymaster { 13 | 14 | address public target; 15 | 16 | event TargetChanged(address oldTarget, address newTarget); 17 | 18 | constructor(address _target) { 19 | target=_target; 20 | } 21 | 22 | function versionPaymaster() external view override virtual returns (string memory){ 23 | return "2.2.0+opengsn.recipient.ipaymaster"; 24 | } 25 | 26 | function setTarget(address _target) external onlyOwner { 27 | emit TargetChanged(target, _target); 28 | target=_target; 29 | } 30 | 31 | function preRelayedCall( 32 | GsnTypes.RelayRequest calldata relayRequest, 33 | bytes calldata signature, 34 | bytes calldata approvalData, 35 | uint256 maxPossibleGas 36 | ) 37 | external 38 | override 39 | virtual 40 | returns (bytes memory context, bool revertOnRecipientRevert) { 41 | (relayRequest, signature, approvalData, maxPossibleGas); 42 | require(relayRequest.request.to==target, "wrong target"); 43 | //returning "true" means this paymaster accepts all requests that 44 | // are not rejected by the recipient contract. 45 | return ("", true); 46 | } 47 | 48 | function postRelayedCall( 49 | bytes calldata context, 50 | bool success, 51 | uint256 gasUseWithoutPost, 52 | GsnTypes.RelayData calldata relayData 53 | ) external override virtual { 54 | (context, success, gasUseWithoutPost, relayData); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ecosystem/opengsn/hardhat.config.js: -------------------------------------------------------------------------------- 1 | // Plugins 2 | require('@nomiclabs/hardhat-ethers') 3 | 4 | // No need for a real mnemonic when you send free transactions 5 | junkMnemonic = 'test test test test test test test test test test test junk' 6 | 7 | 8 | module.exports = { 9 | networks: { 10 | hardhat: { 11 | accounts: { 12 | mnemonic: junkMnemonic 13 | } 14 | }, 15 | 'optimistic-kovan-orig': { 16 | chainId: 69, 17 | url: 'https://kovan.optimism.io', 18 | accounts: { 19 | mnemonic: junkMnemonic 20 | } 21 | }, 22 | 'optimistic-mainnet': { 23 | chainId: 10, 24 | url: 'https://mainnet.optimism.io', 25 | accounts: { 26 | mnemonic: junkMnemonic 27 | } 28 | }, 29 | }, 30 | solidity: '0.8.9', 31 | } 32 | -------------------------------------------------------------------------------- /ecosystem/opengsn/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-project", 3 | "devDependencies": { 4 | "@nomiclabs/hardhat-ethers": "^2.0.5", 5 | "@nomiclabs/hardhat-waffle": "^2.0.3", 6 | "chai": "^4.3.6", 7 | "ethereum-waffle": "^3.4.4", 8 | "ethers": "^5.6.1", 9 | "hardhat": "^2.9.1" 10 | }, 11 | "dependencies": { 12 | "@opengsn/contracts": "^2.2.6", 13 | "@opengsn/provider": "^2.2.6", 14 | "dotenv": "^16.0.0", 15 | "web3-eth-contract": "^1.7.1", 16 | "web3-providers-http": "^1.7.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ecosystem/tenderly/.env.example: -------------------------------------------------------------------------------- 1 | # Put the mnemonic for an account on Optimism here 2 | MNEMONIC=test test test test test test test test test test test junk 3 | 4 | # API KEY for Alchemy 5 | ALCHEMY_API_KEY= 6 | 7 | # URL to access Optimism Goerli (if not using Alchemy) 8 | OPTIMISM_GOERLI_URL= 9 | -------------------------------------------------------------------------------- /ecosystem/tenderly/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | #Hardhat files 4 | cache 5 | artifacts 6 | -------------------------------------------------------------------------------- /ecosystem/tenderly/README.md: -------------------------------------------------------------------------------- 1 | # Using Tenderly to Trace Transactions 2 | [![Discord](https://img.shields.io/discord/667044843901681675.svg?color=768AD4&label=discord&logo=https%3A%2F%2Fdiscordapp.com%2Fassets%2F8c9701b98ad4372b58f13fd9f65f966e.svg)](https://discord-gateway.optimism.io) 3 | [![Twitter Follow](https://img.shields.io/twitter/follow/optimismFND.svg?label=optimismFND&style=social)](https://twitter.com/optimismFND) 4 | 5 | OP Mainnet and OP Goerli transactions sometimes fail. 6 | In this tutorial you learn how to use [Tenderly](https://tenderly.co/) to see what happened, what is the cause of the failure. 7 | 8 | ## Configuration 9 | 10 | The contract we'll use as a sample for debugging is [Fail.sol](contracts/Fail.sol). 11 | 12 | As samples, we have two transactions using this contract: 13 | 14 | - [A successful transaction](https://goerli-optimism.etherscan.io/tx/0x86b4fc03c1656b64206cdedd1ef840563c1aa32a7ab7c2ca4c2dcb205752848d) 15 | - [A failed transaction](https://goerli-optimism.etherscan.io/tx/0x9af716b7ef0ab4b4af9fbf95a7aa3f401140b07e055674c449299e18f918b938) 16 | 17 | ## Tenderly 18 | 19 | Get a [Tenderly account](https://dashboard.tenderly.co/register?utm_source=homepage) if you don't have an account already. 20 | The free tier is sufficient for what we do in this tutorial. 21 | 22 | ## Tracing a transaction 23 | 24 | 1. Search for the transaction hash. 25 | If Tenderly has that transaction, it will find it and tell you on what network it happened. 26 | For the purpose of this tutorial, search for the failed transaction (`0x9af716b7ef0ab4b4af9fbf95a7aa3f401140b07e055674c449299e18f918b938`). 27 | 28 | If the source code isn't in Tenderly, it can download it from Etherscan: 29 | 30 | 1. Click the **Debugger** on the left sidebar. 31 | 32 | 1. Click in the function trace on a contract where you don't have the source code yet. 33 | 34 | 1. Click **Fetch the contract from public explorer**. 35 | Note that sometimes it takes a few minutes to fetch the source code. 36 | 37 | 1. In the overview you can see the stack trace. 38 | The different icons stand for different contract addresses (all implementing the same `Fail` contract in this case). 39 | 40 | ![stack trace](assets/stack-trace.png) 41 | 42 | 1. Below that you can see the function trace, including where exactly in the source the failure was and what happened before that. 43 | 44 | ![function trace](assets/func-trace.png) 45 | 46 | Because of the way we called `cheapFail` when it reverts it causes the entire transaction to revert. 47 | There is a way to call subcontracts without propagating revert, [see here for directions](https://stackoverflow.com/questions/72102722/can-transaction-fail-but-the-calling-contract-will-think-it-was-successful). 48 | 49 | ## Using the debugger 50 | 51 | 1. Trace the successful transaction (`0x86b4fc03c1656b64206cdedd1ef840563c1aa32a7ab7c2ca4c2dcb205752848d`). 52 | 53 | 1. Select **State Changes** on the left bar to see the state changes caused by the transaction. 54 | 55 | ![state changes](assets/state-changes.png) 56 | 57 | 1. Select **Debugger** on the left bar to see the function trace (which functions ran, which functions called which, etc.), the stack trace (the current funtion and who called it), the source code, and the 58 | 59 | ![debugger](assets/debugger.png) 60 | -------------------------------------------------------------------------------- /ecosystem/tenderly/assets/debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum-optimism/optimism-tutorial/5cf1f84e4c55dcff50469af3f89a05d82b9f6681/ecosystem/tenderly/assets/debugger.png -------------------------------------------------------------------------------- /ecosystem/tenderly/assets/func-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum-optimism/optimism-tutorial/5cf1f84e4c55dcff50469af3f89a05d82b9f6681/ecosystem/tenderly/assets/func-trace.png -------------------------------------------------------------------------------- /ecosystem/tenderly/assets/stack-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum-optimism/optimism-tutorial/5cf1f84e4c55dcff50469af3f89a05d82b9f6681/ecosystem/tenderly/assets/stack-trace.png -------------------------------------------------------------------------------- /ecosystem/tenderly/assets/state-changes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum-optimism/optimism-tutorial/5cf1f84e4c55dcff50469af3f89a05d82b9f6681/ecosystem/tenderly/assets/state-changes.png -------------------------------------------------------------------------------- /ecosystem/tenderly/contracts/Fail.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | // Fail in various interesting ways to demonstrate how to use tenderly 5 | // to identify failures. 6 | contract Fail { 7 | uint counter = 0; 8 | uint uselessData = 0; 9 | 10 | Fail next; 11 | 12 | constructor(Fail nextFail) { 13 | next = nextFail; 14 | } 15 | 16 | function useless() private { 17 | for(uint i=0; i<10; i++) 18 | uselessData += i; 19 | } 20 | 21 | function alsoUseless() private { 22 | for(uint i=0; i<10; i++) 23 | uselessData += i; 24 | } 25 | 26 | function success() public returns (uint) { 27 | useless(); 28 | counter++; 29 | if (address(next) != address(0x00)) { 30 | next.success(); 31 | alsoUseless(); 32 | } 33 | 34 | return counter; 35 | } 36 | 37 | function cheapFail() public returns (uint) { 38 | useless(); 39 | counter++; 40 | if (address(next) != address(0x00)) { 41 | next.cheapFail(); 42 | alsoUseless(); 43 | } 44 | 45 | require(false, "Failure, but a cheap one"); 46 | 47 | return counter; 48 | } 49 | 50 | function expensiveFail() public returns (uint) { 51 | useless(); 52 | counter++; 53 | if (address(next) != address(0x00)) { 54 | next.expensiveFail(); 55 | alsoUseless(); 56 | } 57 | 58 | require(false, "Failure, costing all of your gas"); 59 | 60 | return counter; 61 | } 62 | 63 | function complex() public { 64 | useless(); 65 | next.success(); 66 | alsoUseless(); 67 | next.cheapFail(); 68 | alsoUseless(); 69 | } // function complex 70 | } // contract Fail 71 | -------------------------------------------------------------------------------- /ecosystem/tenderly/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomiclabs/hardhat-waffle"); 2 | require('dotenv').config() 3 | 4 | // This is a sample Hardhat task. To learn how to create your own go to 5 | // https://hardhat.org/guides/create-task.html 6 | task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { 7 | const accounts = await hre.ethers.getSigners(); 8 | 9 | for (const account of accounts) { 10 | console.log(account.address); 11 | } 12 | }); 13 | 14 | // You need to export an object to set up your config 15 | // Go to https://hardhat.org/config/ to learn more 16 | 17 | /** 18 | * @type import('hardhat/config').HardhatUserConfig 19 | */ 20 | 21 | const optimismGoerliUrl = 22 | process.env.ALCHEMY_API_KEY ? 23 | `https://opt-goerli.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}` : 24 | process.env.OPTIMISM_GOERLI_URL 25 | 26 | const words = process.env.MNEMONIC.match(/[a-zA-Z]+/g).length 27 | validLength = [12, 15, 18, 24] 28 | if (!validLength.includes(words)) { 29 | console.log(`The mnemonic (${process.env.MNEMONIC}) is the wrong number of words`) 30 | process.exit(-1) 31 | } 32 | 33 | module.exports = { 34 | solidity: "0.8.13", 35 | networks: { 36 | "local-devnode": { 37 | url: "http://localhost:8545", 38 | accounts: { mnemonic: "test test test test test test test test test test test junk" } 39 | }, 40 | "optimism-goerli": { 41 | url: optimismGoerliUrl, 42 | accounts: { mnemonic: process.env.MNEMONIC } 43 | }, 44 | "optimism-bedrock": { 45 | url: 'https://bedrock-beta-1-replica-0.optimism.io/', 46 | accounts: { mnemonic: process.env.MNEMONIC } 47 | } 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /ecosystem/tenderly/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-project", 3 | "devDependencies": { 4 | "@nomiclabs/hardhat-ethers": "^2.0.4", 5 | "@nomiclabs/hardhat-waffle": "^2.0.2", 6 | "chai": "^4.3.6", 7 | "ethereum-waffle": "^3.4.0", 8 | "ethers": "^5.5.4", 9 | "hardhat": "^2.8.3" 10 | }, 11 | "scripts": { 12 | "compile": "yarn hardhat compile", 13 | "test": "yarn hardhat test" 14 | }, 15 | "dependencies": { 16 | "dotenv": "^16.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ecosystem/tenderly/scripts/sample-script.js: -------------------------------------------------------------------------------- 1 | // We require the Hardhat Runtime Environment explicitly here. This is optional 2 | // but useful for running the script in a standalone fashion through `node