From d4a42939e17d16ab9d0336f6a8d11942c74b3aae Mon Sep 17 00:00:00 2001 From: Kevin Ho Date: Sun, 4 Oct 2020 14:10:44 -0400 Subject: [PATCH 01/10] working watcher --- packages/ovm-toolchain/src/index.ts | 1 + packages/ovm-toolchain/src/watcher.ts | 68 +++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 packages/ovm-toolchain/src/watcher.ts diff --git a/packages/ovm-toolchain/src/index.ts b/packages/ovm-toolchain/src/index.ts index c459e1ce7c202..06c06a454cafa 100644 --- a/packages/ovm-toolchain/src/index.ts +++ b/packages/ovm-toolchain/src/index.ts @@ -1,3 +1,4 @@ export * from './ganache' export * from './waffle' export * from './x-domain-utils' +export * from './watcher' diff --git a/packages/ovm-toolchain/src/watcher.ts b/packages/ovm-toolchain/src/watcher.ts new file mode 100644 index 0000000000000..7f2f8941c0d5c --- /dev/null +++ b/packages/ovm-toolchain/src/watcher.ts @@ -0,0 +1,68 @@ +/* External Imports */ +import { ethers } from 'ethers-v4' + +interface Layer { + provider: any + messengerAddress: string +} + +interface WatcherOptions { + l1: Layer + l2: Layer +} + +export class Watcher { + public l1: Layer + public l2: Layer + + constructor(opts: WatcherOptions) { + this.l1 = opts.l1 + this.l2 = opts.l2 + } + + public async getMessageHashesFromL1Tx(l1TxHash: string): Promise { + return this._getMessageHashesFromTx(true, l1TxHash) + } + public async getMessageHashesFromL2Tx(l2TxHash: string): Promise { + return this._getMessageHashesFromTx(false, l2TxHash) + } + + public onceL2Relay(msgHash: string, callback: Function) { + return this._onceRelay(false, msgHash, callback) + } + + public onceL1Relay(msgHash: string, callback: Function) { + return this._onceRelay(true, msgHash, callback) + } + + private async _getMessageHashesFromTx( + isL1: boolean, + txHash: string + ): Promise { + const layer = isL1 ? this.l1 : this.l2 + const l1Receipt = await layer.provider.getTransactionReceipt(txHash) + const filtered = l1Receipt.logs.filter((log: any) => { + return ( + log.address === layer.messengerAddress && + log.topics[0] === ethers.utils.id('SentMessage(bytes32)') + ) + }) + return filtered.map((log: any) => log.data) + } + + private _onceRelay(isL1: boolean, msgHash: string, callback: Function) { + const layer = isL1 ? this.l1 : this.l2 + const filter = { + address: layer.messengerAddress, + topics: [ + ethers.utils.id(`Relayed${isL1 ? 'L2ToL1' : 'L1ToL2'}Message(bytes32)`), + ], + } + + layer.provider.on(filter, (log: any) => { + if (log.data === msgHash) { + callback(log.transactionHash) + } + }) + } +} From ba4b38bedb16931ca8b308ab003406fae810b4d0 Mon Sep 17 00:00:00 2001 From: Kevin Ho Date: Tue, 6 Oct 2020 22:56:25 -0400 Subject: [PATCH 02/10] add ovm compilation to build script --- packages/contracts/buidler.config.ts | 15 ++++++++++++++- packages/contracts/package.json | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/contracts/buidler.config.ts b/packages/contracts/buidler.config.ts index 6dffc0757a136..c9449f34ae630 100644 --- a/packages/contracts/buidler.config.ts +++ b/packages/contracts/buidler.config.ts @@ -1,4 +1,5 @@ -import { usePlugin, BuidlerConfig } from '@nomiclabs/buidler/config' +import * as path from 'path' +import { usePlugin, BuidlerConfig, task } from '@nomiclabs/buidler/config' import { DEFAULT_ACCOUNTS_BUIDLER, @@ -27,6 +28,18 @@ const parseSolppFlags = (): { [flag: string]: boolean } => { return flags } +task('compile') + .addFlag('ovm', 'Compile using OVM solc compiler') + .setAction(async (taskArguments, bre: any, runSuper) => { + if (taskArguments.ovm) { + bre.config.solc = { + path: path.resolve(__dirname, '../../node_modules/@eth-optimism/solc'), + } + bre.config.paths.artifacts = './build/ovm_artifacts' + } + await runSuper(taskArguments) + }) + const config: BuidlerConfig = { networks: { buidlerevm: { diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 030ce063a5ca6..78c8a7703ddb3 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -27,8 +27,9 @@ "test:contracts": "cross-env SOLPP_FLAGS=\"FLAG_IS_TEST,FLAG_IS_DEBUG\" buidler test --show-stack-traces", "coverage": "yarn run coverage:contracts", "coverage:contracts": "cross-env SOLPP_FLAGS=\"FLAG_IS_TEST\" buidler coverage --network coverage --show-stack-traces --testfiles \"test/contracts/**/*.spec.ts\"", - "build": "yarn run build:contracts && yarn run build:typescript && yarn run build:copy", + "build": "yarn run build:contracts && yarn run buidler compile --ovm && yarn run build:typescript && yarn run build:copy", "build:contracts": "buidler compile", + "build:contracts:ovm": "buidler compile --ovm", "build:typescript": "tsc -p .", "build:copy": "yarn run build:copy:contracts", "build:copy:contracts": "copyfiles -u 2 \"contracts/optimistic-ethereum/**/*.sol\" \"build/contracts\"", From 0a559fdf1cb9d86d019ec2e30b7560f804be1be8 Mon Sep 17 00:00:00 2001 From: Kevin Ho Date: Wed, 7 Oct 2020 20:50:11 -0400 Subject: [PATCH 03/10] add temp inits, win stupid prizes --- .../bridge/L1CrossDomainMessenger.sol | 12 ++++++++- .../bridge/L2CrossDomainMessenger.sol | 14 +++++++--- .../queue/L1ToL2TransactionQueue.sol | 9 +++++++ packages/contracts/package.json | 5 ++-- yarn.lock | 26 ------------------- 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/packages/contracts/contracts/optimistic-ethereum/bridge/L1CrossDomainMessenger.sol b/packages/contracts/contracts/optimistic-ethereum/bridge/L1CrossDomainMessenger.sol index d168fa5750682..0fcd3cc3ce891 100644 --- a/packages/contracts/contracts/optimistic-ethereum/bridge/L1CrossDomainMessenger.sol +++ b/packages/contracts/contracts/optimistic-ethereum/bridge/L1CrossDomainMessenger.sol @@ -19,6 +19,8 @@ contract L1CrossDomainMessenger is BaseCrossDomainMessenger, ContractResolver { event RelayedL2ToL1Message(bytes32 msgHash); + public address l1ToL2QueueAddress; + /* * Data Structures */ @@ -44,6 +46,11 @@ contract L1CrossDomainMessenger is BaseCrossDomainMessenger, ContractResolver { ContractResolver(_addressResolver) {} + function tempInit(address _l1ToL2QueueAddress) public { + require(l1ToL2QueueAddress == address(0)); + l1ToL2QueueAddress = _l1ToL2QueueAddress; + } + /* * Public Functions @@ -236,7 +243,10 @@ contract L1CrossDomainMessenger is BaseCrossDomainMessenger, ContractResolver { view returns (L1ToL2TransactionQueue) { - return L1ToL2TransactionQueue(resolveContract("L1ToL2TransactionQueue")); + if (l1ToL2QueueAddress == address(0)) { + return L1ToL2TransactionQueue(resolveContract("L1ToL2TransactionQueue")); + } + return L1ToL2TransactionQueue(l1ToL2QueueAddress); } function resolveStateCommitmentChain() diff --git a/packages/contracts/contracts/optimistic-ethereum/bridge/L2CrossDomainMessenger.sol b/packages/contracts/contracts/optimistic-ethereum/bridge/L2CrossDomainMessenger.sol index 1dc0e69358a60..5192141c1c472 100644 --- a/packages/contracts/contracts/optimistic-ethereum/bridge/L2CrossDomainMessenger.sol +++ b/packages/contracts/contracts/optimistic-ethereum/bridge/L2CrossDomainMessenger.sol @@ -21,6 +21,8 @@ contract L2CrossDomainMessenger is BaseCrossDomainMessenger { address private l1MessageSenderPrecompileAddress; address private l2ToL1MessagePasserPrecompileAddress; + address public authenticatedAddress; + /* * Constructor */ @@ -102,9 +104,15 @@ contract L2CrossDomainMessenger is BaseCrossDomainMessenger { bool ) { - IL1MessageSender l1MessageSenderPrecompile = IL1MessageSender(l1MessageSenderPrecompileAddress); - address l1MessageSenderAddress = l1MessageSenderPrecompile.getL1MessageSender(); - return l1MessageSenderAddress == targetMessengerAddress; + // IL1MessageSender l1MessageSenderPrecompile = IL1MessageSender(l1MessageSenderPrecompileAddress); + // address l1MessageSenderAddress = l1MessageSenderPrecompile.getL1MessageSender(); + // return l1MessageSenderAddress == targetMessengerAddress; + return msg.sender == authenticatedAddress || authenticatedAddress == address(0); + } + + function tempInit(address _authenticatedAddress) public { + require(authenticatedAddress == address(0)); + authenticatedAddress = _authenticatedAddress; } /** diff --git a/packages/contracts/contracts/optimistic-ethereum/queue/L1ToL2TransactionQueue.sol b/packages/contracts/contracts/optimistic-ethereum/queue/L1ToL2TransactionQueue.sol index e6251870b7191..e8537aa944b54 100644 --- a/packages/contracts/contracts/optimistic-ethereum/queue/L1ToL2TransactionQueue.sol +++ b/packages/contracts/contracts/optimistic-ethereum/queue/L1ToL2TransactionQueue.sol @@ -30,6 +30,8 @@ contract L1ToL2TransactionQueue is ContractResolver, RollupQueue { uint constant public L2_GAS_DISCOUNT_DIVISOR = 10; + address public l1MessengerAddress; + /* * Constructor */ @@ -49,6 +51,11 @@ contract L1ToL2TransactionQueue is ContractResolver, RollupQueue { * Public Functions */ + function tempInit(address _l1MessengerAddress) public { + require(l1MessengerAddress == address(0)); + l1MessengerAddress = _l1MessengerAddress; + } + /** * Checks that that a dequeue is authenticated, and dequques if authenticated. */ @@ -69,6 +76,8 @@ contract L1ToL2TransactionQueue is ContractResolver, RollupQueue { ) external { + require(l1MessengerAddress == address(0) || msg.sender == l1MessengerAddress); + uint gasToBurn = _ovmGasLimit / L2_GAS_DISCOUNT_DIVISOR; resolveGasConsumer().consumeGasInternalCall(gasToBurn); diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 78c8a7703ddb3..6a6a7a3a2203c 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,12 +1,13 @@ { "name": "@eth-optimism/rollup-contracts", "private": true, - "version": "0.0.1-alpha.33", + "version": "0.0.1-alpha.35", "main": "build/index.js", "files": [ "build/**/*.js", "build/contracts/*", - "build/artifacts/*json" + "build/artifacts/*json", + "build/ovm_artifacts/*json" ], "license": "MIT", "workspaces": { diff --git a/yarn.lock b/yarn.lock index 673cb53a52ea1..807577a30d4ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5410,32 +5410,6 @@ ethereumjs-common@^1.1.0, ethereumjs-common@^1.3.2, ethereumjs-common@^1.5.0: util.promisify "^1.0.0" uuid "^8.3.0" -"ethereumjs-ovm@git+https://github.com/ethereum-optimism/ethereumjs-vm.git": - version "4.2.0" - uid "02ba6e77a88339b04a053b5e653f644acb1555b8" - resolved "git+https://github.com/ethereum-optimism/ethereumjs-vm.git#02ba6e77a88339b04a053b5e653f644acb1555b8" - dependencies: - "@types/debug" "^4.1.5" - "@types/uuid" "^8.3.0" - async "^2.1.2" - async-eventemitter "^0.2.2" - core-js-pure "^3.0.1" - debug "^4.1.1" - ethereumjs-account "^3.0.0" - ethereumjs-block "^2.2.2" - ethereumjs-blockchain "^4.0.3" - ethereumjs-common "^1.5.0" - ethereumjs-tx "^2.1.2" - ethereumjs-util "^6.2.0" - ethers "^5.0.0" - fake-merkle-patricia-tree "^1.0.1" - functional-red-black-tree "^1.0.1" - merkle-patricia-tree "^2.3.2" - rustbn.js "~0.2.0" - safe-buffer "^5.1.1" - util.promisify "^1.0.0" - uuid "^8.3.0" - ethereumjs-tx@1.3.7, ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.3: version "1.3.7" resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz#88323a2d875b10549b8347e09f4862b546f3d89a" From 13c90cce42524dba7a7c6ca9c9fea6bd742124db Mon Sep 17 00:00:00 2001 From: Kevin Ho Date: Wed, 7 Oct 2020 20:57:12 -0400 Subject: [PATCH 04/10] fix typo --- .../optimistic-ethereum/bridge/L1CrossDomainMessenger.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/contracts/optimistic-ethereum/bridge/L1CrossDomainMessenger.sol b/packages/contracts/contracts/optimistic-ethereum/bridge/L1CrossDomainMessenger.sol index 0fcd3cc3ce891..7460a103421c1 100644 --- a/packages/contracts/contracts/optimistic-ethereum/bridge/L1CrossDomainMessenger.sol +++ b/packages/contracts/contracts/optimistic-ethereum/bridge/L1CrossDomainMessenger.sol @@ -19,7 +19,7 @@ contract L1CrossDomainMessenger is BaseCrossDomainMessenger, ContractResolver { event RelayedL2ToL1Message(bytes32 msgHash); - public address l1ToL2QueueAddress; + address public l1ToL2QueueAddress; /* * Data Structures From 05152c3cb65bdcc6a769b54b2d8e503207ab7f1c Mon Sep 17 00:00:00 2001 From: Kevin Ho Date: Wed, 7 Oct 2020 20:57:29 -0400 Subject: [PATCH 05/10] move ovm build to prepublish script --- packages/contracts/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 6a6a7a3a2203c..7912295506fd2 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -28,7 +28,7 @@ "test:contracts": "cross-env SOLPP_FLAGS=\"FLAG_IS_TEST,FLAG_IS_DEBUG\" buidler test --show-stack-traces", "coverage": "yarn run coverage:contracts", "coverage:contracts": "cross-env SOLPP_FLAGS=\"FLAG_IS_TEST\" buidler coverage --network coverage --show-stack-traces --testfiles \"test/contracts/**/*.spec.ts\"", - "build": "yarn run build:contracts && yarn run buidler compile --ovm && yarn run build:typescript && yarn run build:copy", + "build": "yarn run build:contracts && yarn run build:typescript && yarn run build:copy", "build:contracts": "buidler compile", "build:contracts:ovm": "buidler compile --ovm", "build:typescript": "tsc -p .", @@ -39,7 +39,8 @@ "lint:typescript": "tslint --format stylish --project .", "fix": "yarn run fix:typescript", "fix:typescript": "prettier --config ../../prettier-config.json --write \"index.ts\" \"buidler.config.ts\" \"{src,test,plugins}/**/*.ts\"", - "deploy:all": "env DEBUG=\"info:*,error:*,debug:*\" node ./build/src/exec/deploy-contracts.js" + "deploy:all": "env DEBUG=\"info:*,error:*,debug:*\" node ./build/src/exec/deploy-contracts.js", + "prepublish": "yarn run build && yarn run build:contracts:ovm" }, "dependencies": { "@ethersproject/keccak256": "5.0.3", From 4c4bcac0d1c8077384e14662dc9135949d8c6618 Mon Sep 17 00:00:00 2001 From: Kevin Ho Date: Wed, 7 Oct 2020 20:59:04 -0400 Subject: [PATCH 06/10] add return types to watcher --- packages/ovm-toolchain/src/watcher.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ovm-toolchain/src/watcher.ts b/packages/ovm-toolchain/src/watcher.ts index 7f2f8941c0d5c..8bbdec8a845af 100644 --- a/packages/ovm-toolchain/src/watcher.ts +++ b/packages/ovm-toolchain/src/watcher.ts @@ -27,11 +27,11 @@ export class Watcher { return this._getMessageHashesFromTx(false, l2TxHash) } - public onceL2Relay(msgHash: string, callback: Function) { + public onceL2Relay(msgHash: string, callback: Function): void { return this._onceRelay(false, msgHash, callback) } - public onceL1Relay(msgHash: string, callback: Function) { + public onceL1Relay(msgHash: string, callback: Function): void { return this._onceRelay(true, msgHash, callback) } From 99bc69ba20ff06bbded0d82909f907fbc09cd30e Mon Sep 17 00:00:00 2001 From: Kevin Ho Date: Wed, 7 Oct 2020 21:03:17 -0400 Subject: [PATCH 07/10] publish new version --- packages/ovm-toolchain/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ovm-toolchain/package.json b/packages/ovm-toolchain/package.json index 40e9ef6520866..6a838a0982518 100644 --- a/packages/ovm-toolchain/package.json +++ b/packages/ovm-toolchain/package.json @@ -1,6 +1,6 @@ { "name": "@eth-optimism/ovm-toolchain", - "version": "0.0.1-alpha.7", + "version": "0.0.1-alpha.8", "description": "Wrappers for Ethereum dev tools", "private": true, "main": "build/index.js", From 1c3a5b2f0af06e5811d25ac7a32d1aa5b2208f1d Mon Sep 17 00:00:00 2001 From: Kevin Ho Date: Wed, 7 Oct 2020 21:16:27 -0400 Subject: [PATCH 08/10] add Watcher readme --- packages/ovm-toolchain/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/ovm-toolchain/README.md b/packages/ovm-toolchain/README.md index 87a80dbb7a6a7..f2eaf71f1be27 100644 --- a/packages/ovm-toolchain/README.md +++ b/packages/ovm-toolchain/README.md @@ -86,4 +86,32 @@ const config = { } export default config +``` + +#### Watcher + +Our `Watcher` allows you to get the L1->L2 and L2->L1 message hashes from a transaction hash on the origin layer and then trigger a callback with a when the message is relayed on its destination layer. `getMessageHashesFromL1Tx` will return an array of the message hashes of all of the L1->L2 messages that were sent inside of that L1 tx (This will usually just be a single element array). `getMessageHashesFromL2Tx` does the same for L2->L1 messages. `onceL2Relay` takes in an L1->L2 message hash and a callback that will be triggered after 2-5 minutes with the hash of the L2 tx that the message ends up being relayed in. `onceL1Relay` does the same for L2->L1 messages, except the delay is 7 days. + +```typescript +import { Watcher } from '@eth-optimism/ovm-toolchain/' +import { JsonRpcProvider } from 'ethers/providers' + +const watcher = new Watcher({ + l1: { + provider: new JsonRpcProvider('INFURA_L1_URL'), + messengerAddress: '0x...' + }, + l2: { + provider: new JsonRpcProvider('OPTIMISM_L2_URL'), + messengerAddress: '0x...' + } +}) +const l1TxHash = (await depositContract.deposit(100)).hash +const [messageHash] = await watcher.getMessageHashesFromL1Tx(l1TxHash) +console.log('L1->L2 message hash:', messageHash) +watcher.onceL2Relay(messageHash, (l2txhash) => { + // Takes 2-5 minutes + console.log('Got L2 Tx Hash:', l2txhash) +}) + ``` \ No newline at end of file From ffb18af27a1d0b167618db8fa8e87b2acbbe6bf0 Mon Sep 17 00:00:00 2001 From: Kevin Ho Date: Wed, 7 Oct 2020 22:04:41 -0400 Subject: [PATCH 09/10] allow L1MessageSender in L2XDomainMsgr --- .../bridge/L2CrossDomainMessenger.sol | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/contracts/contracts/optimistic-ethereum/bridge/L2CrossDomainMessenger.sol b/packages/contracts/contracts/optimistic-ethereum/bridge/L2CrossDomainMessenger.sol index 5192141c1c472..4d8742f8a1bec 100644 --- a/packages/contracts/contracts/optimistic-ethereum/bridge/L2CrossDomainMessenger.sol +++ b/packages/contracts/contracts/optimistic-ethereum/bridge/L2CrossDomainMessenger.sol @@ -104,10 +104,12 @@ contract L2CrossDomainMessenger is BaseCrossDomainMessenger { bool ) { - // IL1MessageSender l1MessageSenderPrecompile = IL1MessageSender(l1MessageSenderPrecompileAddress); - // address l1MessageSenderAddress = l1MessageSenderPrecompile.getL1MessageSender(); - // return l1MessageSenderAddress == targetMessengerAddress; - return msg.sender == authenticatedAddress || authenticatedAddress == address(0); + if (authenticatedAddress == address(0)) { + IL1MessageSender l1MessageSenderPrecompile = IL1MessageSender(l1MessageSenderPrecompileAddress); + address l1MessageSenderAddress = l1MessageSenderPrecompile.getL1MessageSender(); + return l1MessageSenderAddress == targetMessengerAddress; + } + return msg.sender == authenticatedAddress; } function tempInit(address _authenticatedAddress) public { From 495b351279969d97d09cabe8f9ec66cef2be5ccf Mon Sep 17 00:00:00 2001 From: Kevin Ho Date: Wed, 7 Oct 2020 22:04:56 -0400 Subject: [PATCH 10/10] clean up toolchain README --- packages/ovm-toolchain/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/ovm-toolchain/README.md b/packages/ovm-toolchain/README.md index f2eaf71f1be27..8cc701eeec3f1 100644 --- a/packages/ovm-toolchain/README.md +++ b/packages/ovm-toolchain/README.md @@ -89,8 +89,7 @@ export default config ``` #### Watcher - -Our `Watcher` allows you to get the L1->L2 and L2->L1 message hashes from a transaction hash on the origin layer and then trigger a callback with a when the message is relayed on its destination layer. `getMessageHashesFromL1Tx` will return an array of the message hashes of all of the L1->L2 messages that were sent inside of that L1 tx (This will usually just be a single element array). `getMessageHashesFromL2Tx` does the same for L2->L1 messages. `onceL2Relay` takes in an L1->L2 message hash and a callback that will be triggered after 2-5 minutes with the hash of the L2 tx that the message ends up being relayed in. `onceL1Relay` does the same for L2->L1 messages, except the delay is 7 days. +Our `Watcher` allows you to retrieve all transaction hashes related to cross domain messages such as deposits and withdrawals. In order to use, first send a transaction which sends a cross domain message, for example a deposit from L1 into L2. After sending the deposit transaction and storing the transaction hash, use `getMessageHashesFromL1Tx(l1TxHash)` to get an array of the message hashes of all of the L1->L2 messages that were sent inside of that L1 tx (This will usually just be a single element array, but it can return multiple if one L1 transaction triggers multiple deposits). `getMessageHashesFromL2Tx(l2TxHash)` does the same for L2->L1 messages. `onceL2Relay(messageHash, callback)` takes in an L1->L2 message hash and a callback that will be triggered after 2-5 minutes with the hash of the L2 tx that the message ends up getting relayed in. `onceL1Relay(messageHash, callback)` does the same for L2->L1 messages, except the delay is 7 days. ```typescript import { Watcher } from '@eth-optimism/ovm-toolchain/' @@ -113,5 +112,4 @@ watcher.onceL2Relay(messageHash, (l2txhash) => { // Takes 2-5 minutes console.log('Got L2 Tx Hash:', l2txhash) }) - ``` \ No newline at end of file