Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contracts/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
PRIVATE_KEY='<0x encoded private key>'
ALCHEMY_API_KEY=''
INFURA_API_KEY='<key>'
ETHERSCAN_API_KEY='<key>'
GNOSISSCAN_API_KEY='<key>'
Expand Down
97 changes: 97 additions & 0 deletions contracts/docs/Bridging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Manual Actions for L2 → L1 Bridging
For Reality Cross-chain proxies L1 → L2 bridging is automatic for every bridge.
However, L2 → L1 bridging requires manual steps, which differ by chain.
Use this guide until bots are configured to handle everything automatically.


## Gnosis
After sending a transaction from L2 (e.g., `handleNotifiedRequest`), refer to this page:
https://docs.gnosischain.com/bridges/using-amb

It's recommended to use **Blockscout** instead of GnosisScan due to possible encoding issues.

Before using `getSignatures` on the `AMBHelper` contract, wait until the message is processed
(usually within an hour). After that, `getSignatures` will return a result you can pass into
`executeSignatures`.


## Polygon
After sending the L2 transaction you'll need to manually call `receiveMessage`
on `foreignProxy` contract.

The argument for the function can be obtained from this template URL (usually generated in **1–3 hours**):

```
https://proof-generator.polygon.technology/api/v1/matic/exit-payload/<your_L2_tx_hash>?eventSignature=0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036
```

This URL contains:
- Your L2 tx hash that you need to manually insert
- The event signature required by Polygon API (same for every tx, belongs to `messageSent` event)

More details:
https://github.com/0xPolygon/fx-portal?tab=readme-ov-file#proof-generation


## zkSync
After sending the tx to L1, check its status on zkSync Etherscan.
When the status becomes **"Executed"** (usually in 2-4 hours), use the script:

https://github.com/kleros/cross-chain-realitio-proxy/blob/master/contracts/scripts/execute_proof.js

Steps:
1. Insert your `txHash` into the script.
2. Run `yarn zksync:proof:production` from `contracts` folder.

This script retrieves the proof and executes the tx on L1 automatically.

Requirements:
- `yarn install`
- `.env` file setup (`PRIVATE_KEY`, `INFURA_API_KEY`)

zkSync docs:
https://code.zksync.io/tutorials/how-to-send-l2-l1-message

If the status is "Executed" but proof is `null`, wait longer.
Once executed successfully, a dispute will be created on KlerosCourt with `ForeignProxy` as arbitrable.


## Arbitrum
Execution occurs only after the **one-week challenge period** has passed, thus a week after sending a tx to L1, navigate to `contracts` folder and run:

```
yarn relay:production --txhash <your_tx_hash>
```

Requirements:
- `yarn install`
- `.env` file setup (`PRIVATE_KEY`, `INFURA_API_KEY`)

Note: this task currently works only with **ethers v5**

Official docs page:
https://docs.arbitrum.io/build-decentralized-apps/cross-chain-messaging
https://github.com/OffchainLabs/arbitrum-tutorials/blob/master/packages/outbox-execute/scripts/exec.js


## Optimism (Base, Redstone, Unichain, etc.)
After sending the L2 tx run the command corresponding to the chosen chain, e.g.:

```
yarn relay-op:base --txhash <your_tx_hash>
```

You must run this command **twice**:
1. Shortly after sending the tx (usually within an hour), to prove the message. Console should show `Proven` if successful
2. One week later, to finalize it

Requirements:
- `yarn install`
- `.env` file setup (`PRIVATE_KEY`, `INFURA_API_KEY`, `ALCHEMY_API_KEY`)

Extra notes:
- Optimism stack requires `eth_getProof`, unsupported by Infura therefore **Alchemy** is used for L2 RPC.
- L1 RPC can still use Infura.

Official docs page:
https://docs.optimism.io/app-developers/tutorials/bridging/cross-dom-solidity#interact-with-the-l2-greeter
38 changes: 33 additions & 5 deletions contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "hardhat-deploy";
import "hardhat-deploy-ethers";
import "./tasks/generate-metaevidence";
import "./tasks/relay-arbitrum";
import "./tasks/relay-op";
import "./tasks/find-dispute-id";
import "./tasks/update-deployments";

