Головна
попередній
четвертий урок
наступний

Як використо­вувати QuePasa в простому проєкті на Tezos

У минулому уроці ми показали як отримувати та використовувати ончейн-дані з публічного індексу TzKT. Це простий, але не зовсім безпечний спосіб: розробники публічного індексера можуть змінити API-запити, обмежити кількість запитів на секунду, на якийсь час перестати давати інформацію через технічні неполадки або передавати некоректні дані.

У цьому уроці ми покажемо, як захостити індексер на своїй машині чи сервері, щоб не залежати від третіх осіб. Показуватимемо на прикладі селективного індексера Que Pasa і бази даних PostgreSQL.

Що ми зробимо

Створимо невеликий проєкт з відображенням інформації про Signum — сервіс для видачі позик під заставу NFT. Для цього проіндексуємо смарт-контракт Signum і відображатимемо дані з нього на сторінці. Наприклад, останні пропозиції щодо позик, схвалені пропозиції та посилання на NFT у заставі.

Алгоритм роботи наступний:

  1. Вирішимо що буде в результаті, наприклад, відображення пропозицій по позиках.
  2. Перевіримо, які контракти зберігають необхідні дані. У нашому випадку Signum використовує лише один контракт, тому буде просто.
  3. Вивчимо структуру сховища смарт-контракту, зокрема його big_map-и. Наприклад, у контракті Signum є big_map "offers" із пропозиціями позик, в яку ще вбудований список "object" з інформацією про NFT у заставі.
    1
  4. Проіндексуємо сховище контракту.
  5. Перевіримо структуру отриманої бази даних та сплануємо, як отримуватимемо потрібні дані.
  6. Напишемо проєкт.

Встановлення та налаштування PostgreSQL та Que Pasa

Перейдіть на сайт PostgreSQL та скачайте версію для своєї ОС. Інсталятор попросить вибрати папку для встановлення, а також вказати пароль для користувача-адміна Postgres — не забудьте цей пароль.

Після встановлення на комп'ютері з'явиться кілька програм, пов'язаних із Postgre. Нам будуть потрібні pgAdmin — графічний інтерфейс для роботи з базами даних, і SQL Shell — шорткат для роботи з PostgreSQL у терміналі.

2

Для початку роботи з Postgres відкрийте SQL Shell. Термінал запитає параметри для підключення до бази даних: адресу сервера, назву бази даних, порт для підключення, ім'я користувача та пароль. Натисніть клавішу Enter чотири рази, а потім введіть пароль, який встановили під час встановлення.

3

Тепер необхідно встановити Que Pasa. Перший крок — установка Rust і Cargo, там треба просто дотримуватися інструкцій. Другий — встановлення Que Pasa з вихідного коду. Треба виконати у терміналі такі команди:

git clone https://github.com/tzConnectBerlin/que-pasa.git
cd que-pasa
cargo install --path .

Залишається налаштувати Que Pasa та PostgreSQL:

  1. Створити нову базу даних у Postgres для зберігання даних та індексів.
  2. Під'єднати Que Pasa до створеної бази даних.
  3. Вказати http-адресу RPC ноди, у якої Que Pasa отримуватиме ончейн-дані.
  4. Вказати адреси смарт-контрактів для індексації.

Створення нової бази даних

Відкрийте SQL Shell, під'єднайтесь до дефолтної бази даних (подивіться туторіал), введіть пароль. Потім виконайте команду

CREATE DATABASE liquidity;

Термінал видасть відповідь CREATE DATABASE. Потім виконайте команду \l, щоб перевірити результат. Вгорі списку має відобразитися нова база даних liquidity.

4

