We develop turnkey token-gated content systems. Unlike traditional subscriptions (Patreon, Medium) where access control is centralized and the creator pays a platform commission (up to 12%), our solution is based on smart contracts. Payments go directly from subscriber to author, and content access is verified on-chain. Implementing such a scheme requires solving several technical challenges: encrypting content accessible only to subscribers, automatic subscription renewal, and organizing tiered access without excessive gas costs. Below we examine the architecture we use in production—from choosing the token standard to integrating decentralized encryption. Our stack: Solidity 0.8.x, Foundry, Lit Protocol, IPFS, Chainlink Automation. Specific code examples and configs are provided.
How to Choose the Optimal Token Standard for Subscriptions?
Each standard solves a specific task. ERC-1155 has become the industry standard for time-based subscriptions: it uses batch transfers and dynamic tokenId generation based on the period, reducing gas costs by 30–50% compared to ERC-721 for mass renewals. ERC-721 is better suited for lifetime NFT memberships with resale and royalties via ERC-2981. ERC-20 staking is the simplest option but freezes user liquidity.
| Model | Subscription Type | Resale | Gas on Mint | Royalties |
|---|---|---|---|---|
| ERC-721 | Lifetime / limited | Yes | High | ERC-2981 |
| ERC-1155 | Time-based (by periods) | No | Medium (batch - low) | No |
| ERC-20 staking | Continuous | No (only unstake) | Very low | No |
Example subscription contract structure:
contract ContentSubscription {
struct SubscriptionTier {
uint256 pricePerMonth; // in wei or ERC-20 tokens
address paymentToken; // address(0) = native ETH
uint256 maxSubscribers; // 0 = unlimited
string contentCID; // IPFS CID for encrypted content
bytes32 encryptionKeyHash; // hash of encryption key for this tier
}
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];
// Renew existing or new subscription
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);
}
}
}
How to Protect Content from Unauthorized Access?
Storing content on-chain is impossible and pointless. The standard scheme: content is encrypted and uploaded to IPFS, the encryption key is granted only to verified subscribers. Problem: who issues the key? If a centralized server is used, there is no point in blockchain. The solution is Lit Protocol or Threshold Network: a decentralized network of nodes stores key shards and grants access only when an on-chain condition is met. Implementation in JavaScript:
import { LitNodeClient, checkAndSignAuthMessage } from '@lit-protocol/lit-node-client';
// Access condition: active subscription to tier 1
const accessControlConditions = [
{
contractAddress: SUBSCRIPTION_CONTRACT_ADDRESS,
standardContractType: '',
chain: 'ethereum',
method: 'isSubscribed',
parameters: [':userAddress', '1'], // tierId = 1
returnValueTest: {
comparator: '=',
value: 'true'
}
}
];
// Encrypt content (executed by creator on upload)
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
};
}
// Decrypt (user on read)
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);
}
Why Chainlink Automation is the Standard for Auto-Renewal?
Auto-renewal requires an external trigger—a smart contract cannot call itself on a schedule. Chainlink Automation (formerly Keepers) checks the checkUpkeep() condition and calls performUpkeep() when necessary:
contract AutoRenewSubscription is AutomationCompatibleInterface {
function checkUpkeep(bytes calldata) external view override
returns (bool upkeepNeeded, bytes memory performData)
{
// Find expiring subscriptions with 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 {
// Attempt to collect funds if balance sufficient
// On failure — event, user notification
}
}
Chainlink Automation is cheaper and more reliable than manual calls—it automates renewal, reducing user friction. Alternatives (Gelato) also work, but Chainlink provides tighter integration with the Ethereum ecosystem.
Monetization Models for Creators: Comparison
| Model | Revenue | Implementation Complexity | Example Usage |
|---|---|---|---|
| Flat (fixed) | Predictable | Low | Newsletters |
| Tiered access | Maximum with segmentation | Medium | Educational platforms |
| Pay-per-content | Low entry barrier | High | Premium articles |
| NFT membership | Royalties on resale | Medium | Closed communities |
Model selection depends on the audience. For example, for a close-knit community with high loyalty, NFT membership works best; for mass content, tiered access is optimal.
What Our Work Includes
- Analysis of monetization model and selection of optimal token standard.
- Development and audit of smart contracts (Solidity, Foundry) with test coverage, including fuzzing via Echidna.
- Integration of decentralized encryption (Lit Protocol / Threshold).
- Configuration of IPFS gateway and key distribution system.
- Creation of user interface (React + wagmi/viem, RainbowKit).
- Connection of auto-renewal via Chainlink Automation.
- Deployment on the chosen network (Ethereum, Polygon, Arbitrum, Base).
- Technical documentation and team training.
- Post-launch support.
Experience and Guarantees
We have implemented 15+ projects in DeFi, NFT, and content tokenization. We use our own libraries of battle-tested smart contracts, reducing risks and development time. We guarantee stable operation thanks to fuzz testing (Echidna) and on-chain monitoring. According to our statistics, clients save up to 30% on gas through batch operation optimization—with a volume of $10,000 in monthly transactions, that's $3,000 in savings.
Development Timeline
A basic subscription system with one tier, Lit Protocol integration, and a simple frontend takes 4 to 6 weeks. A full platform with tiered access, NFT membership, auto-renewal, and analytics for creators takes 10 to 14 weeks. Exact timelines depend on the complexity of integrations and feature scope.
Contact us for a consultation and project evaluation. We will help design and implement a subscription system tailored to your case. Request an analysis of your monetization model—it takes no more than an hour. More details on standards can be found in the Wikipedia article on ERC-1155.







