> ## 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.

# Как создать собственный оракул

> Узнайте, как использовать Web3 Actions для создания собственного оракула, собирающего данные из систем реального мира.

В этом туториале мы покажем, как **создать собственный Web3-оракул с помощью Tenderly Web3 Actions**. Оракул собирает данные из систем реального мира и отправляет их в блокчейн. Он выступает точкой входа для передачи данных из Web2-приложений в смарт-контракты.

Используя оракулы, мы можем получать в наших смарт-контрактах доступ к данным из источников вне блокчейна (например, биржи, данные о трафике и погоде, ставки газа, цены на нефть и т. д.).

<Note>
  Полный код доступен в [**этом Github-репозитории**](https://github.com/Tenderly/examples-web3-actions/).
</Note>

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

Цель этого проекта — научить вас создавать Web3 Action, который получает данные из Web2 API всякий раз, когда смарт-контракт делает запрос данных через on-chain событие. Эти данные затем отправляются в контракт, запросивший их.

Вот краткое описание процесса проекта.

* Создаём Consumer — смарт-контракт, запрашивающий цену ETH у Oracle Contract.
* Oracle Contract запрашивает новую цену монеты у CoinGecko API с помощью события `RequestCoinPrice`.
* Как только запрос цены обнаружен, вызывается Web3 Action для получения значения.
* Значение цены отправляется обратно в Oracle Contract.

<Frame caption="Взаимодействия между различными компонентами">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/web3-actions/how-to-build-a-custom-oracle-1.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=5837f020e024bd52aecdfc4d20a18f39" alt="The interactions between different components" width="1705" height="898" data-path="images/web3-actions/how-to-build-a-custom-oracle-1.webp" />
</Frame>

Для этого проекта мы используем готовый смарт-контракт. Свободно изучайте код и адаптируйте его под свой сценарий.

Обратите внимание, что контракт `SimpleConsumer` принимает `coinPrice` только от того `CoinOracle`, который был ему назначен при развёртывании. Кроме того, `CoinOracle` принимает обновления только если они подписаны тем же кошельком, который его развернул (`owner`).

## План

Вот список шагов, которые нам нужно предпринять для построения нашего оракула:

* Развернуть смарт-контракты `CoinOracle` и `SimpleCoinConsumer`.
* Получить ключ API и другую информацию, необходимую для использования одного из провайдеров Ethereum. Эти данные мы сохраним в Web3 Actions Secrets (*нужно для взаимодействия 5*).
* Получить приватный ключ аккаунта, развернувшего Oracle Contract (`owner`), и сохранить его в Web3 Actions Secrets (*нужно для подписи транзакций во взаимодействии 5*).
* Инициализировать каталог Web3 Actions Tenderly в вашей среде разработки и добавить npm-зависимости.
* Создать Web3 Action, который будет отправлять обновления цен в оракул.

### 1: Разверните смарт-контракты

Оба контракта, `CoinOracle` и `SimpleCoinConsumer`, хранятся в одном файле: `CoinOracle.sol`. Ознакомьтесь с исходным кодом [**в нашем GitHub-репозитории**](https://github.com/Tenderly/examples-web3-actions/blob/main/simple-coin-oracle/CoinOracle.sol).

Контракт `CoinOracle` должен быть развёрнут первым, до контракта `SimpleCoinConsumer`, желательно с другого кошелька. Обязательно передайте адрес `CoinOracle` как параметр конструктора.

Для целей этого туториала мы развернём контракт в тестовой сети Ropsten (ID 3), но вы можете использовать любую другую.

### 2: Инициализация Web3 Actions

<Warning>
  Для этого туториала вам понадобится доступ к **Tenderly Dashboard**. Если у вас нет аккаунта,
  [**зарегистрируйтесь здесь бесплатно**](https://dashboard.tenderly.co/register?utm_source=homepage) (карту привязывать не нужно).
</Warning>

**Установите Tenderly CLI (следуйте** [**этому руководству по установке**](https://github.com/Tenderly/tenderly-cli#installation)**)**. С установленным на машине Tenderly CLI создайте новый каталог `tdly-actions` и перейдите в него (`cd`), чтобы инициализировать ваши Web3 Actions командой `tenderly actions init`.

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

Когда будет предложено, выберите ваши проекты Tenderly, куда вы хотите развернуть Web3 Actions. Выведите содержимое каталога, чтобы изучить файлы, созданные CLI:

```bash title="example" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
ls actions
example.ts
tsconfig.json
package.json
```

Tenderly CLI также создаст npm-проект, что позволит вашим Web3 Actions использовать любой npm-пакет.

**Установите NPM-зависимости** — мы будем использовать Axios для запросов к внешнему API и Ethers для взаимодействия с блокчейном. Выполните следующую команду в терминале, чтобы добавить эти зависимости в ваш npm-проект:

```bash title="example" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
cd actions && npm install --save-dev axios ethers @types/node && cd ..
```

### 3: Создайте свой Web3 Action

Прежде чем двигаться дальше, скопируйте файл `CoinOracleContract.json` в каталог `actions`. **Вы можете найти этот JSON-файл в Remix или** [**в этом GitHub-репозитории**](https://github.com/Tenderly/examples-web3-actions/blob/main/simple-coin-oracle/actions/CoinOracleContract.json).

<Warning>
  Все файлы, которые использует и на которые ссылается код вашего Web3 Action, должны быть размещены в{' '}
  каталоге `actions` (например,{' '}
  `CoinOracleContract.json`).
</Warning>

Задайте переменной `CONTRACT_ADDRESS` адрес развёрнутого `Oracle`:

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



const CONTRACT_ADDRESS = '...'; // replace with contract address

export const coinPrice: ActionFn = async (context: Context, event: Event) => {
  let transactionEvent = event as TransactionEvent;

  const ifc = new ethers.utils.Interface(CoinOracleContract.abi);

  const { data, topics } = transactionEvent.logs[0];
  const priceRequest = ifc.decodeEventLog('RequestCoinPrice', data, topics);
  const price = await getPrice();

  const oc = await oracleContract(context, ifc);

  await oc.update(priceRequest.reqId, price, {
    gasLimit: 250000,
    gasPrice: ethers.utils.parseUnits('100', 'gwei'),
  });
  console.log(`Processed: ${priceRequest.reqId} with price in cents: ${price}`);
};

const getPrice = async (coin: string) => {
  const coinInfo = await axios.get(`https://api.coingecko.com/api/v3/coins/${coin}`);
  return coinInfo.data.market_data.current_price.usd * 100;
};

const oracleContract = async (context: Context, contractInterface: ethers.utils.Interface) => {
  const etherscanApiKey = await context.secrets.get('oracle.providerApiKey');

  const provider = ethers.getDefaultProvider(ethers.providers.getNetwork(3), {
    etherscan: etherscanApiKey,
  });

  const oracleWallet = new ethers.Wallet(
    await context.secrets.get('oracle.addressPrivateKey'),
    provider,
  );

  const contract = new ethers.Contract(CONTRACT_ADDRESS, contractInterface, oracleWallet);
  return contract;
};
```

Наш Web3 Action будет делать вызов к CoinGecko API с помощью Axios в `getPrice`. Полученный от API результат — это цена в центах. Эти данные мы отправим обратно в наш смарт-контракт.

Однако прежде чем двигаться дальше, нам нужно позаботиться о некоторых технических деталях.

**Побочная задача: разберитесь с Typescript.** Если TypeScript выдаёт ошибку, добавьте следующие две строки в ваш файл `tsconfig.json` в блок `compilerOptions`. Это заставит TypeScript не ругаться на импорт JSON-файла как ES-модуля.

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

**Обвязка: создайте интерфейс Ethers.** Файл `CoinOracleContract.json` позволяет создать экземпляр `Interface`, который Ethers использует для кодирования и декодирования данных при взаимодействии со смарт-контрактами. Мы передаём часть `abi` всего JSON-файла.

**Получите данные события.** Используйте только что созданный `Interface` (`ifc`), чтобы вызвать `decodeEventLog`. Мы знаем, что первая запись лога (`logs[0]`) соответствует событию `RequestCoinPrice`, поэтому именно её мы хотим декодировать. В более сложных взаимодействиях, возможно, потребуется делать это динамически, [**как мы сделали здесь**](/monitoring/web3-actions/tutorials/on-chain-events).

**Необходимая Web3-обвязка, или шаблонный код.** Для взаимодействия с нашим Oracle Contract нужно сделать некоторую обвязку. У контракта есть ограничение — обновления могут отправляться только `owner`. Это значит, что нам нужно отправлять транзакции с адреса, который его развернул. Обвязка выполняется в функции `oracleContract`, и вот разбор шагов.

**Обвязка: настройте объект Provider.** Мы можем получить провайдер, используя `ethers.getDefaultProvider` для работы с сетью `3` (ID Ropsten). Также передаём второй аргумент — объект конфигурации, содержащий ключ API. Обратитесь к [**документации ethers**](https://docs.ethers.io/v5/api/providers/#providers-getDefaultProvider) для получения информации о том, как настраивать других провайдеров и использовать альтернативы вроде `JsonRpcProvider`.

**Обвязка: создайте Wallet.** Нужно создать `Wallet`, чтобы каждая транзакция, исходящая от нашего оракула, была подписана и оплачена тем же адресом, который развернул контракт. Поскольку приватный ключ кошелька — чувствительная информация, мы читаем его из *Secrets*:

`await context.secrets.get("w3_oracle.oracle_address_private_key")`

**Обвязка: создайте Contract.** На этом шаге мы собираем всё созданное вместе:

<ul className="mt-6 list-disc first:mt-0 space-y-2 ltr:ml-6 rtl:mr-6">
  <li>
    Экземпляр `Contract` передачей{' '}
    `CONTRACT_ADDRESS`.
  </li>

  <li>
    `contractInterface` для кодирования данных, которые мы хотим отправить
    в сеть.
  </li>

  <li>
    `oracleWallet` для подписания транзакции.
  </li>
</ul>

**🎉 Отправка данных обратно в Oracle Contract.** С завершённой обвязкой последняя строка вызывает функцию `receiveWeatherUpdate` и отправляет прогноз, полученный нашим оракулом. Этот участок отправляет транзакцию в наш смарт-контракт.

### 4: Укажите, когда выполнять ваш Web3 Action

Мы хотим выполнять транзакцию каждый раз, когда `OracleContract` в сети Ropsten генерирует событие `RequestCoinPrice`. Хотим делать это только после того, как транзакция была заминирована.

Замените `YOUR_USERNAME` и `YOUR_PROJECT_SLUG` на ваше имя пользователя Tenderly и slug вашего проекта. Их можно скопировать из URL-адреса Dashboard:

`https://dashboard.tenderly.co/{YOUR_USERNAME}/{YOUR_PROJECT_SLUG}/transactions`

Также замените `CONTRACT_ADDRESS` на адрес развёрнутого Oracle Contract:

```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:
      coinOracle:
        description: Swapit
        function: coinOracle:coinPrice
        trigger:
          type: transaction
          transaction:
            status:
              - mined
            filters:
              - network: 3
                eventEmitted:
                  contract:
                    address: CONTRACT_ADDRESS
                  name: RequestCoinPrice
project_slug: ''
```

### 5: Создайте Secrets для Web3 Action

Чтобы Web3 Actions выполняли операции с развёрнутым `OracleContract`, нам нужен способ обращения к контракту. Вы можете выбрать любую сервис-провайдера (Infura, QuickNode, Alchemy, Etherscan и т. д.). Проверьте [**документацию Ethers по Default Provider**](https://docs.ethers.io/v5/api/providers/#providers-getDefaultProvider), чтобы узнать, что нужно для установления доступа.

Так как мы используем Etherscan как провайдер, нам нужен только API-ключ. Ключ будет храниться в Web3 Actions Secrets.

<Warning>
  Храните все чувствительные данные в Web3 Actions Secrets (например, ключи API,
  приватные ключи кошельков и т. д.). Secrets высоко защищены, так что даже если вы попытаетесь{' '}
  `console.log` значения, они останутся скрытыми.
</Warning>

**Secrets.** Перейдите в Tenderly Dashboard, чтобы разместить скопированный приватный ключ в Web3 Actions Secrets. На боковой панели перейдите в «Actions», нажмите «Secrets», а затем «Add New». Назовите секрет `oracle.providerApiKey` и вставьте токен API, а также любую другую необходимую чувствительную информацию.

<Frame caption="Сохранение приватного ключа в Web3 Action Secrets">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/web3-actions/how-to-build-a-custom-oracle-2.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=3585ebe5e1b16be0eb1e18301b843c6d" alt="Saving your private key to Web3 Action Secrets" width="3604" height="2278" data-path="images/web3-actions/how-to-build-a-custom-oracle-2.webp" />
</Frame>

**Сохраните приватный ключ кошелька Oracle.** Чтобы отправлять транзакции в цепочку, наш Web3 Action должен иметь приватный ключ для подписи и оплаты транзакций. `OracleContract` спроектирован так, чтобы принимать только те обновления, которые подписаны адресом, который его развернул.

Если вы используете MetaMask, выберите аккаунт, с которого вы развернули смарт-контракт, и скопируйте его приватный ключ [**согласно этому руководству MetaMask**](https://metamask.zendesk.com/hc/en-us/articles/360015289632-How-to-Export-an-Account-Private-Key).

В Tenderly Dashboard создайте новый Secret под именем `w3_oracle.oracle_address_private_key` и вставьте приватный ключ.

### 6: Разверните ваш Web3 Action

Перейдите в корень вашего проекта (папка, содержащая файл `tenderly.yaml`) и выполните следующую команду:

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

После того как CLI покажет успешное развёртывание, вы можете перейти в Tenderly Dashboard. Там вы должны увидеть, что ваш Web3 Action развёрнут (или готовится к развёртыванию).

### 7: Запустите проект

Чтобы проверить, работает ли то, что мы создали, вернитесь в Remix и вызовите функцию `doSomethingSmart` контракта `SimpleCoinConsumer`.

Через некоторое время вы увидите выполнение во вкладке Execution в вашем Tenderly Dashboard. Также вы увидите новую транзакцию, отправленную с адреса оракула вашим Web3 Action, вызывающую функцию `update` контракта `OracleContract`. 🎉

<Card title="Как отправить сообщение в Discord о новом пуле Uniswap" href="" />