У колонці Access Privileges навпроти БД liquidity порожньо — потрібно дати користувачеві postgres (або іншому, якщо ви задали інше ім'я) право з нею працювати. Для цього виконайте команду:

GRANT ALL PRIVILEGES ON DATABASE "liquidity" to postgres;

5

Під’єднання бази даних до Que Pasa

Відкрийте термінал і перейдіть до папки із встановленим Que Pasa. Це можна зробити командою cd ~/que-pasa в Unix-системах, або ввести команду cd і перетягнути в термінал папку que-pasa, щоб автоматично прописати шлях.

У терміналі в цій папці виконайте наступні команди.

Спочатку ми створимо файл конфігурації .env за допомогою команди touch і перевіримо що в ньому є за допомогою cat:

touch .env
cat .env

Потім створимо змінну для RPC URL, від якої Que Pasa отримуватиме ончейн-дані.

export NODE_URL=https://mainnet.tezos.marigold.dev/

Створимо ще одну змінну з даними для під’єднання до бази даних:

export DATABASE_URL="host=localhost dbname=liquidity user=postgres password=1234 port=5432"

Тепер можна запустити індексер командою que-pasa, щоб перевірити, чи все працює. Якщо термінал видасть помилку "zero contracts to index..." — все гаразд.

6

Додавання контрактів для індексування

У папці з Que Pasa відкриємо файл settings.yaml і запишемо в нього внутрішню назву контракту для Que Pasa та його адресу. У прикладі ми індексуємо лише один контракт, але можна вказати кілька та індексувати їх одночасно.

у полі "name" запишіть "Liquidity", у полі "address" — адресу "KT1TxqZ8QtKvLu3V3JH7Gx58n7Co8pgtpQU5".

7

Запуск індексування

У терміналі, з якого ви працювали з Que Pasa, виконайте команду:

que-pasa --contract-settings settings.yaml

Que Pasa почне записувати в базу даних операції за вказаним контрактом, починаючи з останнього створеного блоку аж до genesis-блоку (якщо не вказати рівень, на якому слід зупинити індексування). Індексер автоматично конвертує весь зміст сховища контракту в таблиці: прості типи, списки, мапи, big_map та інші. Однак, щоб розібратися, де зберігаються якісь дані, потрібно добре вивчити готову базу даних.

Крім того, Que Pasa через Postgres автоматично створює базові b-tree індекси для кожної таблиці, щоб користувач швидко отримував дані за запитами. Якщо потрібно, у Postgres можна створити додаткові індекси для стовпців у psql shell або PgAdmin.

Повна індексація операцій та зміни стану сховища контракту може тривати кілька годин або навіть днів. Зараз ми використовуватимемо лише дані з останніх блоків, тому індексацію можна перервати натисканням Ctrl+C. Проіндексовані дані збережуться у базі даних, але Que Pasa не індексуватиме нові блоки. Якщо ви плануєте використовувати Que Pasa у продакшені та отримувати дані з нових блоків — не зупиняйте індексування.

8

Перегляд вмісту бази даних

Подивимося, що ми будемо робити з отриманими даними. Спочатку потрібно дізнатися, що взагалі у нас є, а для цього — вивчити базу даних, таблиці та їх вміст.

У Postgres для перегляду вмісту таблиць у базі даних використовується команда \d та її похідні. Спробуймо це зробити. Спочатку під'єднаємось до бази даних liquidity командою \c liquidity, потім виконаємо \d.

9

Отримаємо помилку "Did not find any relations". Ця помилка виникає через неправильно налаштований шлях пошуку таблиць і схем. Налаштуємо його за допомогою команди:

ALTER ROLE postgres IN DATABASE liquidity SET search_path TO "Liquidity";

Так ми змусимо Postgres шукати дані у базі даних liquidity за схемою "Liquidity". Звучить складно, але в результаті \d буде відображати всі таблиці та зв'язки між ними.

Que Pasa створює окрему таблицю для кожної точки входу, елемента storage та складних структур даних усередині storage. У таблицях, що починаються з "entry.", зберігаються виклики відповідних точок входу та їх параметри, а в "storage." — зміст сховища.

10

Щоб переглянути зміст таблиць, можна скористатися командою select * from "table_name". Для прикладу отримаємо транзакції з внесення ліквідності Sirius DEX:

select * from "entry.addLiquidity";

11

За назвами колонок "owner" та "minLqMinted" одразу зрозуміло, що в них зберігається, але є дві колонки, зміст яких потрібно пояснити:

  • tx_context_id — внутрішній ID операцій, включених у конкретний виклик смарт-контракту. Наприклад, якщо користувач бачить дві транзакції в одну, то в обох буде однаковий tx_context_id;
  • id — внутрішній id конкретної операції. Кожен рядок будь-якої таблиці бази даних матиме унікальний ID.

Повна індексація контракту та _live

Повна індексація всіх операцій зі смарт-контрактом може тривати кілька днів. Отже, для наступних прикладів ми будемо використовувати контракт NFT-лендингового сервісу Signum. Цей dApp дозволяє користувачам брати в позики tez використовуючи їх NFT як заставу. Ми проіндексуємо його сховище та виклики точок входу, а потім використовуємо SQL для фільтрації вмісту. Наприклад, запитаємо найбільші схвалені заявки на видачу позики або заявки з найбільшим APY для позикодавців, і навіть згенеруємо посилання на закладені NFT.

Є ще одна причина, чому ми використовуємо Signum у прикладі. Команда задеплоїла його контракт за кілька днів до написання цього уроку і Que Pasa повністю проіндексує його за кілька хвилин.

Щоб перемкнутися на Signum, потрібно замінити назву бази даних та адресу контракту KT1CXwDvsxboyXuX77ohut9KmoVfwuD4A8FK у файлі settings.yaml.

12

Потім у терміналі з Que Pasa запустимо індексацію контракту командою:

que-pasa --contract-settings settings.yaml

Коли індексер перестане зчитувати інформацію зі старих блоків, він синхронізується з мережею — почне завантажувати та обробляти нові блоки.

13

Тепер повернемося до бази даних та переглянемо таблиці. Спочатку змінимо пошуковий шлях командою ALTER ROLE postgres IN DATABASE liquidity SET search_path TO "Signum"; Потім відкриємо список таблиць командою \d.

14

Ми отримали ті самі таблиці для кожної точки входу, сховища та вбудованих big_map. Однак таблиці storage* мають дублікати з різними закінченнями та вмістом:

  • просто storage — таблиця з усіма даними з попередніх блоків;
  • storage_live — таблиця тільки з даними тільки з останнього блоку;
  • storage_ordered — таблиця відсортована у порядку, в якому мережа Tezos виконувала операції;
  • storage_id_seq — службова таблиця з id першого рядка.

Небагато прикладів: у "storage.offers" зберігаються дані про всі пропозиції про позики, які були опубліковані на Signum, а в "storage.offers_live" — ​​дані про актуальні пропозиції в контракті Signum.

У прикладі ми будемо використовувати дві таблиці: "storage_live" і "storage.offers.collateral". Перша містить дані про всі актуальні заявки, на кшталт адрес позичальників і сум, що запитуються, а друга — дані про закладені NFT. Обидві таблиці пов'язані внутрішніми id.

Загалом, щоб переглянути поточний стан сховища або big_map всередині нього, потрібно скористатися таблицями _live. Щоб отримати дані за попередні блоки — звичайними таблицями. Наприклад, команда select * from storage_live; поверне стан сховища контракту, рівень поточного блоку та час включення блоку до блокчейну (level_timestamp).

15

Під‘єднання Postgres до сайту для запиту даних

Для запиту даних ми будемо використовувати PHP-сервер на локальній машині, щоб не підключати API до бази даних.

Спочатку потрібно встановити PHP за інструкцією. Потім створити папку, в якій зберігатимемо скрипт для відображення даних з Singum, наприклад signum_test. Після цього відкрити термінал, перейти в папку та виконати команду:

php -S localhost:8000

Команда запустить локальний PHP-сервер. На нього вже можна перейти за посиланням http://localhost:8000/

16

Рядки з таймштампами відображають запити та відповіді на сервер.

Тепер можна розпочати роботу над сторінкою. Створимо у папці файл signum.php та почнемо писати код. Спочатку — основа: head, body, назва сторінки та текст для тесту.

17

Збережемо скрипт у папці signum_test і перейдемо на нього в браузері за посиланням http://localhost:8000/signum.php.

18

Під’єднаємось до бази даних. Для цього задамо змінні $dbhost, $dbname, $dbuser та $dbpass з відповідними значеннями: адресою localhost, назвою бази даних, ім'ям користувача postgres та паролем.

Створимо змінну $dbconn, через яку керуватимемо підключенням. Спочатку виконаємо функцію pg_coonnect() для під’єднання до PostgreSQL з даними користувача та додамо функцію or die() для вилову помилок. Насамкінець додамо ще один фрагмент php для відключення від PostgreSQL після завершення завантаження сторінки.

19

Перезавантажимо сторінку signum.php. Якщо немає помилок — все добре.

Перш ніж запитувати дані з бази даних, вивчимо сховище контракту Signum і виберемо, що саме потрібно отримати. У таблиці storage.offers зберігаються дані про заявки позичальників: сума позики (amount), відсоток за позикою (premium), термін повернення позики (deadline), адреси позичальника та кредитора. Для початку запитаємо суми позик кількох заявок.

20

Для цього додамо до скрипту SQL-запит:

SELECT "amount" FROM "storage.offers_live" WHERE "amount" IS NOT NULL LIMIT 10

Команда означає наступне:

  • SELECT "amount" — отримати записи з колонки amount;
  • FROM "storage.offers_live" — ​​покажчик на таблицю storage.offers_live;
  • WHERE "amount" IS NOT NULL — умова, що у вибраному рядку має бути значення amount;
  • LIMIT 10 — обмежити результат лише 10 значеннями.

Надамо цей запит змінної $sql. Потім виконаємо функцію цього запиту — $result = pg_query(). В pg_query() передамо два параметри: $dbconn з конфігурацією для підключення до бази даних і $sql з самим запитом.

Насамкінець подивимося результат. Оскільки pg_query() поверне дані у вигляді ряду, ми за допомогою петлі while та функції pg_fetch_array() перенесемо всі елементи в масив і відобразимо їх по черзі. Спочатку присвоїмо змінній $row результат виконання функції pg_fetch_array() на отриманому результаті. Потім у петлі виводитимемо кожен елемент масиву $row["amount"] за допомогою команди echo і розділятимемо елементи комою з пробілом (", ").

21

Перевіримо, що вийшло. Для цього перезавантажимо сторінку.

22

Ускладнимо запит. Ми можемо отримати дані про позики, які вже схвалив позичальник. Для цього додамо фільтр WHERE "amount" IS NOT NULL AND "lender" IS NOT NULL.

Також порахуємо прибуток кредиторів у річних відсотках (APR). Для цього нам потрібно отримати суму позики, відсоток за користування коштами (premium) та термін позики (deadline).

У результаті зробимо такий запит:

23

Результат:

24

Оформимо результат у зручнішу для читання таблицю. Для цього скористаємось php-елементом

. Розділимо значення amount і premium на 1000000, щоб показати нормальну кількість tez, оскільки блокчейн зберігає баланси у мільйонних частках tez — mutez.

25

В результаті буде табличка:

26

Тепер порахуємо APR позик. Для цього додамо в таблицю колонку "Lender's APR", а в петлю while — формулу розрахунку APR. Призначимо змінні кожної ітерації: $p — премія, $a — сума позики, $d — термін повернення позики, $dd — співвідношення терміну позики до кількості днів у року, $apr — значення APR за такою формулою.

27

Перезавантажимо сторінку, щоб переглянути результат.

28

Тепер можна експериментувати. Наприклад, змінити фільтр у SQL-запиті, щоб отримати дані про найбільші активні заявки:

SELECT "amount", "premium", "deadline" FROM "storage.offers_live" WHERE "amount" IS NOT NULL AND "lender" IS NULL ORDER BY "amount" DESC LIMIT 10

29

Тепер використовуємо реляційну базу даних як задумано: отримаємо пов'язані дані з двох таблиць за внутрішнім id.

У базі даних контракту є таблиця storage.offers.collateral, де зберігаються адреси контрактів і Token ID закладених NFT. Ми можемо використовувати внутрішній id (значення tx_context_id) з таблиці storage.offers, щоб знайти відповідні рядки в таблиці storage.offers.collateral. А далі, знаючи адресу та Token ID, ми можемо згенерувати посилання на токен у браузері TzKT або Objkt.com.

Отже, в першому SQL-запиті нам потрібно отримати tx_context_id, щоб потім використовувати ці значення у запиті таблиці storage.offers.collateral.

Додамо запит "tx_context_id".

30

У петлю while додамо змінну $id, якій надамо значення tx_context_id. Потім напишемо SQL-запит $sql2, який запитуватиме адресу контракту і id токена з таблиці storage.offers.collateral з тими самими значеннями tx_context_id, що ми отримали з першого запиту. Насамкінець виконаємо запит функцією pg_querry() і запишемо його в змінну $row2.

Використовувати кілька SQL-запитів — не найкраща практика, оскільки це можна зробити за допомогою команди JOIN, але ми вирішили зробити так для наочності та простоти прикладу.

31

Нарешті додамо до таблиці колонку "Link to Token", а нижче — рядок з посиланням на TzKT. Структура наступна:

tzkt.io/ + адреса контракту + /tokens/ + token id + /transfers

32

Збережемо сторінку та перевіримо результат.

33

За посиланням, ми перейдемо на історію операцій токенів на TzKT, серед яких буде і операція з контрактом Signum.

34

Код скрипта на Gist.

Домашнє завдання

1.Змініть у таблиці посилання TzKT на посилання Objkt.com. Для цього вивчіть структуру посилання NFT на Objkt.com і відредагуйте посилання в таблиці. 2. Відсортуйте перший SQL-запит за сумою позики, від більшої до меншої.

Рішення

Посилання на NFT на Objkt.com просте: воно складається з objkt.com/asssets/, адреси контракту та id токена.

35

Замінимо посилання у таблиці на таке.

36

Потім до SQL-запиту додайте фільтр ORDER BY "amount" DESC.

37

Поділитися в соцмережах: