Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
LABS
Guides

Launching a Subscription Model Using Smart Contracts

A technical tutorial for developers to implement on-chain subscription logic, covering payment cycles, access control, refunds, and tier management.
Chainscore © 2026
introduction
TUTORIAL

Launching a Subscription Model Using Smart Contracts

A technical guide to implementing recurring payments and access control for services directly on the blockchain.

On-chain subscriptions use smart contracts to automate recurring payments and manage user access, eliminating intermediaries. Unlike traditional systems, these models are transparent, censorship-resistant, and programmable. Common use cases include SaaS platforms, premium content gating, and protocol fee collection. The core mechanism involves a contract that checks for a valid, active subscription before granting access to a function or resource. This is typically enforced through a modifier like onlySubscriber that validates payment status and expiration dates.

The most straightforward implementation uses a time-based model. A user pays a one-time fee to subscribe for a fixed period (e.g., 30 days). The contract records the subscription's expiryTimestamp. Every time the user calls a gated function, the contract checks block.timestamp < expiryTimestamp. For auto-renewing subscriptions, the logic becomes more complex, requiring a pull-based payment system where users pre-approve recurring charges, or a push-based system where they manually renew. Security considerations are paramount, especially in preventing users from accessing services after their subscription lapses.

Here is a basic Solidity example for a time-gated subscription contract using the pull-payment pattern with ERC-20 tokens:

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

contract SimpleSubscription {
    IERC20 public paymentToken;
    uint256 public subscriptionPrice;
    uint256 public subscriptionPeriod = 30 days;

    mapping(address => uint256) public expiryTime;

    constructor(address _token, uint256 _price) {
        paymentToken = IERC20(_token);
        subscriptionPrice = _price;
    }

    function subscribe() external {
        require(paymentToken.transferFrom(msg.sender, address(this), subscriptionPrice), "Transfer failed");
        expiryTime[msg.sender] = block.timestamp + subscriptionPeriod;
    }

    modifier onlySubscriber() {
        require(expiryTime[msg.sender] > block.timestamp, "Subscription expired");
        _;
    }

    function accessGatedFeature() external onlySubscriber {
        // Logic for premium feature
    }
}

This contract stores an expiry timestamp for each subscriber and uses a modifier to restrict access.

For production systems, consider more robust patterns. The subscription NFT model mints a non-transferable NFT (ERC-721 or ERC-1155) to represent an active subscription, with its metadata encoding the expiry. This allows for easier off-chain verification and integration with existing NFT marketplaces and wallets. Alternatively, protocols like Superfluid enable real-time finance streams, where users approve a constant flow of tokens per second, providing granular, real-time subscription logic. When designing your model, key decisions include: the payment token (native ETH vs. stablecoins), renewal mechanics, proration for upgrades/downgrades, and gas efficiency of access checks.

Integrating on-chain subscriptions requires careful front-end development. Your dApp must:

  • Connect to user's wallet (e.g., using WalletConnect or MetaMask).
  • Check the user's current subscription status by calling expiryTime(userAddress).
  • Handle the token approval transaction before the subscription purchase.
  • Listen for the SubscriptionUpdated event (if emitted) to update the UI in real time.
  • Gracefully handle expired states by prompting renewal. Tools like The Graph can index subscription events for efficient querying, and OpenZeppelin Defender can automate admin tasks like disabling outdated plans.

Security and user experience are the final hurdles. Common pitfalls include: not using Checks-Effects-Interactions pattern, leading to reentrancy risks; relying on block.timestamp which can be slightly manipulated by miners; and creating gas-inefficient access checks. Always conduct audits for financial contracts. For UX, consider gasless onboarding via meta-transactions, offering free trials by setting an initial expiryTime, and providing clear off-chain receipts. By leveraging the transparency and automation of smart contracts, on-chain subscriptions offer a powerful, trust-minimized foundation for recurring business models in Web3.

prerequisites
GETTING STARTED

Prerequisites and Setup

Before building a subscription smart contract, you need a foundational development environment and a clear understanding of the core components involved.

A functional development environment is the first prerequisite. You will need Node.js (v18 or later) and a package manager like npm or yarn installed. The core tool for writing, testing, and deploying smart contracts is the Hardhat framework, which provides a local Ethereum network, a testing suite, and plugins for tasks like contract verification. Install it globally via npm install --global hardhat. You'll also need a code editor such as VS Code with the Solidity extension for syntax highlighting and error checking.

Understanding the key architectural components is crucial. Your subscription model will be built on a smart contract written in Solidity, which defines the business logic: - Subscription tiers and pricing - Billing cycles (e.g., monthly, yearly) - Payment token acceptance (like ERC-20 tokens or native ETH) - Access control for subscribers and administrators. You must also decide on an oracle solution for off-chain data (like checking real-world payment status) and a relayer pattern if you want to offer gasless transactions for users.

