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

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

Цель этого проекта — научить вас создавать Web3 Action, который получает данные из Web2 API всякий раз, когда смарт-контракт делает запрос данных через on-chain событие. Эти данные затем отправляются в контракт, запросивший их. Вот краткое описание процесса проекта.
  • Создаём Consumer — смарт-контракт, запрашивающий цену ETH у Oracle Contract.
  • Oracle Contract запрашивает новую цену монеты у CoinGecko API с помощью события RequestCoinPrice.
  • Как только запрос цены обнаружен, вызывается Web3 Action для получения значения.
  • Значение цены отправляется обратно в Oracle Contract.
The interactions between different components
Для этого проекта мы используем готовый смарт-контракт. Свободно изучайте код и адаптируйте его под свой сценарий. Обратите внимание, что контракт 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-репозитории. Контракт CoinOracle должен быть развёрнут первым, до контракта SimpleCoinConsumer, желательно с другого кошелька. Обязательно передайте адрес CoinOracle как параметр конструктора. Для целей этого туториала мы развернём контракт в тестовой сети Ropsten (ID 3), но вы можете использовать любую другую.

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

Для этого туториала вам понадобится доступ к Tenderly Dashboard. Если у вас нет аккаунта, зарегистрируйтесь здесь бесплатно (карту привязывать не нужно).
Установите Tenderly CLI (следуйте этому руководству по установке). С установленным на машине Tenderly CLI создайте новый каталог tdly-actions и перейдите в него (cd), чтобы инициализировать ваши Web3 Actions командой tenderly actions init.
$ cd tdly-actions
$ tenderly actions init
Когда будет предложено, выберите ваши проекты Tenderly, куда вы хотите развернуть Web3 Actions. Выведите содержимое каталога, чтобы изучить файлы, созданные CLI:
example
ls actions
example.ts
tsconfig.json
package.json
Tenderly CLI также создаст npm-проект, что позволит вашим Web3 Actions использовать любой npm-пакет. Установите NPM-зависимости — мы будем использовать Axios для запросов к внешнему API и Ethers для взаимодействия с блокчейном. Выполните следующую команду в терминале, чтобы добавить эти зависимости в ваш npm-проект:
example
cd actions && npm install --save-dev axios ethers @types/node && cd ..

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

Прежде чем двигаться дальше, скопируйте файл CoinOracleContract.json в каталог actions. Вы можете найти этот JSON-файл в Remix или в этом GitHub-репозитории.
Все файлы, которые использует и на которые ссылается код вашего Web3 Action, должны быть размещены в каталоге actions (например, CoinOracleContract.json).
Задайте переменной CONTRACT_ADDRESS адрес развёрнутого Oracle:
example.ts



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-модуля.
example.json
"esModuleInterop": true,
"resolveJsonModule": true
Обвязка: создайте интерфейс Ethers. Файл CoinOracleContract.json позволяет создать экземпляр Interface, который Ethers использует для кодирования и декодирования данных при взаимодействии со смарт-контрактами. Мы передаём часть abi всего JSON-файла. Получите данные события. Используйте только что созданный Interface (ifc), чтобы вызвать decodeEventLog. Мы знаем, что первая запись лога (logs[0]) соответствует событию RequestCoinPrice, поэтому именно её мы хотим декодировать. В более сложных взаимодействиях, возможно, потребуется делать это динамически, как мы сделали здесь. Необходимая Web3-обвязка, или шаблонный код. Для взаимодействия с нашим Oracle Contract нужно сделать некоторую обвязку. У контракта есть ограничение — обновления могут отправляться только owner. Это значит, что нам нужно отправлять транзакции с адреса, который его развернул. Обвязка выполняется в функции oracleContract, и вот разбор шагов. Обвязка: настройте объект Provider. Мы можем получить провайдер, используя ethers.getDefaultProvider для работы с сетью 3 (ID Ropsten). Также передаём второй аргумент — объект конфигурации, содержащий ключ API. Обратитесь к документации ethers для получения информации о том, как настраивать других провайдеров и использовать альтернативы вроде JsonRpcProvider. Обвязка: создайте Wallet. Нужно создать Wallet, чтобы каждая транзакция, исходящая от нашего оракула, была подписана и оплачена тем же адресом, который развернул контракт. Поскольку приватный ключ кошелька — чувствительная информация, мы читаем его из Secrets: await context.secrets.get("w3_oracle.oracle_address_private_key") Обвязка: создайте Contract. На этом шаге мы собираем всё созданное вместе:
  • Экземпляр Contract передачей CONTRACT_ADDRESS.
  • contractInterface для кодирования данных, которые мы хотим отправить в сеть.
  • oracleWallet для подписания транзакции.
🎉 Отправка данных обратно в 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:
example.yaml
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, чтобы узнать, что нужно для установления доступа. Так как мы используем Etherscan как провайдер, нам нужен только API-ключ. Ключ будет храниться в Web3 Actions Secrets.
Храните все чувствительные данные в Web3 Actions Secrets (например, ключи API, приватные ключи кошельков и т. д.). Secrets высоко защищены, так что даже если вы попытаетесь console.log значения, они останутся скрытыми.
Secrets. Перейдите в Tenderly Dashboard, чтобы разместить скопированный приватный ключ в Web3 Actions Secrets. На боковой панели перейдите в «Actions», нажмите «Secrets», а затем «Add New». Назовите секрет oracle.providerApiKey и вставьте токен API, а также любую другую необходимую чувствительную информацию.
Saving your private key to Web3 Action Secrets
Сохраните приватный ключ кошелька Oracle. Чтобы отправлять транзакции в цепочку, наш Web3 Action должен иметь приватный ключ для подписи и оплаты транзакций. OracleContract спроектирован так, чтобы принимать только те обновления, которые подписаны адресом, который его развернул. Если вы используете MetaMask, выберите аккаунт, с которого вы развернули смарт-контракт, и скопируйте его приватный ключ согласно этому руководству MetaMask. В Tenderly Dashboard создайте новый Secret под именем w3_oracle.oracle_address_private_key и вставьте приватный ключ.

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

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

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

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

Как отправить сообщение в Discord о новом пуле Uniswap