Expand Down Expand Up @@ -84,7 +85,7 @@ const config: HardhatUserConfig = {
},
optimismSepolia: {
chainId: 11155420,
url: `https://optimism-sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`,
url: `https://opt-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`,
// url: `http://127.0.0.1:8547`, // fork with `anvil --fork-url https://optimism-sepolia.infura.io/v3/${process.env.INFURA_API_KEY} --port 8547`
accounts: [process.env.PRIVATE_KEY as string],
tags: ["home"],
Expand Down Expand Up @@ -149,7 +150,7 @@ const config: HardhatUserConfig = {
},
verify: {
etherscan: {
apiUrl: "https://api.etherscan.io/api",
apiUrl: "https://api.etherscan.io/v2/api?chainid=1",
apiKey: process.env.ETHERSCAN_API_KEY,
},
},
Expand Down Expand Up @@ -186,7 +187,7 @@ const config: HardhatUserConfig = {
},
optimism: {
chainId: 10,
url: `https://optimism-mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`,
url: `https://opt-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`,
accounts: [process.env.PRIVATE_KEY as string],
tags: ["home"],
companionNetworks: {
Expand Down Expand Up @@ -216,7 +217,7 @@ const config: HardhatUserConfig = {
},
base: {
chainId: 8453,
url: `https://base-mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`,
url: `https://base-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`,
accounts: [process.env.PRIVATE_KEY as string],
tags: ["home"],
companionNetworks: {
Expand Down Expand Up @@ -254,12 +255,39 @@ const config: HardhatUserConfig = {
},
verify: {
etherscan: {
apiUrl: "https://api.polygonscan.com/api",
apiUrl: "https://api.etherscan.io/v2/api?chainid=137",
apiKey: process.env.POLYGONSCAN_API_KEY,
},
},
},
},
etherscan: {
apiKey: {
// These are separate from Ethereum's etherscan API key
optimisticEthereum: process.env.OPTIMISM_API_KEY!,
mainnet: process.env.ETHERSCAN_API_KEY!,
polygon: process.env.ETHERSCAN_API_KEY!,
base: process.env.ETHERSCAN_API_KEY!
},
Comment on lines +265 to +271
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use optional chaining or default values instead of non-null assertions.

The non-null assertion operator (!) on lines 267-270 will throw runtime errors if the environment variables are undefined. This can cause Hardhat to fail even for tasks that don't require Etherscan verification.

Consider using optional chaining with fallback values:

   etherscan: {
     apiKey: {
       // These are separate from Ethereum's etherscan API key
-      optimisticEthereum: process.env.OPTIMISM_API_KEY!,
-      mainnet: process.env.ETHERSCAN_API_KEY!,
-      polygon: process.env.ETHERSCAN_API_KEY!,
-      base: process.env.ETHERSCAN_API_KEY!
+      optimisticEthereum: process.env.OPTIMISM_API_KEY || "",
+      mainnet: process.env.ETHERSCAN_API_KEY || "",
+      polygon: process.env.ETHERSCAN_API_KEY || "",
+      base: process.env.ETHERSCAN_API_KEY || ""
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
apiKey: {
// These are separate from Ethereum's etherscan API key
optimisticEthereum: process.env.OPTIMISM_API_KEY!,
mainnet: process.env.ETHERSCAN_API_KEY!,
polygon: process.env.ETHERSCAN_API_KEY!,
base: process.env.ETHERSCAN_API_KEY!
},
apiKey: {
// These are separate from Ethereum's etherscan API key
optimisticEthereum: process.env.OPTIMISM_API_KEY || "",
mainnet: process.env.ETHERSCAN_API_KEY || "",
polygon: process.env.ETHERSCAN_API_KEY || "",
base: process.env.ETHERSCAN_API_KEY || ""
},
🤖 Prompt for AI Agents
In contracts/hardhat.config.ts around lines 265 to 271, the apiKey entries use
non-null assertions (process.env.VAR!) which will throw at runtime if those env
vars are missing; replace the non-null assertions with a safe fallback by using
optional chaining or default values (e.g., process.env.OPTIMISM_API_KEY ?? "" or
process.env.OPTIMISM_API_KEY || "") so the config loads even when keys are
undefined, ensuring tasks that don’t need Etherscan won’t fail.

customChains: [
{
network: "base",
chainId: 8453,
urls: {
apiURL: "https://api.etherscan.io/v2/api?chainid=8453",
browserURL: "https://basescan.org/"
}
},
{
network: "polygon",
chainId: 137,
urls: {
apiURL: "https://api.etherscan.io/v2/api?chainid=137",
browserURL: "https://polygonscan.com/"
}
},
]
},
namedAccounts: {
deployer: {
default: 0,
Expand Down
1 change: 0 additions & 1 deletion contracts/hardhat.config.zksync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import "@matterlabs/hardhat-zksync-solc";
import "@matterlabs/hardhat-zksync-verify";
import "hardhat-deploy";
import "./tasks/update-deployments";
// import "./tasks/generate-metaevidence";

import type { HardhatUserConfig } from "hardhat/config";

Expand Down
14 changes: 12 additions & 2 deletions contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
"hardhat-zksync": "hardhat --config hardhat.config.zksync.ts",
"zksync:proof:staging": "yarn hardhat-zksync run ./scripts/execute_proof.js --network zkSyncSepolia",
"zksync:proof:production": "yarn hardhat-zksync run ./scripts/execute_proof.js --network zkSyncMainnet",
"relay:staging": "hardhat relay-arbitrum --network arbitrumSepolia",
"relay:production": "hardhat relay-arbitrum --network arbitrum",
"relay-op:optimism": "hardhat relay-op --network optimism",
"relay-op:redstone": "hardhat relay-op --network redstone",
"relay-op:base": "hardhat relay-op --network base",
"relay-op:unichain": "hardhat relay-op --network unichain",
"relay-op:opSepolia": "hardhat relay-op --network optimismSepolia",
"relay-op:uniSepolia": "hardhat relay-op --network unichainSepolia",
"etherscan-verify": "hardhat etherscan-verify",
"format:js": "biome format --write scripts deploy tasks",
"check:js": "biome check --write scripts deploy tasks",
Expand Down Expand Up @@ -90,6 +98,7 @@
"@types/node": "^22.14.0",
"chai": "^4.5.0",
"ethers": "^6.13.5",
"ethers5": "npm:ethers@5.7.2",
"hardhat": "^2.22.18",
"hardhat-deploy": "^0.14.0",
"hardhat-deploy-ethers": "^0.4.2",
Expand All @@ -106,11 +115,12 @@
},
"dependencies": {
"@arbitrum/nitro-contracts": "^1.3.0",
"@arbitrum/sdk": "^v3.1.9",
"@arbitrum/sdk": "^4.0.4",
"@kleros/dispute-resolver-interface-contract-0.7": "npm:@kleros/dispute-resolver-interface-contract@^2.0.0",
"@kleros/dispute-resolver-interface-contract-0.8": "npm:@kleros/dispute-resolver-interface-contract@^8.0.0",
"@kleros/ethereum-libraries": "^7.0.0",
"@matterlabs/zksync-contracts": "^0.6.1",
"dotenv": "^16.4.7"
"dotenv": "^16.4.7",
"viem": "^2.38.3"
}
}
52 changes: 27 additions & 25 deletions contracts/scripts/execute_proof.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
const hre = require("hardhat");
const { Provider, utils } = require("zksync-web3");
const ethers = require("ethers");
const { Provider, utils } = require("zksync-ethers");
const { getL1MessageSentEvent, getCalldata } = require("./get_event_properties");
const RealitioForeignArbitrationProxy = require("@kleros/cross-chain-realitio-contracts/artifacts-zk/src/zkRealitioForeignProxy.sol/zkRealitioForeignProxy.json");
const RealitioHomeArbitrationProxy = require("@kleros/cross-chain-realitio-contracts/artifacts-zk/src/zkRealitioHomeProxy.sol/zkRealitioHomeProxy.json");
const RealitioForeignArbitrationProxy = require("@kleros/cross-chain-realitio-contracts/artifacts-zk/src/0.8/RealitioForeignProxyZkSync.sol/RealitioForeignProxyZkSync.json");
const RealitioHomeArbitrationProxy = require("@kleros/cross-chain-realitio-contracts/artifacts-zk/src/0.8/RealitioHomeProxyZkSync.sol/RealitioHomeProxyZkSync.json");

async function executeProof() {
// https://era.zksync.io/docs/dev/how-to/send-message-l2-l1.html
// https://code.zksync.io/tutorials/how-to-send-l2-l1-message
const txHash = "";

const { providers } = ethers;
const foreignNetworks = {
324: hre.config.networks.mainnet,
300: hre.config.networks.sepolia,
};
const chainId = hre.network.config.chainId;
const url = foreignNetworks[chainId];

const l1Provider = new Provider(hre.network.config.url);
const l2Provider = new providers.JsonRpcProvider(url);
const l1Provider = new ethers.JsonRpcProvider(foreignNetworks[chainId]?.url);
const l2Provider = new Provider(hre.network.config.url);

const l1MessageSentEvent = await getL1MessageSentEvent(txHash, utils.L1_MESSENGER, l1Provider);
const l1MessageSentEvent = await getL1MessageSentEvent(txHash, utils.L1_MESSENGER, l2Provider);

if (!l1MessageSentEvent) {
throw new Error("No L1MessageSent event found in the transaction.");
Expand All @@ -28,13 +27,13 @@ async function executeProof() {
const blockNumber = l1MessageSentEvent.blockNumber;
const homeProxy = `0x${BigInt(l1MessageSentEvent.address).toString(16)}`;
const msgHash = l1MessageSentEvent.msgHash;
const eventData = await getCalldata(txHash, l1Provider);
const homeProxyContract = new ethers.Contract(homeProxy, RealitioHomeArbitrationProxy.abi, l1Provider);
const eventData = await getCalldata(txHash, l2Provider);
const homeProxyContract = new ethers.Contract(homeProxy, RealitioHomeArbitrationProxy.abi, l2Provider);
console.log(await homeProxyContract.foreignProxy());
const foreignProxyContract = new ethers.Contract(
await homeProxyContract.foreignProxy(),
RealitioForeignArbitrationProxy.abi,
l2Provider
l1Provider
);

console.log(`Event: ${l1MessageSentEvent.name}`);
Expand All @@ -43,14 +42,18 @@ async function executeProof() {
console.log("Hash:", msgHash);
console.log("Message:", eventData);

const proof = await getL1MessageProof(blockNumber, l1Provider, homeProxy, msgHash);
console.log("Proof is: ", proof);
const { l1BatchNumber, l1BatchTxIndex } = await l1Provider.getTransactionReceipt(txHash);

const l2Receipt = await l2Provider.getTransactionReceipt(txHash);
console.log(l2Receipt);
const logIndex = l2Receipt.l2ToL1Logs[0].logIndex;
console.log(`L2 transaction included in block ${l2Receipt.blockNumber} with log index ${logIndex}`);
const { l1BatchNumber, l1BatchTxIndex } = l2Receipt;
console.log("L1 Index for Tx in block :>> ", l1BatchTxIndex);
console.log("L1 Batch for block :>> ", l1BatchNumber);

const result = await proveL1MessageInclusion(
const proof = await getLogProof(txHash, logIndex, l2Provider);
console.log("Proof is: ", proof);

const result = await proveL2MessageInclusion(
l1BatchNumber,
proof,
l1BatchTxIndex,
Expand All @@ -63,7 +66,7 @@ async function executeProof() {
console.log("Result is :>> ", result);

if (result) {
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, l2Provider);
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, l1Provider);
try {
await foreignProxyContract
.connect(signer)
Expand All @@ -78,17 +81,16 @@ async function executeProof() {
process.exit();
}

async function getL1MessageProof(blockNumber, l1Provider, homeProxy, msgHash) {
console.log(`Getting L1 message proof for block ${blockNumber}`);
return await l1Provider.getMessageProof(blockNumber, homeProxy, msgHash);
async function getLogProof(txHash, l2TxIndex, l2Provider) {
return await l2Provider.getLogProof(txHash, l2TxIndex);
}

async function proveL1MessageInclusion(l1BatchNumber, proof, trxIndex, l1Provider, l2Provider, homeProxy, message) {
const zkAddress = await l1Provider.getMainContractAddress();
async function proveL2MessageInclusion(l1BatchNumber, proof, trxIndex, l1Provider, l2Provider, homeProxy, message) {
const zkAddress = await l2Provider.getMainContractAddress();

const mailboxL1Contract = new ethers.Contract(zkAddress, utils.ZKSYNC_MAIN_ABI, l2Provider);
const mailboxL1Contract = new ethers.Contract(zkAddress, utils.ZKSYNC_MAIN_ABI, l1Provider);
const messageInfo = {
txNumberInBlock: trxIndex,
txNumberInBatch: trxIndex,
sender: homeProxy,
data: message,
};
Expand Down
4 changes: 2 additions & 2 deletions contracts/scripts/get_event_properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async function getL1MessageSentEvent(transactionHash, contractInterface, provide
}

function getFunctionSelector(functionSignature) {
const hash = ethers.utils.id(functionSignature);
const hash = ethers.id(functionSignature);
const selector = hash.slice(0, 10); // 0x + first 4 bytes

return selector;
Expand All @@ -40,7 +40,7 @@ function encodeWithSelector(selector, ...params) {
}

// Otherwise, encode using defaultAbiCoder
return ethers.utils.defaultAbiCoder.encode([param.type], [param.value]).slice(2);
return ethers.AbiCoder.defaultAbiCoder().encode([param.type], [param.value]).slice(2);
});

const encodedData = selector + encodedParams.join("");
Expand Down
Loading