For testing and deployment, you'll interact with blockchain networks. Start by configuring Hardhat to use a local development network for rapid iteration. You will need a crypto wallet (like MetaMask) and test ETH or other tokens on a testnet (e.g., Sepolia or Goerli). Obtain test ETH from a faucet. Essential libraries include OpenZeppelin Contracts for secure, audited base contracts like Ownable for access control and payment-safe math operations. Install them via npm install @openzeppelin/contracts.

Finally, plan your contract's upgradeability and front-end integration from the start. For production, consider using OpenZeppelin's Upgrades Plugin with a proxy pattern to allow for future fixes without losing state. Your web application will interact with the contract using a library like ethers.js or viem. Ensure you have a basic understanding of how to connect a wallet, read contract state, and send transactions from a JavaScript frontend to create a complete user experience.

key-concepts-text
IMPLEMENTATION GUIDE

Launching a Subscription Model Using Smart Contracts

This guide details the core concepts and architectural decisions required to build a recurring payment system on-chain, from defining the subscription lifecycle to handling payments and access control.

A blockchain-based subscription model replaces traditional recurring billing with a self-executing agreement defined in a smart contract. The core components are a payment token (like USDC or the native chain token), a defined billing period (e.g., 30 days), and a price per period. The contract must manage the subscription's state—typically Active, Expired, or Canceled—and enforce access rights based on a user's paid-up status. Unlike off-chain systems, this logic is transparent, tamper-proof, and eliminates the need for a trusted third-party payment processor.

The subscription lifecycle is governed by two primary functions: subscribe and renew. The subscribe function initiates a new subscription, often requiring an initial payment and setting an expiryTimestamp. The renew function is callable by anyone (allowing for permissionless renewals) to extend the subscription if the user's balance is sufficient. A common pattern uses an isSubscribed modifier to gatekeep premium features, checking block.timestamp < userExpiry. This automated enforcement is a key advantage, removing manual invoicing and dunning processes.

Handling payments requires careful design. For maximum compatibility, contracts should accept stablecoins like USDC. The contract must safely transfer funds, typically to a treasury address, using the transferFrom pattern for ERC-20 tokens. A critical security consideration is proration. Should a user who subscribes mid-period pay a full period's fee, or a proportional amount? Implementing proration requires more complex math but is often expected. Furthermore, contracts should allow users to cancel their subscription, which stops future renewals but does not refund the current period.

A major technical challenge is automating the renewal process without a centralized cron job. Solutions include keeper networks like Chainlink Automation or Gelato, which can call the renew function for users when their subscription nears expiry. Alternatively, you can design a pull-based model where renewal is triggered by the user's next interaction with the dApp, though this risks service interruption. For gas efficiency, consider implementing a renewAll function for keepers to batch-process multiple expiring subscriptions in a single transaction.

Finally, subscription data must be accessible. Emit clear events like SubscriptionCreated and SubscriptionRenewed for off-chain indexing. Consider implementing view functions like getUserSubscription to return status and expiry. For advanced features, explore tiered subscriptions with different price points or trial periods that require a startTrial function with signature verification to prevent abuse. By combining these concepts, you can build a robust, decentralized recurring revenue system.

implementation-approaches
ARCHITECTURE

Implementation Approaches

Choose a technical foundation for your on-chain subscription service. Each approach offers different trade-offs in complexity, flexibility, and gas efficiency.

IMPLEMENTATION STRATEGY

EIP-1337 vs. Custom Contract: A Comparison

Key differences between using the EIP-1337 standard and building a custom smart contract for a subscription service.

FeatureEIP-1337 StandardCustom Contract

Standardization & Interoperability

Development Complexity

Low

High

Gas Cost for Setup

~500k gas

~1.2M gas

Wallet Integration (e.g., MetaMask)

Native

Requires custom UI

ERC-20 Token Support

Required

Optional

Subscription Cancellation Flow

Standardized (cancelAtCycle)

Custom logic

Upgradeability & Maintenance

Limited (EIP version)

Full control

Audit & Security Review

Community-reviewed standard

Requires full audit

building-custom-contract
SMART CONTRACT TUTORIAL

Building a Custom Subscription Contract

This guide explains how to implement a recurring subscription model on-chain, covering key design patterns, payment handling, and access control using Solidity.

A subscription smart contract automates recurring payments and access management. Unlike traditional systems, it operates trustlessly, with logic enforced by code. The core components are a payment scheduler, a mechanism to track active subscribers, and a way to grant or revoke access to a service or content. Common implementations use a time-based check, where a user's subscription is valid if the current block timestamp is less than their paid period's expiration. This model is foundational for SaaS dApps, premium content platforms, and any service requiring periodic fees.

