ERC20 extension for subscriptions
An extension of the ERC20 token standard that allows for a new type of approval. This contract enables an owner to approve a spender to spend a fixed amount of tokens on their behalf, with a custom recurrence interval and for a specified duration of time. This allows for the creation of subscriptions using ERC20 tokens.
Forge:
forge install 0xJord4n/ERC20Subscription
Manually:
Take the content of the file ERC20Subscription.sol in src directory and import it in your project
NPM:
Soon
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
interface IERC20Subscription {
function permitForSubscription(
address owner,
address spender,
uint256 value,
uint32 recurrenceInterval,
uint48 approveUntil,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external returns (bool);
function approveForSubscription(
address spender,
uint256 value,
uint32 recurrenceInterval,
uint48 approveUntil
) external returns (bool);
function allowanceForSubscription(
address owner,
address spender,
uint32 recurrenceInterval,
uint48 approvedUntil
) external view returns (uint256);
function increaseAllowanceForSubscription(
address spender,
uint256 addedAmount,
uint32 recurrenceInterval,
uint48 approvedUntil
) external returns (bool);
function decreaseAllowanceForSubscription(
address spender,
uint256 removedAmount,
uint32 recurrenceInterval,
uint48 approvedUntil
) external returns (bool);
function transferFromForSubscription(
address from,
address to,
uint256 amount,
uint32 recurrenceInterval,
uint48 approvedUntil
) external returns (bool);
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract TestToken is ERC20, ERC20Subscription {
constructor() ERC20("Test Token", "TEST") ERC20Subscription("Test Token") {}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./ERC20Subscription.sol";
contract SubscriptionContract is Ownable {
struct UserSubscription {
uint256 amount;
uint48[] lastPayments;
}
mapping(address => UserSubscription) private subscriptions;
ERC20Subscription private token;
event SubscriptionTokensTransferred(address indexed user, uint256 amount);
event SubscriptionCancelled(address indexed user);
constructor(address _tokenAddress) {
token = ERC20Subscription(_tokenAddress);
}
function subscribe(uint256 amount) external {
require(amount > 0, "Invalid amount");
UserSubscription storage userSubscription = subscriptions[msg.sender];
require(userSubscription.amount == 0, "Already subscribed");
userSubscription.amount = amount;
emit SubscriptionTokensTransferred(msg.sender, amount);
}
function cancelSubscription() external {
UserSubscription storage userSubscription = subscriptions[msg.sender];
require(userSubscription.amount > 0, "No subscription found");
// Transfer remaining tokens back to the user
require(token.transfer(msg.sender, userSubscription.amount), "Token transfer failed");
userSubscription.amount = 0;
emit SubscriptionCancelled(msg.sender);
}
function transferSubscriptionTokens(address[] calldata users, uint256[] calldata amounts) external onlyOwner {
require(users.length == amounts.length, "Invalid input length");
for (uint256 i = 0; i < users.length; i++) {
address user = users[i];
uint256 amount = amounts[i];
UserSubscription storage userSubscription = subscriptions[user];
require(userSubscription.amount >= amount, "Insufficient subscription amount");
// Transfer tokens from the contract to the user
require(token.transfer(user, amount), "Token transfer failed");
// Add the current timestamp as the latest payment
userSubscription.lastPayments.push(uint48(block.timestamp));
emit SubscriptionTokensTransferred(user, amount);
}
}
function getUserSubscription(address user) external view returns (uint256 amount, uint48[] memory lastPayments) {
UserSubscription storage userSubscription = subscriptions[user];
return (userSubscription.amount, userSubscription.lastPayments);
}
}
- 1.0.0
- First release
We welcome and encourage anyone to actively participate in the improvement of this project. If you come across any issues, bugs, or areas for optimization, please don't hesitate to report them. By reporting issues, you contribute to the overall stability and quality of the project. Additionally, if you have suggestions or ideas on how to optimize gas usage and make the contract more efficient, we would be thrilled to hear them. While the contract has not been audited, we value community feedback and are open to collaboration to enhance its security and reliability. Your contributions, whether it's reporting issues or proposing gas optimizations, are greatly appreciated and will help us create a better product for everyone involved.
- Fork it (https://github.com/0xJord4n/ERC20Subscription/fork)
- Create your feature branch (
git checkout -b feature/fooBar
) - Commit your changes (
git commit -am 'Add some fooBar'
) - Push to the branch (
git push origin feature/fooBar
) - Create a new Pull Request
- Openzeppelin ERC20 Approval
- Openzeppelin ERC20 Permit
0xJordan – @0xjord4n_ – [email protected]
Distributed under the MIT license. See LICENSE
for more information.
- EVM Chains: 0x1814b7a2a132a816ff5bd8573b1c2bf5995d2fda
- BTC: bc1q6wjvnldvrcaq6aafgacjy70vh32a4rc2f3mdgj
- LTC: MCufZV9LUczQT7Fnctzh58iGPkXrQMjcK7
- XMR: 457Yxyaguty51nD4pDAo2zTGy3a2V22gW4BsTkUUCUUa2Efng8xxKRFNMupiVu8CBGGKAcDCT7rQvhrHP5n8EJSRCjFYwa9
- BCH: qqhpf7cepn23wwu6yxh7nn33u9dslhnkdc7m48y3u8
- Cosmos: cosmos1jy8exr0m6j7twu3d28hd8j3j6m3fzkhgml42ng
- SUI: 0xc0e53341b57c67c3fa1d23a10b4f28c3449e7d6c8930495b5d66236b5944c75f
- Solana: C3VmQpDvQ8zibqQ9AByJidfsmwE1NmKFJ75x3josUFPx
- Aptos: 0x6a9d57a6308beffa370a6efb544b54dc5d157db5252d12c299e872cfee2a8f72