Розробка системи лояльності на токенах
Програма лояльності на токенах — це механіка утримання користувачів через економічні стимули, закріплені у смарт-контрактах. Ключова відмінність від традиційних програм (кешбек, бали в додатку) — прозорість нарахування та можливість користувачів торгувати або передавати накопилені активи. Користувач бачить свій баланс on-chain, а не у закритій базі даних компанії.
Для бізнесу це інструмент утримання та community building: користувачі, які накопили governance токени, стають стейкхолдерами, а не просто покупцями.
Типи loyalty-токенів
Вибір типу токена визначає всю механіку системи.
Non-transferable (soulbound) токени — не можуть бути продані або передані. Ідеально для: програм, де цінність — статус («Gold member»), не актив. Реалізується через переопеління _beforeTokenTransfer в ERC-721 з revert для будь-якого transfer крім mint/burn. Приклади: Otterspace, Disco.
Transferable ERC-20 points — накопилювані бали з ринковою ціною. Ризик: користувачі можуть купити статус без заробляння. Плюс: liquid rewards привабляють більше учасників. Підходить для протоколів з розвинутим secondary market.
Tiered NFT (ERC-1155) — різні рівні лояльності як токени. Tier 1 → Tier 2 → Tier 3 при досягненні порогів. Рівень може бути soulbound, а associated privileges — окремі transferable токени.
Hybrid: soulbound points-токен для підсумовування + окремий claim-able reward токен. Points не продати, але ними можна claim-ити цінні rewards. Це найпоширеніша production-модель (Blur використовує подібну схему).
Архітектура контракту
Points-токен з обмеженим transfer
contract LoyaltyPoints is ERC20 {
address public immutable minter; // тільки авторизований контракт
mapping(address => bool) public transferWhitelist;
modifier onlyMinter() {
require(msg.sender == minter, "Not minter");
_;
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal override {
// Дозволяємо: mint (from == 0), burn (to == 0),
// transfers у whitelist (reward контракт, staking)
if (from != address(0) && to != address(0)) {
require(transferWhitelist[to] || transferWhitelist[from], "Non-transferable");
}
}
function mint(address user, uint256 amount) external onlyMinter {
_mint(user, amount);
}
}
Whitelist включає адресу reward-контракту (для exchange points → rewards) та staking-контракту. Користувач не може відправити points напрямик іншому адресі.
Reward контракт
contract LoyaltyRewards {
LoyaltyPoints public immutable points;
IERC20 public immutable rewardToken;
// Розписання обміну: мінімальний поріг → reward amount
struct RewardTier {
uint256 pointsRequired;
uint256 rewardAmount;
uint256 cooldown; // між claim-ами
}
mapping(uint256 => RewardTier) public tiers;
mapping(address => uint256) public lastClaim;
function claimReward(uint256 tierId) external {
RewardTier memory tier = tiers[tierId];
require(points.balanceOf(msg.sender) >= tier.pointsRequired, "Insufficient points");
require(block.timestamp >= lastClaim[msg.sender] + tier.cooldown, "Cooldown active");
lastClaim[msg.sender] = block.timestamp;
// Сжигаємо points (або ні — залежить від моделі)
points.burnFrom(msg.sender, tier.pointsRequired);
rewardToken.safeTransfer(msg.sender, tier.rewardAmount);
}
}
Вибір: сжигати points при claim або ні — важливе дизайн-рішення. Сжигання створює дефляційну механіку та стимулює накоплення для більших rewards. Без сжигання — користувачи можуть отримувати rewards нескінченно при стабільному накопленні.
Механіки нарахування балів
On-chain триггери
Найпрозоріший варіант: контракт протоколу напрямик вызиває points.mint() при action користувача.
// Всередину DEX контракту
function swap(...) external {
// ... логіка swap ...
// Нараховуємо points пропорційно обсягу
uint256 pointsToMint = (amountIn * POINTS_PER_DOLLAR) / tokenPrice;
loyaltyPoints.mint(msg.sender, pointsToMint);
}
Простота підходу: повна прозорість, немає off-chain компонент. Обмеження: змінювати правила нарахування потребує upgrade основного контракту.
Off-chain розрахунок + Merkle claim
Більш гнучкий підхід: розрахунок відбувається off-chain (щодня або щотижня), користувач claim-ить накопилені points через Merkle proof. Дозволяє змінювати правила без upgrade, додавати складні розрахунки (cross-protocol активність, streak бонуси).
Streak та multiplier механіки
Daily streak: користувач, що взаємодіє N днів поспіль, отримує множник. Реалізація: on-chain зберігання lastActivityDay (block.timestamp / 86400), лічильник currentStreak. Gap > 1 день — streak скидається.
Volume tiers: накопилений обсяг за період визначає множник наступного періоду. Як авіаційні Silver/Gold/Platinum.
Governance інтеграція
Якщо loyalty points повинні давати voting power, стандарт — ERC-20Votes (OpenZeppelin). Додає checkpoint-механізм: баланс votes фіксується на кожному блоці змін, що запобігає flash loan voting атакам.
contract LoyaltyPoints is ERC20Votes {
// ERC20Votes додає delegate(), getPastVotes(), getPastTotalSupply()
// Користувач повинен явно вызвати delegate(address) або делегувати комусь, інакше voting power дорівнює нулю
}
Важливий нюанс: користувачі повинні явно вызвати delegate(address(this)) або делегувати комусь, інакше їхній voting power дорівнює нулю (навіть при великому балансі). Потрібно пояснити в UI.
Антифрод
Rate limiting: максимальна кількість points на транзакцію та на період (день/тиждень). Захищає від одноразового нарахування через велику транзакцію.
Activity verification: якщо нарахування через off-chain розрахунок, додати мінімальні пороги: мінімальний обсяг транзакції, мінімальний час між транзакціями (щоб bot-скрипт не міг генерувати тисячи дрібних).
Sybil resistance: points повинні нараховуватися тільки верифікованим кошелькам (Gitcoin Passport, World ID) якщо програма відкрита. Для закритих систем (KYC користувачі) — достатньо whitelist.
Стек та інтеграція
Solidity 0.8.x + OpenZeppelin 5.x (ERC20, ERC20Votes, AccessControl). Frontend: wagmi + viem + React для відображення балансу та claim UI. Для off-chain розрахунків: TypeScript + The Graph subgraph + PostgreSQL.
| Компонент | Термін розробки |
|---|---|
| Points контракт (ERC-20 + SBT логіка) | 1 тиждень |
| Reward контракт з тирами | 1–2 тижні |
| Off-chain розрахунковий сервіс | 2–3 тижні |
| Merkle claim система | 1 тиждень |
| Frontend інтеграція | 1–2 тижні |
MVP загалом: 4–6 тижнів. Production-система з antifraud, аналітикою та governance інтеграцією — 2–3 місяці.
Вартість залежить від типу токена, механік нарахування та наявного протоколу для інтеграції.