The contract must securely handle incoming payments and update the subscriber's state. A typical subscribe function accepts payment in the native chain currency (e.g., ETH) or a designated ERC-20 token. It should validate the payment amount against a predefined subscriptionCost and duration. Upon successful payment, the function records the new expiration time for the user's address. Critical checks include preventing duplicate payments that don't extend the subscription and using require statements to validate conditions before state changes, following the checks-effects-interactions pattern.

Access control is managed by a view function, often named isSubscribed, that other contracts or frontends can call. It simply compares block.timestamp to the user's stored expiresAt timestamp. For example: function isSubscribed(address user) public view returns (bool) { return subscribers[user] >= block.timestamp; }. This offloads the validation logic, allowing your main application contract to gatekeep functions with a modifier like onlySubscriber. This separation keeps the subscription logic modular and reusable across different parts of your dApp.

Handling renewals and cancellations requires careful design. A renewal can be triggered by the user calling subscribe again, which should extend the existing expiration time. For cancellations, a common pattern is to simply let a subscription lapse—no active "cancel" function is needed, reducing complexity. However, for compliance or user experience, you might implement a refund function prorating unused time, though this introduces significant security and accounting complexity. Always consider gas costs for these operations, especially if they're called frequently.

For production use, integrate with a decentralized oracle like Chainlink Automation to automate expiration checks and cleanup tasks. Instead of relying on users to manually renew, an upkeep can run a function that, for example, emits an event for expired subscriptions or removes old data from storage to save gas. Furthermore, consider implementing upgradeability patterns (like Transparent Proxy) using OpenZeppelin libraries if your fee structure or logic might need to change. Always audit your contract and test edge cases, such as front-running attacks on the subscribe function or handling of zero-value payments.

advanced-features
SUBSCRIPTIONS ON-CHAIN

Implementing Advanced Features

Build recurring revenue models with smart contracts. This guide covers the core patterns, security considerations, and implementation strategies for on-chain subscriptions.

02

Implementing a Pay-as-You-Go Model

Instead of fixed time periods, charge users per action or unit of consumption using a prepaid credit system.

  • Design Pattern: Users deposit funds into a contract that deducts a set fee per transaction.
  • Example: An API service charges 0.001 ETH per 1000 requests.
  • Advantage: More flexible for users with variable usage patterns.

This model requires careful gas optimization and front-running protection for the deduction function.

03

Handling Failed Payments & Grace Periods

Smart contracts cannot initiate transactions, so handling lapsed payments requires specific logic.

  • Pull vs. Push: Use a pull-based model where the service provider calls a function to collect payment, allowing for grace periods.
  • State Tracking: Maintain a lastPaid timestamp and a gracePeriod duration (e.g., 30 days).
  • Automation: Integrate with Chainlink Automation or Gelato to automatically check for and process overdue subscriptions.
05

Security & Anti-Fraud Measures

Subscription contracts are high-value targets. Implement these critical safeguards:

  • Reentrancy Guards: Protect payment collection functions.
  • Access Control: Use OpenZeppelin's Ownable or AccessControl for admin functions.
  • Price Oracle: For stablecoin-denominated plans, use a Chainlink Price Feed to handle ETH/USD volatility.
  • Upgradeability: Consider a Transparent Proxy or UUPS pattern to patch vulnerabilities, but beware of the associated risks.
06

Analytics & Off-Chain Infrastructure

Monitor subscription health and manage customer relationships with off-chain tools.

  • Event Indexing: Use The Graph to index SubscriptionCreated and PaymentCollected events for dashboards.
  • Notification Service: Send email/SMS alerts for failed payments using webhook triggers from your indexer.
  • Compliance: For fiat on-ramps, integrate KYC providers like Synapse or Persona at the point of initial sign-up.
frontend-integration
FRONTEND INTEGRATION AND USER FLOW

Launching a Subscription Model Using Smart Contracts

A practical guide to building the user interface and experience for a blockchain-based subscription service, connecting smart contract logic to a usable web application.

Integrating a subscription smart contract into a frontend requires a clear user flow and robust wallet interaction. The core steps are: connecting a user's wallet (e.g., MetaMask, WalletConnect), reading subscription status from the contract, and executing transactions for sign-up, renewal, or cancellation. Use libraries like ethers.js or viem to interface with the blockchain. The first step is to fetch the user's current subscription details—such as isActive, expiryTimestamp, and planId—by calling the contract's view functions. This data dictates the UI state, showing an active subscription dashboard or a sign-up prompt.

