Разработка системы подписок на контент за токены
Токен-гейтинг контента — это не 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 в метаданных или в контракте. Продаваемый, transferable. Идеально для «пожизненных» подписок или ограниченных тиров.
ERC-1155 timed access: полуфунгибельные токены с привязкой к периоду. Например, tokenId = 202504 — доступ на апрель 2025. Экономит gas за счёт 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 (бывший Keepers) проверяет условие 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 недель.







