Skip to content

Instantly share code, notes, and snippets.

@joeblau
Created November 13, 2024 20:42
Show Gist options
  • Save joeblau/27b883ae22c9ee05ceab64f9c409d4ba to your computer and use it in GitHub Desktop.
Save joeblau/27b883ae22c9ee05ceab64f9c409d4ba to your computer and use it in GitHub Desktop.
Subscription NFT
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";
contract SubscriptionNFT is ERC721 {
IERC20 public usdcToken;
address public owner;
enum Tier { BASIC, PREMIUM, VIP }
struct Subscription {
Tier tier;
uint256 expiration;
}
mapping(uint256 => Subscription) private _subscriptions;
uint256 private _tokenIds;
event SubscriptionMinted(address indexed to, uint256 tokenId, Tier tier, uint256 expiration);
event SubscriptionReloaded(uint256 tokenId, uint256 newExpiration);
modifier onlyOwner() {
require(msg.sender == owner, "Not authorized");
_;
}
constructor(address _usdcAddress) ERC721("SubscriptionNFT", "SUBNFT") {
usdcToken = IERC20(_usdcAddress);
owner = msg.sender;
}
function mint(address to, Tier tier, uint256 amount) external {
require(to != address(0), "Invalid address");
require(amount > 0, "Amount must be greater than zero");
// Accept payment
require(usdcToken.transferFrom(msg.sender, address(this), amount), "USDC transfer failed");
// Mint NFT
_tokenIds++;
uint256 newTokenId = _tokenIds;
_safeMint(to, newTokenId);
// Set subscription details
_subscriptions[newTokenId] = Subscription({
tier: tier,
expiration: block.timestamp + 365 days
});
emit SubscriptionMinted(to, newTokenId, tier, _subscriptions[newTokenId].expiration);
}
function reloadSubscription(uint256 tokenId, uint256 amount) external {
require(_exists(tokenId), "Token does not exist");
require(amount > 0, "Amount must be greater than zero");
// Accept payment
require(usdcToken.transferFrom(msg.sender, address(this), amount), "USDC transfer failed");
// Extend expiration
_subscriptions[tokenId].expiration += 365 days;
emit SubscriptionReloaded(tokenId, _subscriptions[tokenId].expiration);
}
function isSubscriptionActive(uint256 tokenId) public view returns (bool) {
require(_exists(tokenId), "Token does not exist");
return _subscriptions[tokenId].expiration >= block.timestamp;
}
function getSubscriptionDetails(uint256 tokenId) external view returns (Tier tier, uint256 expiration) {
require(_exists(tokenId), "Token does not exist");
Subscription memory sub = _subscriptions[tokenId];
return (sub.tier, sub.expiration);
}
// Override transfer functions to make tokens soulbound
function _transfer(address from, address to, uint256 tokenId) internal override {
revert("Soulbound tokens cannot be transferred");
}
function approve(address to, uint256 tokenId) public override {
revert("Soulbound tokens cannot be approved for transfer");
}
function setApprovalForAll(address operator, bool approved) public override {
revert("Soulbound tokens cannot be approved for transfer");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment