> ## Documentation Index
> Fetch the complete documentation index at: https://docs.optimism.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Relay transactions manually

> Learn to relay transactions directly by sending the correct transaction.

<Info>OP Stack interop is in active development. Some features may be experimental.</Info>

<Note>Messages are relayed automatically in the interop devnet.</Note>

## Overview

Learn to relay transactions directly by sending the correct transaction.

<Expandable title="About this tutorial">
  **Prerequisite technical knowledge**

  * Familiarity with blockchain concepts
  * Familiarity with [Foundry](https://book.getfoundry.sh/getting-started/installation), especially `cast`

  **What you'll learn**

  * How to use `cast` to relay transactions when autorelay does not work
  * How to relay transactions using JavaScript

  **Development environment requirements**

  * Unix-like operating system (Linux, macOS, or WSL for Windows)
  * Node.js version 16 or higher
  * Git for version control
  * Supersim environment configured and running
  * Foundry tools installed (forge, cast, anvil)
</Expandable>

### What you'll build

* A program to relay messages using [the JavaScript library](https://www.npmjs.com/package/@eth-optimism/viem)
* A shell script to relay messages using [`cast`](https://book.getfoundry.sh/cast/)

## Setup

These steps are necessary to run the tutorial, regardless of whether you are using `cast` or the JavaScript API.

<Steps>
  <Step title="Run Supersim">
    This exercise needs to be done on Supersim.\
    You cannot use the devnets because you cannot disable autorelay on them.

    1. Follow the [installation guide](/app-developers/tutorials/development/supersim/installation).
    2. Run Supersim *without* `--interop.relay`.

    ```sh theme={null}
    ./supersim
    ```
  </Step>

  <Step title="Create the state for relaying messages">
    The results of this step are similar to what the [message passing tutorial](/app-developers/tutorials/interoperability/message-passing) would produce if you did not have autorelay on.

    Execute this script:

    ```sh theme={null}
    #! /bin/sh
    # full shell script preserved here...
    # (Greeter.sol, GreetingSender.sol, sendAndRelay.sh setup)
    # ...
    ```

    This script installs `Greeter.sol` on chain B and `GreetingSender.sol` on chain A.\
    These smart contracts let us send a message from chain A that needs to be relayed to chain B.

    Then, the script creates `./manual-relay/sendAndRelay.sh` to manually relay a message from chain A to chain B.\
    That script is [explained below](#manual-relay-using-cast).

    Finally, this script writes out some parameter setting lines that should be executed on the main shell before you continue.\
    With a fresh Supersim running, these should be:

    ```sh theme={null}
    GREETER_A_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
    GREETER_B_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
    PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
    ```
  </Step>
</Steps>

## Manual relay using the API

<Steps>
  <Step title="Setup">
    Use a [Node](https://nodejs.org/en) project.

    1. Initialize a new Node project.

    ```sh theme={null}
    mkdir -p manual-relay/offchain
    cd manual-relay/offchain
    npm init -y
    npm install --save-dev viem @eth-optimism/viem
    mkdir src
    ```

    2. Export environment variables:

    ```sh theme={null}
    export GREETER_A_ADDRESS GREETER_B_ADDRESS PRIVATE_KEY
    ```
  </Step>

  <Step title="Manual relaying app">
    Create a file `manual-relay.mjs` with:

    ```javascript theme={null}
        import {
        createWalletClient,
        http,
        publicActions,
        getContract,
    } from 'viem'
    import { privateKeyToAccount } from 'viem/accounts'
    import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains' 
    import { walletActionsL2, publicActionsL2 } from '@eth-optimism/viem'

    import { readFileSync } from 'fs';

    const greeterData = JSON.parse(readFileSync('../onchain/out/Greeter.sol/Greeter.json'))
    const greetingSenderData = JSON.parse(readFileSync('../onchain/out/Greeter.sol/Greeter.json'))

    const account = privateKeyToAccount(process.env.PRIVATE_KEY)

    const walletA = createWalletClient({
        chain: supersimL2A,
        transport: http(),
        account
    }).extend(publicActions)
        .extend(publicActionsL2())
    //    .extend(walletActionsL2())

    const walletB = createWalletClient({
        chain: supersimL2B,
        transport: http(),
        account
    }).extend(publicActions)
    //    .extend(publicActionsL2())
        .extend(walletActionsL2())

    const greeter = getContract({
        address: process.env.GREETER_B_ADDRESS,
        abi: greeterData.abi,
        client: walletB
    })

    const greetingSender = getContract({
        address: process.env.GREETER_A_ADDRESS,
        abi: greetingSenderData.abi,
        client: walletA
    })

    const txnBHash = await greeter.write.setGreeting(
        ["Greeting directly to chain B"])
    await walletB.waitForTransactionReceipt({hash: txnBHash})

    const greeting1 = await greeter.read.greet()
    console.log(`Chain B Greeting: ${greeting1}`)

    const txnAHash = await greetingSender.write.setGreeting(
        ["Greeting through chain A"])
    const receiptA = await walletA.waitForTransactionReceipt({hash: txnAHash})

    const greeting2 = await greeter.read.greet()
    console.log(`Greeting before the relay transaction: ${greeting2}`)

    const sentMessages = await walletA.interop.getCrossDomainMessages({
      logs: receiptA.logs,
    })
    const sentMessage = sentMessages[0] // We only sent 1 message
    const relayMessageParams = await walletA.interop.buildExecutingMessage({
      log: sentMessage.log,
    })
    const relayMsgTxnHash = await walletB.interop.relayCrossDomainMessage(relayMessageParams)

    const receiptRelay = await walletB.waitForTransactionReceipt({
      hash: relayMsgTxnHash,
    })

    const greeting3 = await greeter.read.greet()
    console.log(`Greeting after the relay transaction: ${greeting3}`)
    ```

    <Expandable title="Explanation">
      ```javascript theme={null}
      import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains' 
      import { walletActionsL2, publicActionsL2 } from '@eth-optimism/viem'
      ```
    </Expandable>

    Run JavaScript program:

    ```sh theme={null}
    node manual-relay.mjs
    ```
  </Step>

  <Step title="Debugging">
    To see what messages were relayed by a specific transaction:

    ```javascript theme={null}
    import { decodeRelayedL2ToL2Messages } from '@eth-optimism/viem'

    const decodedRelays = decodeRelayedL2ToL2Messages({ receipt: receiptRelay })

    console.log(decodedRelays)
    console.log(decodedRelays.successfulMessages[0].log)
    ```
  </Step>
</Steps>

## Manual relay using `cast`

You can see an example of how to manually relay using `cast` in `manual-relay/sendAndRelay.sh`.\
It is somewhat complicated, so the setup creates one that is tailored to your environment.

Run the script:

```sh theme={null}
./manual-relay/sendAndRelay.sh
```

Here is the detailed explanation:

1. Configuration parameters

   ```sh theme={null}
   #! /bin/sh
   PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
   USER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
   URL_CHAIN_A=http://localhost:9545
   URL_CHAIN_B=http://localhost:9546
   GREETER_A_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
   GREETER_B_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
   CHAIN_ID_B=902
   ```

   This is the configuration.
   The greeter addresses are identical because the nonce for the user address has an identical nonce on both chains.

2. Send a message that needs to be relayed

   ```sh theme={null}
   cast send -q --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A $$"
   ```

   Send a message from chain A to chain B. The `$$` is the process ID, so if you rerun the script you'll see that the information changes.

3. Find the log entry to relay

   ```sh theme={null}
   cast logs "SentMessage(uint256,address,uint256,address,bytes)" --rpc-url $URL_CHAIN_A | tail -14 > log-entry
   ```

   Whenever `L2ToL2CrossDomainMessenger` sends a message to a different blockchain, it emits a [`SendMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L83-L91) event.
   Extract only the latest `SendMessage` event from the logs.

   <Expandable title="Example `log-entry`">
     ```yaml theme={null}
     - address: 0x4200000000000000000000000000000000000023
       blockHash: 0xcd0be97ffb41694faf3a172ac612a23f224afc1bfecd7cb737a7a464cf5d133e
       blockNumber: 426
       data: 0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064a41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001948656c6c6f2066726f6d20636861696e2041203131333030370000000000000000000000000000000000000000000000000000000000000000000000
       logIndex: 0
       removed: false
       topics: [
               0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320
               0x0000000000000000000000000000000000000000000000000000000000000386
               0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa3
               0x0000000000000000000000000000000000000000000000000000000000000000
       ]
       transactionHash: 0x1d6f2e5e2c8f3eb055e95741380ca36492f784b9782848b66b66c65c5937ff3a
       transactionIndex: 0
     ```
   </Expandable>

4. Manipulate the log entry to obtain information

   ```sh theme={null}
   TOPICS=`cat log-entry | grep -A4 topics | awk '{print $1}' | tail -4 | sed 's/0x//'`
   TOPICS=`echo $TOPICS | sed 's/ //g'`
   ```

   Consolidate the log topics into a single hex string.

   ```sh theme={null}
   ORIGIN=0x4200000000000000000000000000000000000023
   BLOCK_NUMBER=`cat log-entry | awk '/blockNumber/ {print $2}'`
   LOG_INDEX=`cat log-entry | awk '/logIndex/ {print $2}'`
   TIMESTAMP=`cast block $BLOCK_NUMBER --rpc-url $URL_CHAIN_A | awk '/timestamp/ {print $2}'`
   CHAIN_ID_A=`cast chain-id --rpc-url $URL_CHAIN_A`
   SENT_MESSAGE=`cat log-entry | awk '/data/ {print $2}'`
   ```

   Read additional fields from the log entry.

   ```sh theme={null}
   LOG_ENTRY=0x`echo $TOPICS$SENT_MESSAGE | sed 's/0x//'`
   ```

   Consolidate the entire log entry.

5. Create the access list for the executing message

```sh theme={null}
RPC_PARAMS=$(cat <<INNER_END_OF_FILE
{
    "origin": "$ORIGIN",
    "blockNumber": "$BLOCK_NUMBER",
    "logIndex": "$LOG_INDEX",
    "timestamp": "$TIMESTAMP",
    "chainId": "$CHAIN_ID_A",
    "payload": "$LOG_ENTRY"
}
INNER_END_OF_FILE
)

ACCESS_LIST=`cast rpc admin_getAccessListForIdentifier --rpc-url http://localhost:8420 "$RPC_PARAMS" | jq .accessList`

```

To secure cross-chain messaging and prevent potential [denial-of-service attacks](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/interop-access-list.md), relay transactions require properly formatted access lists that include a checksum derived from the message data.
This lets sequencers know what executing messages to expect in a transaction, which makes it easy not to include transactions that are invalid because they rely on messages that were never sent.

The [algorithm to calculate the access list](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol#L87-L115) is a bit complicated, but you dont need to worry about it.
Supersim exposes [RPC calls](https://supersim.pages.dev/guides/interop/cast?highlight=manuall#7-construct-the-access-list-for-the-message) that calculates it for you on port 8420.
The code above will calculate the correct access list even if you're using a different interop cluster where autorelay is not functioning.
This is because the code implements a [pure function](https://en.wikipedia.org/wiki/Pure_function), which produces consistent results regardless of external state.
In contrast, the `admin_getAccessListByMsgHash` RPC call is not a pure function, it is dependent on system state and therefore less flexible in these situations.

6. Show that the manual relay is necessary

   ```sh theme={null}
   echo Old greeting
   cast call $GREETER_B_ADDRESS "greet()(string)" --rpc-url $URL_CHAIN_B
   ```

   Show the current greeting.
   The message has not been relayed yet, so it's still the old greeting.

7. Actually relay the message

   ```sh theme={null}
   cast send -q $ORIGIN "relayMessage((address,uint256,uint256,uint256,uint256),bytes)" "($ORIGIN,$BLOCK_NUMBER,$LOG_INDEX,$TIMESTAMP,$CHAIN_ID_A)" $LOG_ENTRY --access-list "$ACCESS_LIST" --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY
   ```

   Call [`relayMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L197-L256) to relay the message.

8. Show the relay results

   ```sh theme={null}
   echo New greeting
   cast call $GREETER_B_ADDRESS "greet()(string)" --rpc-url $URL_CHAIN_B
   ```

   Again, show the current greeting.
   Now it's the new one.

## Next steps

* Review the \[OP Stack interop explainer]\(/> op-stack/interop/explainer) for answers to common questions about interoperability.
* Read the [message passing explainer](/app-developers/guides/interoperability/message-passing) to understand what happens "under the hood".
* Write a revolutionary app that uses multiple blockchains within the OP Stack ecosystem.
