Децентралізована аффілейт-програма з відкритим вихідним кодом на блокчейні Waves

Децентралізована аффілейт-програма на блокчейні Waves, реалізована в рамках гранту Waves Labs командою Bettex.

Пост не є рекламним! Програма має відкритий вихідний код, її використання та розповсюдження безкоштовно. Застосування програми стимулює розвиток dApp додатків та в цілому сприяє децентралізації, що йде на благо кожному користувачеві Мережі.

Децентралізована аффілейт-програма з відкритим вихідним кодом на блокчейні Waves

Представлений dApp для affiliate програм є шаблоном для проектів, що включають affiliate як частину свого функціоналу. Код може бути використаний як шаблон копіювання, як бібліотека або як набір ідей для технічної реалізації.

За функціоналом це звичайна affiliate-система, що реалізує реєстрацію із зазначенням реферера, багаторівневе нарахування винагород за рефералів та мотивацію для реєстрації в системі (кешбек). Система є “чистим” dApp, тобто веб-додаток взаємодіє безпосередньо з блокчейном, не маючи свого бекенда, бази даних тощо.

Використовуються техніки, які також можуть бути корисними у багатьох інших проектах:

  • Виклик смарт-акаунта в борг з негайним погашенням (у момент виклику на обліковому записі немає токенів для оплати виклику, але вони з'являються там в результаті виклику).
  • PoW-captcha — захист від високочастотного автоматизованого виклику функцій смарт-акаунта — аналог капчі, але через доказ використання обчислювальних ресурсів.
  • Запит до ключів data за шаблоном.

Додаток складається з:

  • коду смарт-акаунта мовою ride4dapps (який за задумом мерзнеться в основний смарт-аккаунт, для якого треба реалізувати affiliate-функціонал);
  • js-обгортки, що реалізує рівень абстракції над WAVES NODE REST API;
  • коду на vuejs framework, що є прикладом використання бібліотеки та RIDE-коду.

Опишемо всі ці особливості.

Виклик смарт-акаунту в борг із негайним погашенням

Виклик InvokeScript вимагає оплати комісії з облікового запису, що ініціює транзакцію. Це не проблема, якщо ви робите проект для блокчейн-гіків, у яких на обліковому записі є певна кількість токенів WAVES, але якщо продукт орієнтований на використання в широких масах - це стає серйозною проблемою. Адже користувач повинен подбати про покупку токенів WAVES (або іншого відповідного ассета, яким можна оплачувати транзакції), що підвищує і без того чималий поріг входу в проект. Ми можемо роздати користувачам ассет, яким дозволить сплатити транзакції та зіткнемося з ризиком їх нецільового використання, коли створюються автоматизовані системи для викачування ліквідного ассета з нашої системи.

Було б дуже зручно, якби була можливість здійснювати виклик InvokeScript "за рахунок одержувача" (того смарт-акаунта, на якому встановлено скрипт), і така можливість, хоч і не очевидним чином, існує.

Якщо всередині InvokeScript зробити ScriptTransfer на адресу викликаючого, який компенсує витрачені на fee токени, такий виклик пройде успішно, навіть якщо в момент виклику на обліковому записі не було ніяких ассетів. Це можливо завдяки тому, що перевірка наявності достатньої кількості токенів здійснюється після виклику транзакції, а не перед нею, так що можна робити транзакції в кредит за умови їхнього негайного погашення.

ScriptTransfer(i.caller, i.fee, unit)

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

func checkFee(i:Invocation) = {
if i.fee > maxFee then throw(“unreasonable large fee”) else
if i.feeAssetId != unit then throw(“fee must be in WAVES”) else true
}

Також для захисту від зловмисної та безглуздої витрати коштів необхідний захист від автоматичного виклику (PoW-captcha)

PoW-captcha

Сама ідея proof-of-work captcha не є новою і вже реалізована в різних проектах, у тому числі реалізованих на базі WAVES. Сенс ідеї в тому, щоб для здійснення дії, що витрачає ресурси нашого проекту, що викликає повинен витратити і свої власні ресурси, що робить атаку на виснаження ресурсів досить витратною. Для дуже легкої та маловитратної валідації того, що відправник транзакції вирішив PoW-завдання, існує перевірка id транзакції:

if take(toBase58String(i.transactionId), 3) != “123” then throw(“proof of work failed”) else

Для того, щоб провести транзакцію, той, що викликає, повинен підібрати такі параметри, щоб її base58 код (id) починався на цифри 123, що відповідає в середньому парі десятків секунд процесорного часу і в цілому розумно для нашого завдання. Якщо потрібно більш простий чи складніший PoW, то завдання легко доопрацювати очевидним способом.

Запит до ключів data за шаблоном

Для того, щоб використовувати blockchain як базу даних, життєво необхідно мати інструменти API для запитів до бази як key-val за шаблонами. Такий інструментарій з'явився на початку липня 2019 року у вигляді параметра ?matches у запиту REST API /addresses/data?matches=regexp. Тепер, якщо нам потрібно з веб-програми отримати не один ключ і не всі ключі відразу, а лише якусь групу, то можна зробити вибірку на ім'я ключа. Наприклад, у даному проекті транзакції виведення коштів кодуються у вигляді

withdraw_${userAddress}_${txid}

що дозволяє отримати список транзакцій на виведення коштів для будь-якої заданої адреси за шаблоном:

?matches=withdraw_${userAddress}_.*

Тепер розберемо складові готового рішення.

Код на vuejs

Код є робочою демонстрацією, наближеною до реального проекту. Він реалізує вхід через Waves Keeper та роботу з бібліотекою affiliate.js, за допомогою якої він реєструє користувача в системі, опитує дані про транзакції, а також дозволяє виводити зароблені кошти на обліковий запис користувача.

Децентралізована аффілейт-програма з відкритим вихідним кодом на блокчейні Waves

Код RIDE

Складається з функцій register, fund і withdraw.

Функція register реєструє у системі користувача. У неї два параметри: referer (адреса рефера) і параметр salt, що не використовується в коді функції, який потрібен для підбору id транзакції (завдання PoW-captcha).

Функція (як і інші функції з цього проекту) використовує техніку виклику у борг, результатом функції є фінансування виплати fee за виклик цієї функції. Завдяки даному рішенню, користувач, що тільки що створив гаманець, відразу може працювати з системою і йому не потрібно спантеличуватися питанням придбання або отримання ассета, що дозволяє оплатити комісію за транзакцію.

Результатом роботи функції реєстрації є два записи:

${owner)_referer = referer
${referer}_referral_${owner} = owner

Це дозволяє проводити прямий і зворотний пошук (реферер даного користувача та всі реферали даного користувача).

Функція fund є скоріше шаблоном розробки цього функціонала. У поданому вигляді вона бере всі кошти, що перераховуються транзакцією, і розподіляє їх на акаунти реферерів 1, 2, 3 рівня, на акаунт "кешбека" і акаунт "здавання" (сюди потрапляє все, що залишилося при розподілі по попередніх акаунтах).

Кешбек - це засіб стимулювання кінцевого користувача для участі в реферальній системі. Виплачувану частину комісії системою у вигляді "кешбеку" користувач може зняти так само, як винагороди за рефералів.

При використанні реферальної системи функцію fund слід видозмінити, вбудувати в основну логіку смарт-аккаунта, на якому працюватиме система. Наприклад, якщо реферальна винагорода виплачується за зроблену ставку, то функція fund повинна бути вбудована в логіку, де робиться ставка (або виконується інша цільова дія, за яку виплачується винагорода). У цю функцію закодовано три рівні реферальних винагород. Якщо потрібно зробити більше або менше рівнів, це також правиться в коді. Відсоток винагороди задається константами level1-level3, у коді він вважається як amount * level / 1000тобто значення 1 відповідає 0,1% (це теж можна змінювати в коді).

Виклик функції змінює баланс облікового запису і створює записи для цілей логування виду:

fund_address_txid = address:owner:inc:level:timestamp
Для получения timestamp (текущего времени) используется такая вот связка
func getTimestamp() = {
let block = extract(blockInfoByHeight(height))
toString(block.timestamp)
}

Тобто час транзакції - це час блоку, в якій він знаходиться. Це надійніше, ніж використовувати timestamp з самої транзакції, тим більше, що він не доступний з callable.
Функція withdraw виводить усі накопичені винагороди на обліковий запис користувача. Створює записи для цілей логування:

# withdraw log: withdraw_user_txid=amount:timestamp

додаток

Основна частина програми - бібліотека affiliate.js, що є мостом між моделями даних affiliate і WAVES NODE REST API. Реалізує рівень абстракції, незалежний від фреймворку (може бути використаний будь-хто). Активні функції (register, withdraw) припускають, що у системі встановлено Waves Keeper, сама бібліотека це перевіряє.

Реалізує методи:

fetchReferralTransactions
fetchWithdrawTransactions
fetchMyBalance
fetchReferrals
fetchReferer
withdraw
register

Функціонал методів очевидний з назв, параметри та дані, що повертаються описані в коді. Додаткових коментарів вимагає функція register — вона запускає цикл підбору id транзакції, щоб він починався на 123 — це описана вище PoW-captcha, що захищає від масових реєстрацій. Функція знаходить транзакцію з потрібним ID, а потім підписує через Waves Keeper.

DEX affiliate програма доступна на GitHub.com.

Джерело: habr.com

Додати коментар або відгук