Skip to main content
Этот туториал научит вас создавать serverless-бэкенд для вашего смарт-контракта с помощью Tenderly Web3 Actions. Web3 Actions позволяют запускать пользовательский код в ответ на on-chain или off-chain события, инициированные вашим смарт-контрактом. Чтобы проиллюстрировать, как работают Web3 Actions, мы построим простую игру «крестики-нолики» и развернём её в тестовой сети. Смарт-контракт поддерживает состояние игры, а Web3 Actions отслеживают изменения в игре. Всякий раз, когда из смарт-контракта генерируется конкретное событие, Tenderly будет выполнять ваш пользовательский код в виде NodeJS-проекта. Результаты игры и игровое поле будут выводиться в консоль каждый раз, когда игрок делает ход или игра окончена.
Ознакомьтесь с этим Github-репозиторием, чтобы найти исходный код этого проекта. Свободно клонируйте репозиторий и экспериментируйте с ним, или кодите вместе с нами.

Понимание Web3 Actions

Смарт-контракты позволяют вам выполнять пользовательский код при срабатывании события, вызове функции или, возможно, периодически. Tenderly помогает упорядочить этот процесс с помощью Web3 Actions. Вы можете написать свои Web3 Actions в одном JavaScript-файле или развернуть как NodeJS-проект. Web3 Actions также позволяют выполнять любые действия, которые обычно делают с NodeJS. Этот туториал покажет, как использовать Tenderly CLI для развёртывания Web3 Actions и обеспечения их запуска при выполнении определённых условий.
Узнайте больше о Web3 Actions и о том, как использовать их в своих проектах, в Основах Web3 Actions.

Обзор проекта

Цель этого проекта — показать вам, как развернуть смарт-контракт и написать несколько JavaScript-функций, которые Tenderly будет вызывать при возникновении события. Вы узнаете, как использовать Tenderly CLI для развёртывания ваших Web3 Actions на инфраструктуре Tenderly. Вот пошаговый обзор того, как это сделать:
  1. Разверните смарт-контракт в тестовую сеть
  2. Используйте Tenderly CLI для настройки JavaScript- и конфигурационных файлов, необходимых для запуска Web3 Actions
  3. Разработайте функции, реагирующие на события игры
  4. Разверните эти функции на инфраструктуре Tenderly с помощью CLI
Для этого туториала вам понадобится доступ к Tenderly Dashboard. Если у вас нет аккаунта, зарегистрируйтесь здесь бесплатно (карта не требуется).

0: Разверните смарт-контракт

Если вы кодите вместе с нами, вы можете пропустить этот шаг и использовать контракт, который мы развернули и верифицировали в Tenderly.
Мы используем заранее написанный смарт-контракт. Ознакомьтесь с исходным кодом смарт-контракта на GitHub. Этот смарт-контракт спроектирован так, чтобы генерировать события всякий раз, когда происходит изменение состояния игры. Наша простая игра в крестики-нолики может иметь четыре возможных состояния:
  • Начало игры
  • Игрок присоединяется к игре
  • Игрок делает ход
  • Конец игры

Предварительные условия

Создайте два аккаунта — разверните смарт-контракт в любую сеть, поддерживаемую Tenderly. Наиболее удобный способ это сделать — с помощью Remix и плагина кошелька MetaMask. Вам нужны два аккаунта с положительным балансом Ether. Эти аккаунты будут представлять двух игроков. Для целей этого туториала мы используем Sepolia. Если вы планируете следовать этому туториалу, вы можете использовать кран Sepolia для пополнения аккаунтов эфиром. Скомпилируйте контракт — используйте IDE Remix для компиляции смарт-контракта. Создайте новый файл контракта и добавьте код, найденный здесь.
Compiling the contract in the Remix IDE

Компиляция контракта в IDE Remix

Разверните в тестовую сеть — в плагине MetaMask убедитесь, что выбрана Sepolia. Затем перейдите в раздел «Deploy And Run Transactions» в Remix и выберите Injected Web3. Нажмите «Deploy», убедившись, что аккаунт соответствует аккаунту в MetaMask.
Deploying the contract to a testnet

Развёртывание контракта в тестнет

Copying the contract address

Копирование адреса контракта

Чтобы получить больше информации о развёрнутом контракте, обратитесь к разделу «Deployed Contracts». Нажмите значок копирования, чтобы скопировать полный адрес контракта (0x133...), и сохраните его где-нибудь, так как он нам понадобится на следующих шагах. Теперь перейдите в Tenderly Dashboard, чтобы верифицировать контракт. Это позволит нам взаимодействовать с контрактом позже в туториале.

1: Настройка Web3 Actions через Tenderly CLI

Чтобы продолжить туториал, на вашей машине должен быть установлен Tenderly CLI. Следуйте этому руководству, чтобы узнать, как его настроить и аутентифицировать доступ. С установленным CLI создайте новый каталог и перейдите в него (cd). Инициализируйте свои Web3 Actions, выполнив команду tenderly actions init:
Вам будет предложено выбрать один из ваших существующих проектов Tenderly. Ваши Web3 Actions будут развёрнуты в этот проект и должны появиться в вашей структуре каталогов так:
example
По умолчанию используется TypeScript, но вы можете переключиться на обычный JavaScript. Перед выполнением команды init задайте JavaScript как предпочитаемый язык так: tenderly actions init --language javascript.
package.json содержит npm-зависимости, которые будут доступны при развёртывании вашего Web3 Action. Файл tsconfig.json содержит конфигурацию TypeScript, относящуюся только к файлам .ts в каталоге actions.

1.1. Добавьте ABI контракта Tic-Tac-Toe

Прежде чем углубляться в код, скопируйте ABI, сгенерированный компилятором, в каталог actions. В нашем примере это каталог tdly-actions. В Remix перейдите к files/artifacts/TicTacToe.json, скопируйте содержимое файла и вставьте его в файл TicTacToe.json вашего проекта.
Все файлы, которые использует и на которые ссылается код вашего Web3 Action, должны находиться в каталоге actions (например, TicTacToe.json).

1.2. Настройте TypeScript для импорта JSON-файла как модуля

По умолчанию TypeScript не позволяет импортировать JSON-файлы как модули. Нужно настроить TypeScript так, чтобы можно было импортировать файл TicTacToe.json как модуль и обращаться к нему как к объекту. Перейдите в файл tsconfig.json и добавьте следующие две настройки в раздел compilerOptions:
example

2: Напишите функцию для обработки события новой игры

Напишите функцию, которая будет выполняться при начале новой игры. Переименуем файл TypeScript в что-то более описательное:
example
Чтобы использовать возможности TypeScript, сначала нужно определить тип, представляющий состояние игры. По сути, мы будем хранить реплику состояния игры в Storage вашего Web3 Action. Каждое поле содержит ID игрока, сходившего туда. Мы будем сопоставлять адрес игрока его ходу (первый, второй и т. д.) в объекте players.

2.1. Добавьте Action

example.ts
На этом этапе мы готовы определить сам Web3 Action так:
example.ts
Этот Web3 Action будет обрабатывать событие GameCreated, определённое в смарт-контракте. Взглянув на смарт-контракт, мы видим, что из функции newGame генерируется только одно событие, поэтому нас интересует первая запись лога. Мы можем получить result с помощью ethers.js, декодировав txEvent.logs[0].data события GameCreated на основе TicTacToe.abi. Здесь мы можем получить ID, ассоциированный с этой конкретной игрой, после того как она была создана: result.gameId. Мы хотим отслеживать данные для этой конкретной игры: игроков и сделанные ими ходы, используя новый экземпляр Game. Мы сохраняем объект, представляющий новую игру, в Storage такой командой: context.storage.putJson(gameId, game). С пустой доской мы закладываем основу для сохранения будущих изменений, которые обрабатываются другими actions. Вы также можете написать автоматизированные тесты, чтобы проверить поведение Web3 Action. Некоторые тесты доступны в репозитории, но это выходит за рамки этого туториала.

