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

# How to Handle On-Chain Events

> Use Web3 Actions as a serverless backend to respond to on-chain events. Follow a Tic-Tac-Toe example to monitor game changes.

This tutorial will teach you how to **create a serverless backend for your smart contract using Tenderly Web3 Actions**. Web3 Actions allow you to run custom code in response to on-chain or off-chain events that are initiated by your smart contract.

To illustrate how Web3 Actions work, we build a simple Tic-Tac-Toe game and deploy it to a test network. The smart contract maintains the game state while Web3 Actions monitor changes to the game.

**Whenever a specific event gets fired from the smart contract, Tenderly will execute your custom code in the form of a NodeJS project**. The results of the game and the game board will be printed to the console each time a player makes a move or when the game is over.

<Info>
  Check out this [Github repo](https://github.com/Tenderly/examples-web3-actions/) to find the
  source code for this project. Feel free to clone the repo and play around with it, or code along.
</Info>

## Understanding Web3 Actions

Smart contracts allow you to execute custom code when an event is triggered, a function is invoked, or perhaps periodically.

Tenderly helps you streamline this process with Web3 Actions. You can write your Web3 Actions in a single Javascript file or deploy them as a NodeJS project.

**Web3 Actions also allow you to perform any action you normally would with NodeJS**. This tutorial will show you how to use the Tenderly CLI to deploy your Web3 Actions and ensure they run when specific conditions are met.

<Note>
  Learn more about Web3 Actions and explore how you can use them in your projects with [**Basics of
  Web3 Actions**](/monitoring/web3-actions/tutorials/on-chain-events).
</Note>

### Project Overview

The purpose of this project is to show you how to deploy a smart contract and write several Javascript functions that Tenderly will invoke when an event occurs. You will learn how to use the Tenderly CLI to deploy your Web3 Actions to Tenderly’s infrastructure.

Here’s a step-by-step overview of how to do this:

1. Deploy the smart contract to a test network
2. Use the Tenderly CLI to set up Javascript and config files needed to run the Web3 Actions
3. Develop functions that respond to game events
4. Deploy those functions to Tenderly’s infrastructure using the CLI

<Warning>
  For this tutorial, you’ll need access to the **Tenderly Dashboard**. If you don’t have an account,
  [**sign up here for free**](https://dashboard.tenderly.co/register?utm_source=homepage) (no cc
  required as well).
</Warning>

## 0: Deploy the smart contract

<Note>
  If you’re coding along, you can skip this step and use the contract we [**deployed and verified in
  Tenderly**](https://dashboard.tenderly.co/contract/sepolia/0x1338d7232786b365b0a6bbd06552f885371f5f06).
</Note>

We use a pre-written smart contract. Check out the source code of the[ **smart contract on GitHub**](https://github.com/Tenderly/examples-web3-actions/blob/main/tic-tac-toe/TicTacToe.sol). This smart contract is designed to emit events whenever a change to the state of the game occurs.

Our simple Tic-Tac-Toe game can have four possible states:

* Game start
* Player joins the game
* Player makes a move
* Game over

### Prerequisite

**Create Two Accounts** - deploy the smart contract to any [network that is supported by Tenderly](/platform/supported-networks). The most convenient way to do this is with [Remix](https://remix.ethereum.org/) and the [Metamask](https://metamask.io/) wallet plugin. You need two accounts that have a positive Ether balance. These accounts will represent the two players. For the purpose of this tutorial we use Sepolia. If you plan to follow along with this tutorial, you can use the [Sepolia faucet](https://faucet.chainstack.com/sepolia-testnet-faucet/) to add Ether to the accounts.

**Compile the contract -** use the Remix IDE to compile the smart contract. Create a new contract file and add the code found [**here**](https://github.com/Tenderly/examples-web3-actions/blob/main/tic-tac-toe/TicTacToe.sol).

<Frame caption="Compiling the contract in the Remix IDE">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(69).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=6f20b3090790b771b014692cdcf7be9a" alt="Compiling the contract in the Remix IDE" width="2878" height="1642" data-path="images/image (69).webp" />
</Frame>

**Deploy to a test network** - in your Metamask browser plugin, make sure Sepolia is selected as your preferred network. Next, go to the **“Deploy And Run Transactions”** section in Remix and select **`Injected Web3`**. Click **“Deploy”** once you’ve made sure that the account references the account in Metamask.

<Frame caption="Deploying the contract to a testnet">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(73).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=a8cfef3f43f563adbb3dfdd62177b50f" alt="Deploying the contract to a testnet" width="1070" height="1372" data-path="images/image (73).webp" />
</Frame>

<Frame caption="Copying the contract address">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(92).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=4a2339153a44fa17921bce22e01e703b" alt="Copying the contract address" width="912" height="1070" data-path="images/image (92).webp" />
</Frame>

To get more information on the deployed contract, check out the **“Deployed Contracts”** section. Click the copy icon to copy the full contract address (`0x133...`) and store it somewhere because we’ll need it for the steps that follow.

Now go to the **Tenderly Dashboard to verify the contract**. This will make it possible for us to interact with the contract later on in the tutorial.

## 1: Set Up Web3 Actions via the Tenderly CLI

To continue with this tutorial, you need to have the **Tenderly CLI** installed on your machine. Follow [**this guide**](https://github.com/Tenderly/tenderly-cli#installation) to learn how to set it up and authenticate access.

With the CLI installed, create a new directory and `cd` into it. Initialize your Web3 Actions by running the `tenderly actions init` command:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
$> cd tdly-actions
$> tenderly actions init
```

You'll be prompted to select one of your existing Tenderly projects. Your Web3 Actions will be deployed to this project and should appear in your directory structure like so:

```bash title="example" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
$> ls actions
  example.ts     # where we write Web3 actions code
  tsconfig.json
  package.json
```

Typescript is the default language but you can switch to plain Javascript. Before you execute the init command, set Javascript as your preferred language like so: `tenderly actions init --language javascript`.

<Note>
  The `package.json` holds npm dependencies which will be available when you deploy your Web3
  Action. The `tsconfig.json` file holds Typescript
  configuration related only to `.ts` files within the{' '}
  `actions` directory.
</Note>

### 1.1. Add Tic-Tac-Toe Contract’s ABI

Before we dive deeper into the code, copy the ABI generated by the compiler to the actions directory. In our example, this is the `tdly-actions` directory.

In Remix, go to `files/artifacts/TicTacToe.json`, copy/paste the file contents and paste them into your project’s `TicTacToe.json` file.

<Warning>
  All files your Web3 Action code uses and references must be placed in the{' '}
  `actions` directory (e.g.{' '}
  `TicTacToe.json`).
</Warning>

### 1.2. Configure Typescript to Import a JSON File as a Module

By default, Typescript doesn’t allow you to import JSON files as modules. You need to configure Typescript to be able to import the `TicTacToe.json` file as a module and access it as an object.

Go to your `tsconfig.json` file and include the following two configurations under the `compilerOptions` entry:

```diff title="example" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
{
    ...
    "compilerOptions": {
	...
+        "resolveJsonModule": true,
+        "esModuleInterop": true
    },
   ...
}
```

## 2: Write a Function to Handle New Game Event

Write a function that will execute when a new game is started. Let’s rename the Typescript file to something more descriptive:

```bash title="example" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
mv example.ts ticTacToeActions.ts
```

To harness the power of Typescript, we'll first need to define a type representing the game state. In essence, we'll store a replica of the game state in the storage of your Web3 Action. Each field contains the ID of the player who played there. We'll map the player's address to their turn (first, second, etc) in the `players` object.

### 2.1. Add an Action

```typescript title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
// ticTacToeActions.ts

export type Game = {
  players: { [address: string]: number };
  board: number[][];
};
```

At this point, we are ready to define the actual Web3 Action like so:

```typescript title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
export const createNewGame = (): Game => {
  return {
    players: {},
    board: [
      [0, 0, 0],
      [0, 0, 0],
      [0, 0, 0],
    ],
  };
};

// ticTacToeActions.ts continued
export const newGameAction: ActionFn = async (context: Context, event: Event) => {
  let txEvent = event as TransactionEvent;

  let iface = new ethers.utils.Interface(TicTacToe.abi);

  const result = iface.decodeEventLog('GameCreated', txEvent.logs[0].data, txEvent.logs[0].topics);

  const { gameId, playerNumber, player } = result;

  console.log('Game Created Event:', {
    gameId: gameId.toString(),
    playerNumber,
    player,
  });

  const game: Game = createNewGame();
  await context.storage.putJson(gameId.toString(), game);
};
```

This Web3 Action will handle the `GameCreated` event that is defined in the smart contract.

Looking at the smart contract, we can see that only one event is getting fired from the `newGame` function, so we’re interested in the first log entry. We can get the `result` with ethers.js by decoding `txEvent.logs[0].data` of the GameCreated event, based on the `TicTacToe.abi`.

Here we can access the ID associated with this particular game once it has been created: `result.gameId`. We want to track the data for this particular game: players and moves they made, using a brand new `Game` instance. We’re saving the object representing the new game in the Storage with this command: `context.storage.putJson(gameId, game)`.

With an empty board, we are setting the foundation to persist future changes which are handled by other actions.

You may also write automated tests to verify the behaviour of the Web3 Action. Some tests are available in the repository, but this is out of the scope of this tutorial.

### 2.2. Specify New Game Action Invocation

Open up tenderly.yaml. In the `specs` section, define the specs for `newGame` (arbitrary name) to invoke the function `newGameAction`. You can do so like this `newGameAction:newGameAction`, first we define the name of the file containing the function and then the function name.

Next, specify the `trigger` that Tenderly is supposed to watch out for to invoke the action. This is a `transaction` trigger that will run when the block is mined. We’re doing this for `network 3` when the event `NewGame` is emitted from the contract on the specified address.

Replace `TTT_CONTRACT_ADDRESS` with the actual address of your smart contract. If you want to deploy the contract to a network other than Sepolia, specify the network’s ID as the value of the network.

<Note>
  Replace `YOUR_USERNAME` and{' '}
  `YOUR_PROJECT_SLUG` with your Tenderly username and the
  slug of your project. You can copy those from the Dashboard URL:{' '}
  `https://dashboard.tenderly.co/{YOUR_USERNAME}/{YOUR_PROJECT_SLUG}/transactions`
</Note>

```yaml title="example.yaml" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
account_id: ''
actions:
  YOUR_USERNAME/YOUR_PROJECT_SLUG:
    runtime: v1
    sources: actions
    specs:
      newGame:
        description: Respond to newGame event
        function: ticTacToeActions:newGameAction
        trigger:
          type: transaction
          transaction:
            status:
              - mined
            filters:
              - network: 3
                eventEmitted:
                  contract:
                    address: TTT_CONTRACT_ADDRESS
                  name: GameCreated
project_slug: ''
```

### 2.3. Verify Your Tic-Tac-Toe Smart Contract on Tenderly

Before deploying the contract, you need to verify it in your Tenderly Dashboard.

If you wish, you can [upload the contract through your browser](/contract-verification/overview). Once uploaded, select the TicTacToe contract, the network you deployed it to, and the contract address.

Click Add Contract and fill out the compiler options as shown in the picture below:

<Frame caption="Verifying and adding the contract to Tenderly ">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(74).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=01b598bfc2a6fbdf63ddb1533f10d40c" alt="Verifying and adding the contract to Tenderly " width="2154" height="1142" data-path="images/image (74).webp" />
</Frame>

<Frame caption="Adding the contract compiler information ">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(87).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=e99c1eb47857101c573bddf8131cf40d" alt="Adding the contract compiler information " width="1668" height="660" data-path="images/image (87).webp" />
</Frame>

### 2.4. Deploy the Web3 Action to Tenderly

To deploy your Web3 Action, execute the `deploy` command using the Tenderly CLI:

```bash title="example" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
tenderly actions deploy
```

The output should look like this:

<Frame caption="The Web3 Action execution output">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(79).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=265837ec27e0378acfbc192074661c47" alt="The Web3 Action execution output" width="1111" height="202" data-path="images/image (79).webp" />
</Frame>

To see details about the deployment of your Web3 Action, check the Tenderly Dashboard. If the deployment was successful, you should see something like this:

<Frame caption="Opening the Web3 Action deployment information ">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(80).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=c35821d0a7d044029d8e1047cfa89ff6" alt="Opening the Web3 Action deployment information " width="2878" height="1394" data-path="images/image (80).webp" />
</Frame>

### 2.5. Try Joining a New Game 🎉

To verify that everything is working properly, head back to Remix and create a new game or even several games.

Open up the TicTacToe contract and hit `newGame`. Metamask should prompt you to confirm the execution of the Web3 Action.

Once the transaction is submitted to the chain, Remix should produce an output similar to this:

<Frame caption="Creating a new game in Remix">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(96).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=a0aad1196ca95ba6470b8b5f0edcee12" alt="Creating a new game in Remix" width="2880" height="1648" data-path="images/image (96).webp" />
</Frame>

Next, head back to your Tenderly Dashboard and open the Transactions section to see your transaction. In the Actions section, you’ll notice that the **“Latest Execution”** column has changed.

To view the execution history, open your Web3 Action and click the Execution History tab:

<Frame caption="Opening the Execution history tab for your Web3 Action ">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(81).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=7b2dedf972e0e4a6f9bc11fe27a33f85" alt="Opening the Execution history tab for your Web3 Action " width="2868" height="1444" data-path="images/image (81).webp" />
</Frame>

The execution history will provide you with the following data:

<Frame caption="The execution history data">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(89).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=ec2d6994130b322e35d858124d104898" alt="The execution history data" width="2169" height="1106" data-path="images/image (89).webp" />
</Frame>

In the upper pane, you'll find information about the payload coming from the chain. The lower pane contains details about the logged game number. In our case, this is `0xb`, which means it’s the 11th game started for this contract.

### 2.6. Check the Storage of Your Web 3 Actions

Click the **“Go To Storage”** button to see the contents of the Storage. Each game that is initiated will get its own storage slot in this key-value map.

When you open the game we just created with the **ID 11**, you’ll see zeros across all the fields, meaning no players have played the game yet.

<Frame caption="Opening the Storage of your Web3 Action ">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(90).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=88f11391c94122ab621cac47f2bc8bdd" alt="Opening the Storage of your Web3 Action " width="1522" height="558" data-path="images/image (90).webp" />
</Frame>

## 3: Add a Web3 Action to Handle New Players Joining a Game

The player joining the game should be processed by registering the address of the player and their move (1 or 2).

Besides the boilerplate code that helps us retrieve the event data, the `playerJoinAction.ts` file also contains the code that allows us to:

* Read the current game state from the storage using the game’s ID (`storage.getJson`).
* Retrieve the player’s address and store their turn: `game.players[player] = playerNumber`
* Save the updated game object to the storage of Web3 Action using `storage.putJson`.

```typescript title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
// playerJoinAction.ts

export const playerJoinedAction: ActionFn = async (context: Context, event: Event) => {
  let txEvent = event as TransactionEvent;
  let iface = new ethers.utils.Interface(TicTacToe.abi);
  const result = iface.decodeEventLog(
    'PlayerJoinedGame',
    txEvent.logs[0].data,
    txEvent.logs[0].topics,
  );

  const gameId = result.gameId.toString();
  const playerAddress = result.player.toLowerCase() as string;
  const playerNumber = result.playerNumber;

  console.log('Player joined event:', {
    gameId,
    playerAddress,
    playerNumber,
  });

  const game: Game = (await context.storage.getJson(gameId)) as Game;
  game.players[playerAddress] = playerNumber;

  await context.storage.putJson(result.gameId.toString(), game);
};
```

### 3.1. Specify PlayerJoinedGame Action Invocation

We also need to extend the `specs` from the `tenderly.yaml` file to include the specifications needed to invoke the Web3 Action.

```yaml title="example.yaml" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
playerJoined:
  description: Respond to player joining game
  function: ticTacToeActions:playerJoinedAction
  trigger:
    type: transaction
    transaction:
      status:
        - mined
      filters:
        - network: 3
          eventEmitted:
            contract:
              address: TTT_CONTRACT_ADDRESS
            name: PlayerJoinedGame
```

### 3.2. Deploy the action to Tenderly

Deploy your Web3 Action by running the `deploy` command. This command will also redeploy previously deployed Web3 Actions.

```javascript title="example.js" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
tenderly action deploy
```

### 3.3. Try Joining a New Game

Join a new game by going over to Remix and adding the game number from the logs in the `newGame` input field. To submit the transaction, click the `joinGame` button:

<Frame caption="Joining a new game in Remix ">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(85).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=b775e77378e6a2706fe73eed98a3d79d" alt="Joining a new game in Remix " width="852" height="796" data-path="images/image (85).webp" />
</Frame>

Once the transaction has been mined, the logs of the execution will display `playerNumber: 1`.

<Frame caption="The execution log">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(93).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=c528321ee8ce731435000c561537e60a" alt="The execution log" width="1116" height="345" data-path="images/image (93).webp" />
</Frame>

To play the game, switch to your other account in Metamask and click `joinGame` again. Upon the completion of the transaction, you’ll see a similar log output in the Execution History.

### 3.4. Check the Storage of Your Web 3 Actions

<Frame caption="Opening the execution of your Web3 Action">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(94).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=0e7db1401134a1407e2857804a9ec87e" alt="Opening the execution of your Web3 Action" width="1763" height="728" data-path="images/image (94).webp" />
</Frame>

Open any of these executions and click the **“Go To Storage”** button.

The game our players have joined has the ID of 11, and this is the state before any move has been made. The Tic-Tac-Toe board contains only zeros and the map from an Ethereum address to the player’s turn is present.

<Frame caption="Opening the Storage key value ">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(91).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=d98b4d0220c38499bde2925eabcc1a6c" alt="Opening the Storage key value " width="1400" height="678" data-path="images/image (91).webp" />
</Frame>

### 3.5. Add an Action to Handle When Player Makes a Move

Using Ethers, we get the game ID and load the game instance from storage. Next, update the field in row `result.boardRow` and column `result.boardCol` with the player’s input `game.players[player]`**.**

The function `processNewGameState`, will log the board to the console, but you can also send a tweet when a move is made, trigger a new transaction on the chain, or use it in any other way.

```typescript title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
// ticTacToeActions.ts
//...
export const playerMadeMoveAction: ActionFn = async (context: Context, event: Event) => {
  let txEvent = event as TransactionEvent;
  let iface = new ethers.utils.Interface(TicTacToe.abi);
  const result = iface.decodeEventLog(
    'PlayerMadeMove',
    txEvent.logs[0].data,
    txEvent.logs[0].topics,
  );

  const gameId = result.gameId.toString();
  const game = (await context.storage.getJson(gameId)) as Game;
  const player = result.player.toLowerCase() as string;
  const { boardRow, boardCol } = result;

  console.log("Player's move event log:", {
    gameId,
    player,
    boardRow: boardRow.toString(),
    boardCol: boardCol.toString(),
  });

  console.log(
    `Move: gameId ${gameId}, game ${JSON.stringify(
      game,
    )}, boradRow ${boardRow}, boardCol ${boardCol}, player ${player}`,
  );

  game.board[boardRow][boardCol] = game.players[player];
  console.log('MV', JSON.stringify(game));
  await context.storage.putJson(gameId, game);

  processNewGameState(game);
};

const processNewGameState = (game: Game) => {
  let board = '\n';
  game.board.forEach(row => {
    row.forEach(field => {
      if (field == 1) {
        board += '❎ ';
        return;
      }

      if (field == 2) {
        board += '🅾️ ';
        return;
      }

      board += '💜 ';
    });

    board += '\n';
  });

  console.log(board);
};
```

The corresponding spec for this action is:

```yaml title="example.yaml" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
playerMadeMove:
  description: Respond to player making a move event
  function: ticTacToeActions:playerMadeMoveAction
  trigger:
    type: transaction
    transaction:
      status:
        - mined
      filters:
        - network: 3
          eventEmitted:
            contract:
              address: TTT_CONTRACT_ADDRESS
            name: PlayerMadeMove
```

### 3.6. Make a Move to Play the Game

To make a move, use Metamask to switch to the first player who joined the game. Next, enter the game number you received, as well as the row and column on the board (0, 1).

<Frame caption="Making a move in Remix ">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(88).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=d87ef8ceb06a0bbd9474af3eb12c2169" alt="Making a move in Remix " width="914" height="934" data-path="images/image (88).webp" />
</Frame>

When the action gets executed, the output should look like this:

<Frame caption="The Web3 Action execution output">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(95).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=781967b49bfe714430a3558cbdb95f5d" alt="The Web3 Action execution output" width="698" height="236" data-path="images/image (95).webp" />
</Frame>

## Step 4. Add a Web3 Action to Handle When the Game is Over

Below you’ll find the code for the `GameOver` event. The `GameOver` event is fired when a player makes a move that wins the game or when the board is full. This event is fired after the `PlayerMadeMove` event. In the `txEvent.logs` list, the `GameOver` event is the second element.

An easy way to get `GameOver` event is to access it via the `txEvent.logs[1]`. However, we’ll implement a more robust solution which doesn’t depend on the order and number of events fired.

First, you need to get the `gameOverTopic` using ethers via `iface.getEventTopics`. This will give you the corresponding hexadecimal value. Next, you need to find the log entry in the `txEvent.logs`, whose topics list contains the `gameOverTopic`. This is our `GameOver` event log that we can decode using Ethers.

```javascript title="example.js" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
export const gameOverAction: ActionFn = async (context: Context, event: Event) => {
	let txEvent = event as TransactionEvent;
	let iface = new ethers.utils.Interface(TicTacToe.abi);

	const gameOverTopic = iface.getEventTopic("GameOver");
	const gameOverLog = txEvent.logs.find(log => log.topics.find(topic => topic == gameOverTopic) !== undefined);

	if (gameOverLog == undefined) {
		// impossible
		throw Error("GameOver log not found in event's logs");
	}

	const result = iface.decodeEventLog("GameOver", gameOverLog.data, gameOverLog.topics);

	console.log(result);

	const gameId = result.gameId.toString();
	const winner = result.winner as number;

	const winnerMessage = getWinnerMessage(winner);
	if (winnerMessage !== false) {
		console.info(`🎉 Winner of the game ${gameId} is ${winnerMessage}`);
	} else {
		console.error("🤔 weird winner code");
	}
}
```

### 4.1. Specify GameOver Action Invocation

Finally, we need to add the specification to invoke the `GameOver` action by extending the `specs` in `tenderly.yaml`:

```yaml title="example.yaml" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
gameOver:
  description: Respond to game over
  function: ticTacToeActions:gameOverAction
  trigger:
    type: transaction
    transaction:
      status:
        - mined
      filters:
        - network: 3
          eventEmitted:
            contract:
              address: TTT_CONTRACT_ADDRESS
            name: GameOver
```

### 4.2. Play the Game

Keep playing until one player wins the game or the board is entirely filled out. At the end of the game, the Execution History should look like this:

<Frame caption="The execution history at the end of the game">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(78).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=eb6b64f3d40bbc512205c312c51d4c04" alt="The execution history at the end of the game" width="2542" height="826" data-path="images/image (78).webp" />
</Frame>
