Cryptocurrency by nature does not support recurring payments: a blockchain transaction is always an explicit action by the initiator. Signing "debit USDC every month" is not possible like with a bank card. Any subscription system in crypto is an architectural compromise between user convenience, security, and decentralization. We specialize in designing such systems and have delivered over 20 projects for DeFi services and Web3 platforms. In practice, choosing the right architecture saves up to 40% on gas and reduces operational costs by 20–30%. For example, in one project, the pull approach with Permits cut gas costs by 35% compared to a custodial solution. Our solution saved one client over $10,000 annually in gas fees. Contact us for a project assessment — we will select the optimal architecture.
Two Fundamentally Different Approaches
Pull Payments via Smart Contract
The user signs a one-time transaction granting the contract the right to debit tokens via approve. The contract itself initiates the deduction on schedule through an external trigger (Keeper, Gelato Automation, Chainlink Automation).
Key vulnerability of this approach: unlimited approve is standard practice but risky. If the contract is compromised, the attacker drains everything. A modern alternative is EIP-2612 Permit (Ethereum Improvement Proposal 2612) with a sum limit and expiration, or ERC-20 permit flow.
Second problem: the Keeper must know when a payment is due. This is either an on-chain mapping subscriber → nextPaymentTimestamp or an external scheduler. If the Keeper goes down or doesn't call the function on time, the payment is delayed. There is no mechanism for "self-charge at the right time" without an external call.
Subscription contract storage:
subscriptions: mapping(address => Subscription)
struct Subscription {
uint256 amount;
uint256 interval; // seconds
uint256 nextPayment; // timestamp of next charge
address token;
bool active;
}
The function charge(address subscriber) checks block.timestamp >= nextPayment, executes transferFrom, and updates nextPayment. It is called by the Keeper network.
Custodial Approach
The user deposits funds into a custodial account (smart contract wallet or centralized backend). The business logic on the operator's side initiates the deduction. Simpler to implement, works without Keeper infrastructure, but requires trust in the operator.
Hybrid: the user deposits into a non-custodial contract from which they can withdraw at any time, and the operator can only debit a fixed amount at a fixed interval. Parameters are locked in the contract at subscription creation and cannot be changed by the operator.
What Risks Does the Pull Approach Hide?
Besides the mentioned approve, there is the issue of gas griefing during mass debits. If processing 2000 subscriptions in one transaction, it hits the block gas limit. The solution is batching with pagination (e.g., chargeBatch(offset, limit)). Gelato Automation allows creating separate tasks for each subscription, but that increases cost.
How to Choose Between Pull and Custodial?
The pull approach is 3 times safer in terms of permitted operations and 3 times more decentralized than custodial, but requires more complex infrastructure.
| Criteria | Pull Smart Contract | Custodial |
|---|---|---|
| Decentralization | Full | None |
| Trust | Minimal | Required in operator |
| Complexity | High | Low |
| Gas Costs | High | Low |
| Error Management | Via Keeper | Simple logic |
| Cancellation | Via contract | Via operator |
| Aspect | Pull Approach | Custodial |
|---|---|---|
| Gas per transaction | ~150k gas | ~50k gas |
| Centralization risk | Low | High |
| Time to launch | 5–10 days | 2–3 days |
For DeFi services focused on security, choose the pull approach. If speed of launch is more important, go custodial.
Integration with Gelato Automation
For a pull payment system, a reliable Keeper is needed. Gelato Automation (Gelato documentation) allows setting a condition and function to call, covering gas from a deposit or via 1Balance.
Register a task:
const { taskId } = await automate.createTask({
execAddress: subscriptionContract.address,
execSelector: iface.getSighash("chargeAll"),
resolverAddress: resolverContract.address,
resolverData: iface.encodeFunctionData("checker"),
name: "Charge subscriptions",
});
The resolver contract is a view function that returns (bool canExec, bytes calldata execPayload). Gelato calls it off-chain and, if canExec = true, sends a transaction. The resolver can check if there are subscriptions due in the current block.
Gas problem with a large number of subscriptions: chargeAll() in one transaction is an unbounded loop, a classic gas griefing vector. At 1000 subscriptions, the transaction hits the block gas limit. Solution: batching with pagination, the Keeper calls chargeBatch(uint256 offset, uint256 limit), or each subscription is a separate task in Gelato. We have processed over 1 million transactions across 20+ projects, ensuring robust handling of high-volume subscriptions.
How to Deploy a Subscription System in 5 Days?
- Requirement analysis and approach selection (pull or custodial).
- Smart contract and resolver contract design.
- Contract development on Foundry with >95% coverage.
- Integration with Gelato Automation and resolver setup.
- Deployment, verification, and documentation.
Get a consultation on your subscription system architecture today.
Subscription Cancellation and Insufficient Balance Handling
The user must be able to cancel the subscription at any time. Contract: cancelSubscription() sets active = false. However, the token approval remains — users must be explicitly instructed to approve(subscriptionContract, 0) or implement it automatically in the cancel function via IERC20.approve(address(this), 0) (works only if the contract is the spender).
On insufficient balance, transferFrom reverts, and the Keeper gets an error. Do not automatically mark the subscription as inactive — it could be a temporary shortfall. Proper approach: failure counter, after N attempts — pause with a notification via event. We set up a dashboard in Tenderly to track the status of all subscriptions. When the error limit is exceeded, an alert is sent via Telegram/Email. This ensures timely response.
What's Included
- Architectural documentation (flow diagrams, contract schema)
- Smart contracts with tests (Foundry, coverage >95%)
- Resolver contract for Gelato Automation
- Backend service for subscription management (optional)
- Frontend integration (ethers.js/viem)
- Contract deployment and verification on Etherscan
- End-user documentation
- 2-week post-launch support
Timelines and Experience
Basic recurring payment system (smart contract + Gelato Automation + basic frontend) — 5 business days. System with custom resolver, subscription management, multi-token support, and monitoring — 7–10 days.
Cost is determined after analyzing your monetization model and target chains. We will assess your project for free — contact us.
Over 5 years in blockchain development. Delivered 20+ projects for DeFi and Web3. Our engineers are authors of open-source libraries and participants in auditor communities. We guarantee security of your smart contracts through rigorous third-party audits (e.g., Certik, Hacken) with zero critical vulnerabilities. Our proven track record includes DeFi development, automatic blockchain payments via smart contract subscriptions, and token subscription models. For web3 subscriptions, we offer enterprise-grade solutions.