2.2. Задайте вызов New Game Action

Откройте tenderly.yaml. В разделе specs определите спецификации для newGame (произвольное имя) для вызова функции newGameAction. Это можно сделать так: newGameAction:newGameAction, сначала определяем имя файла, содержащего функцию, а затем имя функции. Далее укажите trigger, за которым Tenderly должен следить для вызова action. Это триггер transaction, который сработает при майнинге блока. Мы делаем это для network 3, когда событие NewGame генерируется контрактом по указанному адресу. Замените TTT_CONTRACT_ADDRESS фактическим адресом вашего смарт-контракта. Если вы хотите развернуть контракт в сеть, отличную от Sepolia, укажите ID сети как значение network.
Замените YOUR_USERNAME и YOUR_PROJECT_SLUG на ваше имя пользователя Tenderly и slug вашего проекта. Их можно скопировать из URL Dashboard: https://dashboard.tenderly.co/{YOUR_USERNAME}/{YOUR_PROJECT_SLUG}/transactions
example.yaml

2.3. Верифицируйте ваш смарт-контракт Tic-Tac-Toe в Tenderly

Прежде чем разворачивать контракт, нужно верифицировать его в Tenderly Dashboard. При желании вы можете загрузить контракт через браузер. После загрузки выберите контракт TicTacToe, сеть, в которую вы его развернули, и адрес контракта. Нажмите Add Contract и заполните опции компилятора, как показано на картинке ниже:
Verifying and adding the contract to Tenderly

Верификация и добавление контракта в Tenderly

Adding the contract compiler information

Добавление информации о компиляторе контракта

2.4. Разверните Web3 Action в Tenderly

Чтобы развернуть ваш Web3 Action, выполните команду deploy через Tenderly CLI:
example
Вывод должен выглядеть так:
The Web3 Action execution output

Вывод выполнения Web3 Action

Чтобы увидеть детали развёртывания вашего Web3 Action, зайдите в Tenderly Dashboard. Если развёртывание прошло успешно, вы увидите что-то вроде этого:
Opening the Web3 Action deployment information

Открытие информации о развёртывании Web3 Action

2.5. Попробуйте начать новую игру 🎉

Чтобы убедиться, что всё работает правильно, вернитесь в Remix и создайте новую игру или несколько игр. Откройте контракт TicTacToe и нажмите newGame. MetaMask должен запросить у вас подтверждение выполнения Web3 Action. После того как транзакция отправлена в цепочку, Remix должен вывести результат, похожий на этот:
Creating a new game in Remix

Создание новой игры в Remix

Далее вернитесь в Tenderly Dashboard и откройте раздел Transactions, чтобы увидеть свою транзакцию. В разделе Actions вы заметите, что колонка «Latest Execution» изменилась. Чтобы просмотреть историю выполнения, откройте свой Web3 Action и нажмите вкладку Execution History:
Opening the Execution history tab for your Web3 Action

Открытие вкладки Execution history для Web3 Action

История выполнения предоставит вам следующие данные:
The execution history data

Данные истории выполнения

В верхней панели вы найдёте информацию о полезной нагрузке, приходящей из цепочки. Нижняя панель содержит детали о залогированном номере игры. В нашем случае это 0xb, что означает, что это 11-я игра, начатая для этого контракта.

2.6. Проверьте Storage вашего Web3 Action

Нажмите кнопку «Go To Storage», чтобы увидеть содержимое Storage. Каждая инициированная игра получит свой слот в этой карте ключ-значение. Когда вы откроете только что созданную игру с ID 11, вы увидите нули во всех полях, что означает, что ни один игрок ещё не играл.
Opening the Storage of your Web3 Action

Открытие Storage вашего Web3 Action

3: Добавьте Web3 Action для обработки присоединения новых игроков к игре

