@lazysuperheroes/token-graveyard
v2.1.2
Published
Permanent NFT storage contract for Hedera with royalty bypass capabilities
Maintainers
Readme
Token Graveyard v2.0
A permanent NFT storage solution on Hedera with royalty bypass capabilities.
🎯 Overview
Token Graveyard is a "trapdoor" smart contract that permanently stores NFTs. Once an NFT enters the graveyard, it can never be retrieved. Version 2.0 introduces:
- Royalty Bypass: Stake NFTs with royalties using HTS allowances to avoid royalty fees
- Dual Pathways: Direct send for NFTs without royalties, staking for NFTs with royalties
- Role-Based Access: Admin and ContractUser roles for flexible ecosystem integration
- LazyGasStation Integration: Centralized $LAZY payment and burn mechanism
- Enhanced Events: Clean, indexed events for easy tracking and UX integration
📦 NPM Package Usage
Installation
npm install @lazysuperheroes/token-graveyardImporting in Solidity
Basic Integration (Recommended):
// Use the interface - no internal dependencies exposed
import "@lazysuperheroes/token-graveyard/contracts/interfaces/ITokenGraveyard.sol";
contract MyNFTBurner {
ITokenGraveyard public graveyard;
constructor(address graveyardAddress) {
graveyard = ITokenGraveyard(graveyardAddress);
}
function burnMyNFTs(address tokenAddress, uint256[] memory serials) external {
// User must have set NFT allowances to this contract first
graveyard.stakeNFTsToTheGraveOnBehalf(tokenAddress, serials, msg.sender);
}
}Advanced - Direct Implementation Inheritance:
// Only if you need to extend functionality
import "@lazysuperheroes/token-graveyard/contracts/TokenGraveyard.sol";Available Imports
// Core Interface (most common)
import "@lazysuperheroes/token-graveyard/contracts/interfaces/ITokenGraveyard.sol";
// Other Interfaces
import "@lazysuperheroes/token-graveyard/contracts/interfaces/ILazyGasStation.sol";
import "@lazysuperheroes/token-graveyard/contracts/interfaces/ILazyDelegateRegistry.sol";
import "@lazysuperheroes/token-graveyard/contracts/interfaces/IRoles.sol";
// Full Implementations (if extending)
import "@lazysuperheroes/token-graveyard/contracts/TokenGraveyard.sol";
import "@lazysuperheroes/token-graveyard/contracts/TokenStaker.sol";Integration Testing
Deploy in your test suite:
const TokenGraveyardABI = require('@lazysuperheroes/token-graveyard/abi/TokenGraveyard.json');
const { ethers } = require('hardhat');
describe("My Contract Integration", function() {
let graveyard;
beforeEach(async function() {
// Deploy graveyard for testing
const TokenGraveyard = await ethers.getContractFactory(
TokenGraveyardABI.abi,
TokenGraveyardABI.bytecode
);
graveyard = await TokenGraveyard.deploy(
lazyTokenAddress,
lazyGasStationAddress,
registryAddress, // or ethers.ZeroAddress
ethers.parseUnits("0", 8), // Free for testing
0 // No burn for testing
);
await graveyard.waitForDeployment();
});
it("should bury NFTs via graveyard", async function() {
// Your integration test
await graveyard.stakeNFTsToTheGrave(nftAddress, serials);
});
});Version Compatibility
Included Hedera Precompiles:
HederaTokenServiceLite.sol- Lightweight HTS interfaceHederaResponseCodes.sol- Hedera system response codes
Note: These are internal dependencies. If your project uses different variants, there's no conflict as long as you use explicit import paths:
// Your code uses the graveyard's interface - no HTS exposure
import "@lazysuperheroes/token-graveyard/contracts/interfaces/ITokenGraveyard.sol";
// Your project can have its own HTS variant
import "./contracts/hedera/MyHederaTokenService.sol";
// No clash - different import pathsPeer Dependencies
{
"peerDependencies": {
"@openzeppelin/contracts": "^4.9.6"
}
}Make sure you have OpenZeppelin installed in your project.
📘 For complete integration examples, deployment patterns, and troubleshooting, see INTEGRATION_GUIDE.md
🏗️ Architecture
Core Components
- TokenGraveyard.sol: Main contract inheriting from TokenStaker
- TokenStaker.sol: NFT staking module using HTS allowances
- LazyGasStation.sol: Centralized payment handler for $LAZY tokens
- LazyDelegateRegistry.sol (optional): NFT delegation management
Inheritance Chain
TokenGraveyard
├─> TokenStaker
│ ├─> HederaTokenService
│ └─> Uses: LazyGasStation, LazyDelegateRegistry
└─> IRoles (interface)📋 Business Logic
Two Pathways for Burial
1. Direct Send via SDK (No Royalties)
- Use Case: NFTs without royalties attached
- Method: Hedera SDK
TransferTransaction(no contract call needed) - Requirements:
- Token must be associated with graveyard first (via
associateToken()orassociateTokenFree()) - User transfers NFTs directly to graveyard address using Hedera SDK
- Token must be associated with graveyard first (via
- Transfer Type: Standard HTS transfer via SDK
- Why No Contract Call?: For no-royalty NFTs, there's no need for the allowance-based bypass mechanism
2. Staking Send (With Royalties)
- Use Case: NFTs with royalties attached
- Method:
stakeNFTsToTheGrave(address tokenAddress, uint256[] memory serials) - Requirements:
- User sets NFT allowance to graveyard contract
- User pays nominal $LAZY cost (if not admin/contract user)
- Token auto-associated if needed
- Unlimited NFTs (batched in groups of 8)
- Transfer Type: HTS allowance-based transfer (bypasses royalties)
- Why It Works: Uses 1 tinybar allowance transfers that avoid triggering royalty fees
Role System
Admin Role
- Add/remove other admins (cannot remove last admin)
- Add/remove contract users
- Update $LAZY cost and burn percentage
- Withdraw hbar and $LAZY from contract
- Associate tokens for free
ContractUser Role
- Associate tokens for free
- Stake NFTs on behalf of other users (for ecosystem contracts)
- Example: Token swap contract sends old NFTs to graveyard
Regular Users
- Pay nominal $LAZY cost to associate tokens
- Send/stake their own NFTs to graveyard
Payment Flow
- User Action: Calls graveyard method
- $LAZY Payment: Graveyard calls
LazyGasStation.drawLazyFrom(user, amount, burnPercentage) - LazyGasStation: Takes $LAZY from user allowance, burns percentage, keeps remainder
- NFT Transfer: Graveyard completes NFT burial
- Event Emission:
NFTsBuriedevent fired with details
Gas Management
- LazyGasStation holds pools of hbar and $LAZY
- TokenStaker's
refill()modifier auto-refills from LazyGasStation when low - Enables smooth staking operations without user gas concerns
🚀 Deployment
Prerequisites
npm installEnvironment Variables
Create a .env file:
# Network
ENVIRONMENT=TEST # or MAIN
# Operator Account
ACCOUNT_ID=0.0.YOUR_ACCOUNT
PRIVATE_KEY=YOUR_PRIVATE_KEY
# Existing Contracts (if already deployed)
LAZY_CONTRACT=0.0.LAZY_SCT_CONTRACT_ID
LAZY_TOKEN=0.0.LAZY_TOKEN_ID
LAZY_GAS_STATION_CONTRACT_ID=0.0.GAS_STATION_ID
LAZY_DELEGATE_REGISTRY_CONTRACT_ID=0.0.REGISTRY_ID # Optional
# Configuration
LAZY_BURN_PERCENT=25 # Percentage of $LAZY payment to burn
LAZY_GRAVEYARD_COST=10 # Cost in $LAZY (before decimals)
LAZY_DECIMALS=1
# Test Accounts (optional - will auto-create if not specified)
ALICE_ACCOUNT_ID=
ALICE_PRIVATE_KEY=
BOB_ACCOUNT_ID=
BOB_PRIVATE_KEY=Deploy Contracts
# Deploy to testnet
npm run deploy
# Run tests
npm run test
# Interact with deployed contract
npm run interactDeployment Order
- LAZYTokenCreator (if not exists): Mints $LAZY token
- LazyGasStation (if not exists): Manages payments and gas
- LazyDelegateRegistry (optional): Delegation management
- TokenGraveyard: Main graveyard contract
- Configuration:
- Add graveyard as contract user to LazyGasStation
- Fund LazyGasStation with hbar and $LAZY
💻 UX Implementation Guide
For Regular Users
Scenario 1: Bury NFTs Without Royalties
// 1. Ensure token is associated with graveyard (one-time per token)
// Option A: Admin/ContractUser associates for free
await graveyardContract.associateTokenFree(nftTokenAddress);
// Option B: Regular user pays $LAZY to associate
await setAllowance(lazyToken, lazyGasStation, lazyCost);
await graveyardContract.associateToken(nftTokenAddress);
// 2. Transfer NFTs directly to graveyard via Hedera SDK
const transferTx = new TransferTransaction()
.addNftTransfer(nftTokenId, serial1, userAccountId, graveyardAccountId)
.addNftTransfer(nftTokenId, serial2, userAccountId, graveyardAccountId);
await transferTx.execute(client);
// 3. NFTs are permanently stored (no contract call needed for transfer)Scenario 2: Bury NFTs With Royalties (Avoid Fees)
// 1. Set $LAZY allowance to LazyGasStation
await setAllowance(lazyToken, lazyGasStation, lazyCost);
// 2. Set NFT allowance to graveyard
await setNFTAllowanceAll(nftToken, graveyardContract);
// 3. Call staking method (bypasses royalties, auto-associates)
await graveyardContract.stakeNFTsToTheGrave(
nftTokenAddress,
[serial1, serial2, ..., serialN] // Unlimited (batched)
);
// 4. NFTs are permanently stored, no royalties paidFor Contract Users (Ecosystem Contracts)
// Example: Token Swap Contract
// 1. User approves old NFTs to swap contract
// 2. Swap contract calls graveyard on behalf of user
await graveyardContract.stakeNFTsToTheGraveOnBehalf(
oldNFTToken,
[serial1, serial2],
userAddress // The actual owner
);
// 3. Swap contract gives user new NFTs
// 4. Old NFTs permanently stored in graveyardFor Admins
// Add contract user
await graveyardContract.addContractUser(swapContractAddress);
// Update costs
await graveyardContract.updateCost(newLazyCost, newBurnPercentage);
// Associate token for free
await graveyardContract.associateTokenFree(newNFTToken);
// Withdraw accumulated funds
await graveyardContract.withdrawLazy(treasuryAddress, amount);
await graveyardContract.withdrawHbar(treasuryAddress, amount);Monitoring Events
// Listen for burial events
graveyardContract.on('NFTsBuried', (user, token, serials, viaStaking) => {
console.log(`User ${user} buried ${serials.length} NFTs from ${token}`);
console.log(`Method: ${viaStaking ? 'Staking' : 'Direct'}`);
});
// Listen for role changes
graveyardContract.on('RoleUpdated', (executor, target, role, added) => {
console.log(`${executor} ${added ? 'added' : 'removed'} ${target} as ${role}`);
});📝 Contract Methods
Public Methods
Token Association
associateToken(address tokenAddress): Paid association for regular usersassociateTokenFree(address tokenAddress): Free association for admins/contract usersbatchAssociateTokens(address[] tokenAddresses): Batch free association
NFT Burial
stakeNFTsToTheGrave(address tokenAddress, uint256[] serials): Staking send for royalty NFTs (max 50, batched internally)stakeNFTsToTheGraveOnBehalf(address tokenAddress, uint256[] serials, address onBehalfOf): Contract user stakes for others- Direct Send: For no-royalty NFTs, use Hedera SDK
TransferTransactiondirectly (no contract method needed)
Role Management
addAdmin(address admin): Add adminremoveAdmin(address admin): Remove adminaddContractUser(address contractUser): Add contract userremoveContractUser(address contractUser): Remove contract user
Admin Functions
updateCost(uint256 lazyCost, uint256 lazyBurnPercentage): Update costswithdrawLazy(address receiver, uint256 amount): Withdraw $LAZYwithdrawHbar(address receiver, uint256 amount): Withdraw hbar
View Functions
getCost(): Returns (lazyCost, lazyBurnPercentage)isTokenAssociated(address tokenAddress): Check if token associatedgetAssociatedTokens(): Get all associated tokensgetAdmins(): Get all adminsgetContractUsers(): Get all contract usersisAdmin(address account): Check if account is adminisContractUser(address account): Check if account is contract user
🔒 Security Features
Custom Errors
- Gas-efficient error handling
- Clear error messages for debugging
- Type-safe error parameters
ReentrancyGuard
- Protects all state-changing functions
- Prevents re-entrancy attacks
Role-Based Access Control
- EnumerableSet for efficient role management
- Cannot remove last admin
- Granular permissions
NFT Trapping
- No withdrawal methods
- No delegate-back functionality
- Truly permanent storage
🧪 Testing
# Run full test suite
npm run test
# Specific test file
npx mocha test/TokenGraveyard.test.jsTest Coverage
- ✅ Deployment and initialization
- ✅ Role management (add/remove admins and contract users)
- ✅ Access control enforcement
- ✅ Token association (paid and free)
- ✅ Staking NFT burial (royalty bypass)
- ✅ Batch processing (>8 NFTs)
- ✅ On-behalf staking (contract users)
- ✅ Admin functions (withdraw, update costs)
- ✅ Edge cases (empty arrays, zero serials, limits)
📊 Gas Estimates
| Operation | Gas Limit | Notes | |-----------|-----------|-------| | Deploy | 4,500,000 | Initial deployment | | Associate Token (Paid) | 1,350,000 | BASE_GAS + ASSOCIATION_GAS | | Associate Token (Free) | 1,350,000 | Admin/contract user | | Stake NFTs | 2,500,000 | Up to 8 NFTs (royalty bypass) | | Stake NFTs (Batch) | 3,500,000 | 9-50 NFTs | | Update Cost | 400,000 | Admin function | | Withdraw | 600,000 | $LAZY or hbar | | Direct Send (SDK) | N/A | No contract gas - uses Hedera SDK |
🛠️ Scripts
Decode Error
node scripts/decodeSmartContractError.js testnet 0.0.CONTRACT_IDGet Logs
npm run logsSolhint
npm run solhint🌟 Use Cases
- Burn Old NFT Collections: Project wants to permanently remove old/deprecated NFTs
- Token Swaps: Upgrade mechanism where old NFTs are buried and new ones issued
- Community Cleanup: Remove spam or unwanted NFTs from circulation
- Deflationary Mechanics: Reduce total supply for scarcity
- Royalty Avoidance: Move NFTs with high royalties without paying fees
⚠️ Important Notes
- Permanent Storage: NFTs sent to graveyard can NEVER be retrieved
- No Delegation: Delegation is hardcoded to false - NFTs are locked forever
- Cost Management: Admins should monitor and adjust $LAZY costs as needed
- Gas Station Funding: LazyGasStation must have sufficient hbar/$LAZY
- Allowances: Users must set proper allowances before staking
📜 License
MIT
🤝 Contributing
Contributions welcome! Please test thoroughly before submitting PRs.
📞 Support
For issues or questions, please open a GitHub issue.
Version: 2.0
Author: stowerling.eth / stowerling.hbar
Network: Hedera Mainnet / Testnet
