Розробка системи підписок на контент за токени
Токен-гейтинг контенту — це не Patreon на блокчейні. Принципіальна відмінність: платежі через смарт-контракт без посередника, creator отримує дохід напрямик, підписка може бути NFT з вторинним ринком, доступ верифікується on-chain без централізованого сервера авторизації.
Реалізувати це коректно складніше, ніж кажется, — особливо у частині верифікації доступу до off-chain контенту (відео, статті, файли).
Моделі підписки: ERC-20 vs ERC-721 vs ERC-1155
ERC-20 staking: користувач стейкує визначену кількість токенів creator'а — отримує доступ. Проста модель, але стейк замораховує ліквідність.
ERC-721 subscription NFT: кожна активна підписка — окремий NFT з expiry timestamp у метаданих або контракті. Продаваний, трансфабельний. Ідеально для «пожизненних» підписок або обмежених тирів.
ERC-1155 timed access: полуфунгібельні токени з привязкою до періоду. Наприклад, tokenId = 202504 — доступ на квітень 2025. Економить газ з batch transfers, логично для щомісячних підписок.
contract ContentSubscription {
struct SubscriptionTier {
uint256 pricePerMonth; // у wei або ERC-20 токенах
address paymentToken; // address(0) = native ETH
uint256 maxSubscribers; // 0 = unlimited
string contentCID; // IPFS CID для зашифрованого контенту
bytes32 encryptionKeyHash; // хеш ключа шифрування для цього тиру
}
struct Subscription {
uint256 tierId;
uint256 expiresAt;
uint256 startedAt;
bool autoRenew;
}
mapping(uint256 => SubscriptionTier) public tiers;
mapping(address => mapping(uint256 => Subscription)) public subscriptions;
address public creator;
uint256 public platformFeeBps = 250; // 2.5%
modifier onlyCreator() {
require(msg.sender == creator, "Not creator");
_;
}
function subscribe(uint256 tierId, uint256 months, bool autoRenew) external payable {
SubscriptionTier memory tier = tiers[tierId];
uint256 totalCost = tier.pricePerMonth * months;
if (tier.paymentToken == address(0)) {
require(msg.value >= totalCost, "Insufficient ETH");
} else {
IERC20(tier.paymentToken).transferFrom(msg.sender, address(this), totalCost);
}
Subscription storage sub = subscriptions[msg.sender][tierId];
// Продовження існуючої або нова підписка
uint256 currentExpiry = sub.expiresAt > block.timestamp
? sub.expiresAt
: block.timestamp;
sub.expiresAt = currentExpiry + months * 30 days;
sub.tierId = tierId;
sub.autoRenew = autoRenew;
if (sub.startedAt == 0) sub.startedAt = block.timestamp;
_distributeRevenue(tier, totalCost);
emit Subscribed(msg.sender, tierId, sub.expiresAt);
}
function isSubscribed(address user, uint256 tierId) external view returns (bool) {
return subscriptions[user][tierId].expiresAt > block.timestamp;
}
function _distributeRevenue(SubscriptionTier memory tier, uint256 amount) internal {
uint256 platformFee = amount * platformFeeBps / 10000;
uint256 creatorAmount = amount - platformFee;
if (tier.paymentToken == address(0)) {
payable(creator).transfer(creatorAmount);
payable(platform).transfer(platformFee);
} else {
IERC20(tier.paymentToken).transfer(creator, creatorAmount);
IERC20(tier.paymentToken).transfer(platform, platformFee);
}
}
}
Верифікація доступу до зашифрованого контенту
Зберігати контент on-chain неможливо та безсмисленно. Стандартна схема: контент шифрується та завантажується в IPFS, ключ шифрування выдається тільки верифікованим підписникам.
Проблема: хто выдає ключ? Якщо централізований сервер — немає сенсу у блокчейні. Рішення — Lit Protocol або Threshold Network: децентралізована мережа нод зберігає ключові шарди та выдає доступ тільки при on-chain умові.
import { LitNodeClient, checkAndSignAuthMessage } from '@lit-protocol/lit-node-client';
// Умова доступу: активна підписка на тир 1
const accessControlConditions = [
{
contractAddress: SUBSCRIPTION_CONTRACT_ADDRESS,
standardContractType: '',
chain: 'ethereum',
method: 'isSubscribed',
parameters: [':userAddress', '1'], // tierId = 1
returnValueTest: {
comparator: '=',
value: 'true'
}
}
];
// Шифрування контенту (creator при завантаженні)
async function encryptContent(content) {
const client = new LitNodeClient();
await client.connect();
const authSig = await checkAndSignAuthMessage({ chain: 'ethereum' });
const { encryptedString, symmetricKey } = await LitJsSdk.encryptString(content);
const encryptedSymmetricKey = await client.saveEncryptionKey({
accessControlConditions,
symmetricKey,
authSig,
chain: 'ethereum'
});
return {
encryptedContent: encryptedString,
encryptedKey: encryptedSymmetricKey
};
}
// Розшифровка (користувач при читанні)
async function decryptContent(encryptedContent, encryptedKey) {
const client = new LitNodeClient();
await client.connect();
const authSig = await checkAndSignAuthMessage({ chain: 'ethereum' });
const symmetricKey = await client.getEncryptionKey({
accessControlConditions,
toDecrypt: encryptedKey,
chain: 'ethereum',
authSig
});
return await LitJsSdk.decryptString(encryptedContent, symmetricKey);
}
Моделі монетизації для creator'а
| Модель | Описання | Плюси | Мінуси |
|---|---|---|---|
| Flat subscription | Фіксована ціна в місяць | Предбачуваний дохід | Немає премиум тирів |
| Tiered access | Базовий / Pro / VIP | Гнучко, максимізує виручку | Складніше управляти |
| Pay-per-content | Кожна частина окремо | Монетизація hit-контенту | Трення для користувача |
| NFT membership | Пожизненна підписка як NFT | Вторинний ринок = доп доход | Одноразовий платіж |
| Hybrid | Базовий тір + розові покупки | Оптимальний баланс | Найбільша складність |
NFT membership заслуговує уваги: creator отримує royalty від вторинних продаж (ERC-2981). Якщо ранній holder продає своє місце — creator отримує 5–10% від ціни перепродажі автоматично.
Auto-renewal через Chainlink Automation
Автопродовження підписки потребує зовнішнього триггера — смарт-контракт не може сам себе вызвати по розписанню.
Chainlink Automation перевіряє умову checkUpkeep() та вызиває performUpkeep() при необхідності:
contract AutoRenewSubscription is AutomationCompatibleInterface {
function checkUpkeep(bytes calldata) external view override
returns (bool upkeepNeeded, bytes memory performData)
{
// Знайти істекаючі підписки з autoRenew=true
address[] memory toRenew = _findExpiringSubscriptions();
upkeepNeeded = toRenew.length > 0;
performData = abi.encode(toRenew);
}
function performUpkeep(bytes calldata performData) external override {
address[] memory toRenew = abi.decode(performData, (address[]));
for (uint256 i = 0; i < toRenew.length; i++) {
_attemptRenewal(toRenew[i]);
}
}
function _attemptRenewal(address subscriber) internal {
// Спроба списати, якщо баланс достатній — продовжити
// При неудачі — подія, уведомлення користувача
}
}
Терміни розробки
Базова система підписок з одним тиром, Lit Protocol інтеграцією та простим frontend — 4–6 тижнів. Повнофункціональна платформа з tiered access, NFT membership, auto-renewal та аналітикою для creator'а — 10–14 тижнів.