Присоединение игрока к игре должно обрабатываться регистрацией адреса игрока и его хода (1 или 2). Помимо шаблонного кода, который помогает получить данные события, файл playerJoinAction.ts также содержит код, позволяющий:
  • Читать текущее состояние игры из storage по ID игры (storage.getJson).
  • Получать адрес игрока и сохранять его ход: game.players[player] = playerNumber
  • Сохранять обновлённый объект игры в storage Web3 Action с помощью storage.putJson.
example.ts

3.1. Задайте вызов PlayerJoinedGame Action

Нам также нужно расширить specs в файле tenderly.yaml, чтобы включить спецификации, необходимые для вызова Web3 Action.
example.yaml

3.2. Разверните action в Tenderly

Разверните ваш Web3 Action, выполнив команду deploy. Эта команда также переразвернёт ранее развёрнутые Web3 Actions.
example.js

3.3. Попробуйте присоединиться к новой игре

Присоединитесь к новой игре, перейдя в Remix и добавив номер игры из логов в поле ввода newGame. Чтобы отправить транзакцию, нажмите кнопку joinGame:
Joining a new game in Remix

Присоединение к новой игре в Remix

После того как транзакция заминирована, в логах выполнения будет отображаться playerNumber: 1.
The execution log

Лог выполнения

Чтобы играть в игру, переключитесь на второй аккаунт в MetaMask и снова нажмите joinGame. После завершения транзакции вы увидите похожий вывод в Execution History.

3.4. Проверьте Storage вашего Web3 Action

Opening the execution of your Web3 Action

Открытие выполнения вашего Web3 Action

Откройте любое из этих выполнений и нажмите кнопку «Go To Storage». Игра, к которой присоединились наши игроки, имеет ID 11, и это состояние до того, как был сделан какой-либо ход. Доска Tic-Tac-Toe содержит только нули, и карта из Ethereum-адреса в ход игрока присутствует.
Opening the Storage key value

Открытие значения ключа Storage

3.5. Добавьте Action для обработки хода игрока

Используя Ethers, мы получаем ID игры и загружаем экземпляр игры из storage. Далее обновляем поле в строке result.boardRow и колонке result.boardCol вводом игрока game.players[player]. Функция processNewGameState будет выводить доску в консоль, но также вы можете отправить твит при ходе, вызвать новую транзакцию в цепочке или использовать её любым другим способом.
example.ts
Соответствующая спецификация для этого action:
example.yaml

3.6. Сделайте ход, чтобы сыграть в игру

Чтобы сделать ход, используйте MetaMask для переключения на первого игрока, присоединившегося к игре. Далее введите полученный номер игры, а также строку и колонку на доске (0, 1).
Making a move in Remix

Ход в Remix

Когда action выполнится, вывод должен выглядеть так:
The Web3 Action execution output

Вывод выполнения Web3 Action

Шаг 4. Добавьте Web3 Action для обработки окончания игры

Ниже приведён код для события GameOver. Событие GameOver срабатывает, когда игрок делает ход, выигрывающий игру, или когда доска заполнена. Это событие срабатывает после события PlayerMadeMove. В списке txEvent.logs событие GameOver — это второй элемент. Простой способ получить событие GameOver — обратиться к нему через txEvent.logs[1]. Однако мы реализуем более надёжное решение, не зависящее от порядка и количества сгенерированных событий. Сначала нужно получить gameOverTopic через ethers с помощью iface.getEventTopics. Это даст вам соответствующее шестнадцатеричное значение. Далее нужно найти запись лога в txEvent.logs, список topic’ов которой содержит gameOverTopic. Это лог нашего события GameOver, который мы можем декодировать с помощью Ethers.
example.js

4.1. Задайте вызов GameOver Action

Наконец, нам нужно добавить спецификацию для вызова action GameOver, расширив specs в tenderly.yaml:
example.yaml

4.2. Играйте в игру

Продолжайте играть, пока один из игроков не выиграет или доска не будет полностью заполнена. В конце игры Execution History должен выглядеть так:
The execution history at the end of the game

История выполнения в конце игры