NFT SUB: Bringing Subscription Models to Web3
The subscription model is standard in Web2, but reproducing it on-chain has been difficult. Traditional recurring payments depend on centralized processors and off-chain scheduling, none of which map cleanly to blockchain execution. NFT SUB addresses this by expressing subscriptions as verifiable on-chain events.
Implemented as a TypeScript SDK, NFT SUB issues ERC-1155 tokens as subscription units. Merchants register on-chain and define their terms: pricing, duration, accepted assets. Subscribers mint NFTs that act as both proof of payment and access credentials. Metadata captures status, expiration, and renewal history.
Renewals are automated through Reactive Network. It monitors subscription states and triggers renewal transactions without intervention. Payments aren’t missed, and expired accounts don’t slip through. ETH and ERC-20 assets are supported, giving users flexibility in how they pay.
The first version of NFT SUB was built during the Reactive Network's Hackathon in September 2025, where it earned 3rd prize. The event brought together teams experimenting with the Reactive tech stack, producing impressive Web3 projects and winning prize incentives.
Try NFT SUB | See GitHub Project | Watch YouTube Demo
SDK Flow
The process works as follows:
- User → Subscribe (Frontend Interaction): The user initiates a subscription request through the dApp interface.
- Smart Contracts → Mint Subscription NFT (On-Chain Action): The subscription contract processes the request and mints an ERC-1155 NFT that represents the user’s active subscription.
- Smart Contracts → Emit Renewal Event (Reactive Signal): The contract emits a scheduling event. Reactive listens for it and prepares the renewal job.
- Reactive Network → Trigger Renewal (Automated Execution): When the renewal time arrives, Reactive calls the subscription contract to execute the renewal.
- Smart Contracts → Process Payment (On-Chain Action): The contract charges the subscriber and routes payment to the merchant.
- Subscription NFT → Access Service (Verification Layer): The service checks the user’s NFT. If it’s valid and active, access is granted.
Reactive Contract
`SubscriptionReactive.sol` acts as the automation layer that links subscription payments with lifecycle management. Built on the Reactive architecture, it listens for payment events across chains, updates subscription state, and executes scheduled maintenance routines.
The contract tracks subscription expirations per user–merchant pair through a dual-key mapping:
mapping(address => mapping(uint256 => uint64)) private trackedExpirations;Each expiration is stored as a 64-bit Unix timestamp for gas efficiency and to support multiple concurrent subscriptions per user.
Operational parameters are defined through a configuration struct:
struct Config {
address subscriptionManager;
address subscriptionNFT;
uint256 destinationChainId;
uint64 callbackGasLimit;
}This sets the destination chain where NFTs are managed and the gas budget for cross-chain callbacks.
The main Reactive entry point is the `react()` function. It routes external triggers to either payment handling or CRON maintenance:
function react(LogRecord calldata log) external override vmOnly whenNotPaused {
if (log.topic_0 == uint256(SubscriptionConstants.PAYMENT_RECEIVED_TOPIC)) {
_processPaymentEvent(log);
} else if (log.chain_id == 0 && log._contract == address(0)) {
_processCronEvent();
}
}Payment Handling
When a payment event is detected, the contract decodes the payload and adjusts the subscription expiration:
uint64 currentExpiry = trackedExpirations[user][merchantId];
uint64 newExpiry;
if (currentExpiry > uint64(block.timestamp)) {
newExpiry = currentExpiry + period; // Stack additional time
} else {
newExpiry = uint64(block.timestamp) + period; // Fresh start
}Renewals stack remaining time; expired subscriptions restart from the current timestamp.
After processing, the contract issues a callback to the subscription NFT contract on the destination chain:
bytes memory payload = abi.encodeWithSignature(
"onPaymentProcessed(address,uint256,uint64)",
user,
merchantId,
newExpiry
);
emit Callback(
config.destinationChainId,
config.subscriptionNFT,
config.callbackGasLimit,
payload
);The callback instructs the NFT contract to update metadata, mint, or extend the subscription token.
Scheduled Maintenance
Time-based triggers handle batch expiry management. The contract subscribes to CRON intervals via:
function subscribeToCron(uint256 interval) external override rnOnly {
ISubscriptionService(service).subscribe(
0, // CRON subscription indicator
address(0),
interval,
0, 0, 0
);
}When triggered, `_processCronEvent()` batches up to 50 expiring subscriptions and issues an update callback:
function _processCronEvent() private {
uint256 maxBatch = 50;
if (expiringUsers.length > 0 && expiringUsers.length <= maxBatch) {
bytes memory payload = abi.encodeWithSignature(
"processBatchExpiry(address[],uint256[])",
expiringUsers,
expiringMerchantIds
);
emit Callback(
config.destinationChainId,
config.subscriptionNFT,
config.callbackGasLimit,
payload
);
}
}Event Subscription
The contract follows a `subscribe → react → callback` pattern. It subscribes to payment events from origin chains:
function subscribeToPaymentEvents(
uint256 chainId,
address contractAddress
) external override rnOnly {
ISubscriptionService(service).subscribe(
chainId,
contractAddress,
uint256(SubscriptionConstants.PAYMENT_RECEIVED_TOPIC),
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE
);
}Once events occur, the contract updates local state and triggers cross-chain callbacks to the NFT contract. This preserves a unified subscription lifecycle without requiring users to interact with multiple chains.
Closing Note
NFT SUB combines ERC-1155 tokens with Reactive Network's event monitoring to create an on-chain subscription system. The architecture separates concerns: subscription state lives as NFTs on the destination chain while Reactive Contracts monitor payments and trigger state updates. This removes the need for centralized subscription management while maintaining automated renewals and cross-chain payment support.
The system supports standard subscription patterns: recurring payments, expiration tracking, automated renewals. No off-chain infrastructure required. Subscription NFTs are transferable assets that serve as both payment receipts and access credentials. The reactive monitoring layer scales independently, handling payment detection and lifecycle management across multiple chains through a `subscribe-react-callback` pattern.
This app makes it clear that Reactive contract logic isn’t mysterious. Hackathon participants delivered it under tight timelines, showing it’s difficult but absolutely doable. If you’re ready to push your skills further, Reactive Bounties are open.
About Reactive Network
Reactive is an EVM-compatible execution layer for dApps built with Reactive contracts. These contracts differ from traditional smart contracts by using inversion-of-control for the transaction lifecycle, triggered by data flows across blockchains rather than by direct user input.
Reactive contracts listen for event logs from multiple chains and execute Solidity logic in response. They can determine autonomously when to transmit data to destination chains, enabling conditional cross-chain state changes. The network delivers fast and cost-effective computation via a proprietary parallelized EVM implementation.
Website | Blog | Twitter | Telegram | Discord | Reactive Docs
Build once — react everywhere!