The subscription purchase flow involves a multi-step transaction. After a user selects a plan, the frontend must request approval for the subscription fee, typically in a stablecoin like USDC. This is a two-transaction process: first, an approve() call on the ERC-20 token contract to grant the subscription contract spending allowance, followed by the subscribe(uint256 planId) call. To improve UX, consider using ERC-20 permit signatures for gasless approvals or bundling transactions via a relayer. Always display clear transaction status, estimated gas, and error handling for insufficient funds or rejected transactions.

Managing recurring payments and expiration requires proactive frontend logic. Since Ethereum lacks native cron jobs, your app should poll the contract's getUserSubscription function or listen for SubscriptionRenewed and SubscriptionExpired events. For a seamless experience, implement background checks using setInterval or WebSocket subscriptions via providers like Alchemy or Infura. When a subscription nears expiry, the UI should prompt the user to renew, potentially triggering an automatic renewal transaction if they've pre-authorized it. This creates a cycle of read-state, act, and listen that keeps the user's status synchronized.

Security and user experience are paramount. Never store private keys or seed phrases. All transaction signing must occur in the user's wallet. Implement chain validation to ensure users are on the correct network (e.g., Ethereum Mainnet, Polygon). Use error boundaries and fallback UIs for failed RPC calls. For testing, integrate with Sepolia or Goerli testnets and faucets. Provide clear documentation on gas fees and network requirements. A well-designed flow reduces friction and builds trust, turning smart contract functionality into a viable product.

SUBSCRIPTION SMART CONTRACTS

Common Pitfalls and Security Considerations

Implementing a subscription model on-chain introduces unique technical and security challenges. This guide addresses frequent developer questions and critical issues to avoid when building recurring payment systems.

A common failure occurs when a user's wallet lacks sufficient ETH (or native gas token) to pay for the gas of the automated renewal transaction. Unlike a one-time approval, subscriptions require the user's wallet to have a gas balance for every future renewal.

Key Considerations:

  • Relayer Dependency: Most subscription models rely on a centralized relayer or keeper network (like Chainlink Automation or Gelato) to execute renewals. The user must fund this future gas cost.
  • Failed State: If the renewal transaction fails due to out-of-gas errors, the subscription lapses, potentially disrupting service.

Solutions:

  • Gas Abstraction: Use account abstraction (ERC-4337) with a paymaster to sponsor gas fees, billing the cost as part of the subscription.
  • Warnings & Estimates: Frontends should clearly display estimated future gas costs and warn users to maintain a gas balance.
  • Fallback Periods: Implement a grace period where the subscription is paused, not cancelled, allowing the user to top up their wallet.
DEVELOPER FAQ

Frequently Asked Questions

Common technical questions and troubleshooting for building subscription models with on-chain smart contracts.

The standard approach is to store an expiration timestamp for each user. Instead of iterating through arrays or updating complex state on every check, your isSubscribed(address user) function should simply compare userExpiry[user] with block.timestamp. This uses a single SLOAD operation (warm: 100 gas, cold: 2100 gas).

Avoid storing subscription status as a boolean that gets toggled on renewal; calculating expiry off-chain and writing only the new timestamp minimizes state changes. For recurring payments, consider storing the subscription period (e.g., 30 days in seconds) and the last payment timestamp, then calculating expiry as lastPayment + period.

conclusion
IMPLEMENTATION SUMMARY

Conclusion and Next Steps

You have built the core components of a subscription smart contract. This section reviews the key concepts and suggests advanced features to implement.

Your subscription contract now handles the fundamental logic: users can subscribe to a plan, pay recurring fees, and lose access upon expiration. The core architecture relies on a mapping to track each subscriber's Subscription struct, which stores their plan ID and renewal timestamp. The onlyActiveSubscriber modifier is a critical security pattern that protects your premium functions. For recurring payments, you implemented a renewSubscription function that users must manually call, which is a common design for on-chain subscriptions to avoid the complexities and gas costs of automated triggers.

To enhance your contract, consider these next steps for production readiness. First, implement a withdrawal pattern for the contract owner to securely collect accumulated fees, rather than using transfer or send. Second, add an upgrade mechanism using a proxy pattern like the Transparent Proxy from OpenZeppelin to fix bugs or add features post-deployment. Third, integrate an off-chain indexer or subgraph to efficiently query all active subscribers, as on-chain enumeration of mappings is gas-intensive and impractical.

For a more sophisticated system, explore integrating Chainlink Automation or Gelato Network to automate the subscription renewal process. These decentralized services can call your renewSubscription function on behalf of users when their term expires, creating a seamless experience. Additionally, consider adding support for ERC-20 token payments alongside native ETH, allowing for greater flexibility. Always conduct thorough testing on a testnet like Sepolia, perform a security audit, and verify your contract source code on a block explorer like Etherscan before mainnet deployment.

How to Build a Crypto Subscription Model with Smart Contracts | ChainScore Guides