JSPM

@paulstinchcombe/kaminftlibrary

0.0.3
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 3
  • Score
    100M100P100Q34556F
  • License MIT

A comprehensive Solidity library providing extended features for ERC721 and ERC1155 contracts including programmable royalties, ERC20 payment integration, platform commission system, time-based rental system, and mandatory royalty enforcement.

Package Exports

    This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@paulstinchcombe/kaminftlibrary) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

    Readme

    KamiNFTLibrary

    A comprehensive Solidity library providing advanced features for ERC721 and ERC1155 contracts, including programmable royalties, ERC20 payment integration, platform commission system, time-based rental system, and mandatory royalty enforcement.

    Features

    🎯 Core Features

    • Programmable Royalties: Multiple receivers with configurable percentages (up to 30%)
    • ERC20 Payment Integration: Native support for ERC20 tokens as payment
    • Platform Commission System: Built-in platform fee collection (up to 20%)
    • Time-based Rental System: NFT rental functionality with automatic expiration
    • Mandatory Royalty Enforcement: Ensures royalties are paid before transfers
    • Role-based Access Control: Secure permission management
    • Storage Slot Pattern: Proxy-compatible storage using dedicated slots

    🔧 Advanced Capabilities

    • Token-specific Royalties: Set different royalty structures per token
    • Dual Royalty Types: Separate mint and transfer royalty configurations
    • Rental Management: Extend, end, and track rental periods
    • Transfer Validation: Comprehensive transfer validation with royalty checks
    • Event Tracking: Extensive event emission for transparency
    • Rounding & Dust Handling: Robust handling of rounding errors and dust in royalty splits
    • ERC721/ERC1155 Support: Compatible with both token standards
    • Gas Optimization: Efficient functions with optimized gas usage

    Installation

    npm install @paulstinchcombe/kaminftlibrary

    Quick Start Example

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.24;
    
    import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
    import "@openzeppelin/contracts/access/AccessControl.sol";
    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    import "@paulstinchcombe/kaminftlibrary/contracts/KamiNFTLibrary.sol";
    
    contract MyNFT is ERC721Enumerable, AccessControl {
        using KamiNFTLibrary for *;
        using SafeERC20 for IERC20;
    
        IERC20 public paymentToken;
        address public contractOwner;
    
        constructor(address _paymentToken) ERC721("MyNFT", "MNFT") {
            paymentToken = IERC20(_paymentToken);
            contractOwner = msg.sender;
            _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
            _grantRole(KamiNFTLibrary.OWNER_ROLE, msg.sender);
    
            // Initialize library configurations
            KamiNFTLibrary.initializeRoyaltyConfig();
        }
    
        // Platform and royalty config initialization
        function initializePlatform(address platform, uint96 commission) external onlyRole(DEFAULT_ADMIN_ROLE) {
            KamiNFTLibrary.initializePlatform(platform, commission);
        }
    
        function setRoyaltyPercentage(uint96 pct) external onlyRole(DEFAULT_ADMIN_ROLE) {
            KamiNFTLibrary.setRoyaltyPercentage(pct, address(this));
        }
    
        function setMintRoyalties(KamiNFTLibrary.RoyaltyData[] calldata royalties) external onlyRole(DEFAULT_ADMIN_ROLE) {
            KamiNFTLibrary.setMintRoyalties(royalties, address(this));
        }
    
        function setTransferRoyalties(KamiNFTLibrary.RoyaltyData[] calldata royalties) external onlyRole(DEFAULT_ADMIN_ROLE) {
            KamiNFTLibrary.setTransferRoyalties(royalties, address(this));
        }
    
        // Payment distribution
        function distributeMintRoyalties(uint256 tokenId, uint256 mintPrice) external {
            paymentToken.safeTransferFrom(msg.sender, address(this), mintPrice);
            KamiNFTLibrary.distributeMintRoyalties(tokenId, mintPrice, paymentToken);
        }
    
        function distributeTransferRoyalties(uint256 tokenId, uint256 salePrice) external returns (uint256) {
            paymentToken.safeTransferFrom(msg.sender, address(this), salePrice);
            return KamiNFTLibrary.distributeTransferRoyalties(tokenId, salePrice, paymentToken);
        }
    
        // Rental system
        function rentToken(uint256 tokenId, uint256 duration, uint256 rentalPrice) external {
            KamiNFTLibrary.rentToken(paymentToken, tokenId, duration, rentalPrice, ownerOf(tokenId), address(this), false);
        }
    
        function endRental(uint256 tokenId) external {
            KamiNFTLibrary.endRentalSimple(tokenId, ownerOf(tokenId), false);
        }
    
        function extendRental(uint256 tokenId, uint256 additionalDuration, uint256 additionalPayment) external {
            KamiNFTLibrary.extendRental(paymentToken, tokenId, additionalDuration, additionalPayment, ownerOf(tokenId), false);
        }
    
        function isRented(uint256 tokenId) external view returns (bool) {
            return KamiNFTLibrary.isRented(tokenId);
        }
    
        // Utility
        function owner() external view returns (address) {
            return contractOwner;
        }
    }

    API Reference

    Configuration Functions

    Platform Configuration

    function initializePlatform(address platformAddress_, uint96 platformCommissionPercentage_) internal

    Initialize platform configuration with commission percentage and address.

    Royalty Configuration

    function initializeRoyaltyConfig() internal
    function setRoyaltyPercentage(uint96 newRoyaltyPercentage, address accessControl) internal
    function setMintRoyalties(RoyaltyData[] calldata royalties, address accessControl) internal
    function setTransferRoyalties(RoyaltyData[] calldata royalties, address accessControl) internal
    function setTokenMintRoyalties(uint256 tokenId, RoyaltyData[] calldata royalties, address accessControl, function(uint256) view returns (bool) exists) internal
    function setTokenTransferRoyalties(uint256 tokenId, RoyaltyData[] calldata royalties, address accessControl, function(uint256) view returns (bool) exists) internal

    Distribution Functions

    Mint Royalties

    function distributeMintRoyalties(uint256 tokenId, uint256 amount, IERC20 paymentToken) internal

    Distribute royalties and platform commission during minting.

    Transfer Royalties

    function distributeTransferRoyalties(uint256 tokenId, uint256 salePrice, IERC20 paymentToken) internal returns (uint256 totalDistributed)

    Distribute royalties and platform commission during transfers.

    Rental Functions

    Core Rental Operations

    function rentToken(IERC20 paymentToken, uint256 tokenId, uint256 duration, uint256 rentalPrice, address tokenOwner, address accessControl, bool isERC1155) internal
    function endRentalSimple(uint256 tokenId, address tokenOwner, bool isERC1155) internal
    function extendRental(IERC20 paymentToken, uint256 tokenId, uint256 additionalDuration, uint256 additionalPayment, address tokenOwner, bool isERC1155) internal

    Rental Queries

    function isRented(uint256 tokenId) internal view returns (bool)
    function getRentalInfo(mapping(uint256 => Rental) storage rentals, uint256 tokenId, function(uint256) view returns (bool) exists) internal view returns (Rental memory)

    Transfer Functions

    Sale and Transfer

    function sellToken(IERC20 paymentToken, uint256 tokenId, address to, uint256 salePrice, address seller) internal
    function validateTransfer(uint256 tokenId, address from, address to, address tokenOwner, function(address, address) view returns (bool) isApprovedForAll, function(uint256) view returns (address) getApproved) internal view
    function updateRentalOnTransferSimple(uint256 tokenId, address from, address to) internal

    Royalty Payment

    function initiateTransferWithRoyalty(uint256 tokenId, address to, uint256 salePrice, address owner) internal
    function payTransferRoyalty(IERC20 paymentToken, uint256 tokenId, address to, uint256 salePrice, address buyer, address seller) internal
    function calculateTransferPayment(uint256 salePrice) internal view returns (uint256 totalRequired)
    function isTransferRoyaltyRequired(uint256 tokenId, address from, address to, uint256 salePrice) internal view returns (bool)

    Getter Functions

    function platformAddress() internal view returns (address)
    function platformCommission() internal view returns (uint96)
    function royaltyPercentage() internal view returns (uint96)
    function getMintRoyaltyReceivers(uint256 tokenId) internal view returns (RoyaltyData[] memory)
    function getTransferRoyaltyReceivers(uint256 tokenId) internal view returns (RoyaltyData[] memory)

    Data Structures

    struct RoyaltyData {
        address receiver;      // The address that will receive the royalty payment
        uint96 feeNumerator;   // The royalty percentage in basis points (e.g., 1000 = 10%)
    }
    
    struct Rental {
        address renter;        // The address currently renting the token
        uint256 startTime;     // The timestamp when the rental started
        uint256 endTime;       // The timestamp when the rental will end
        uint256 rentalPrice;   // The total price paid for the rental
        bool active;           // Whether the rental is currently active
    }
    
    struct PlatformConfig {
        uint96 commissionPercentage; // The platform commission percentage in basis points
        address platformAddress;     // The address that receives platform commission payments
    }
    
    struct RoyaltyConfig {
        uint96 royaltyPercentage;           // The global royalty percentage in basis points
        RoyaltyData[] mintRoyaltyReceivers; // Array of royalty receivers for mint operations
        RoyaltyData[] transferRoyaltyReceivers; // Array of royalty receivers for transfer operations
        mapping(uint256 => RoyaltyData[]) tokenMintRoyalties;     // Token-specific mint royalties
        mapping(uint256 => RoyaltyData[]) tokenTransferRoyalties; // Token-specific transfer royalties
    }

    Usage Examples

    Setting Up Royalties

    // Initialize with 10% global royalty and 5% platform commission
    KamiNFTLibrary.initializeRoyaltyConfig();
    KamiNFTLibrary.initializePlatform(platformAddress, 500); // 5%
    
    // Set mint royalties: 70% to artist, 30% to platform
    KamiNFTLibrary.RoyaltyData[] memory mintRoyalties = new KamiNFTLibrary.RoyaltyData[](2);
    mintRoyalties[0] = KamiNFTLibrary.RoyaltyData(artistAddress, 7000); // 70%
    mintRoyalties[1] = KamiNFTLibrary.RoyaltyData(platformAddress, 3000); // 30%
    KamiNFTLibrary.setMintRoyalties(mintRoyalties, address(this));
    
    // Set transfer royalties: 60% to artist, 40% to creator
    KamiNFTLibrary.RoyaltyData[] memory transferRoyalties = new KamiNFTLibrary.RoyaltyData[](2);
    transferRoyalties[0] = KamiNFTLibrary.RoyaltyData(artistAddress, 6000); // 60%
    transferRoyalties[1] = KamiNFTLibrary.RoyaltyData(creatorAddress, 4000); // 40%
    KamiNFTLibrary.setTransferRoyalties(transferRoyalties, address(this));

    Minting with Royalties

    function mintWithRoyalties(uint256 tokenId, uint256 mintPrice) external {
        // Transfer payment from minter to contract
        paymentToken.safeTransferFrom(msg.sender, address(this), mintPrice);
    
        // Distribute royalties and platform commission
        KamiNFTLibrary.distributeMintRoyalties(tokenId, mintPrice, paymentToken);
    
        // Mint the token
        _mint(msg.sender, tokenId);
    }

    Renting Tokens

    function rentToken(uint256 tokenId, uint256 duration, uint256 rentalPrice) external {
        // Rent token for specified duration
        KamiNFTLibrary.rentToken(
            paymentToken,
            tokenId,
            duration,
            rentalPrice,
            ownerOf(tokenId),
            address(this),
            false // isERC1155
        );
    }
    
    function endRental(uint256 tokenId) external {
        // End rental early (only owner or renter can call)
        KamiNFTLibrary.endRentalSimple(tokenId, ownerOf(tokenId), false);
    }

    Selling with Royalties

    function sellToken(uint256 tokenId, address buyer, uint256 salePrice) external {
        // Sell token with automatic royalty distribution
        KamiNFTLibrary.sellToken(
            paymentToken,
            tokenId,
            buyer,
            salePrice,
            msg.sender // seller
        );
    
        // Transfer token to buyer
        _transfer(msg.sender, buyer, tokenId);
    }

    Testing

    The project includes a comprehensive test suite covering all functionality:

    Test Coverage

    • 20/20 tests passing
    • Platform configuration and validation
    • Royalty management and distribution
    • Mint and transfer royalty calculations
    • Rental system functionality
    • Edge cases and rounding handling
    • Buyer-as-royalty-receiver scenarios

    Gas Optimization Results

    • distributeMintRoyalties: ~104k gas
    • distributeTransferRoyalties: ~125k gas
    • rentToken: ~213k gas
    • sellToken: ~182k gas

    Running Tests

    # Run all tests
    npx hardhat test
    
    # Run with gas reporting
    npx hardhat test --gas
    
    # Run specific test file
    npx hardhat test test/KamiNFTLibrary.test.ts

    Deployment

    Prerequisites

    • Node.js >= 16.0.0
    • npm >= 8.0.0
    • Hardhat
    • OpenZeppelin Contracts v5

    Setup

    # Install dependencies
    npm install
    
    # Compile contracts
    npx hardhat compile
    
    # Run tests
    npx hardhat test
    
    # Deploy to network (example)
    npx hardhat run scripts/deploy.js --network mainnet

    Environment Configuration

    # .env file
    PRIVATE_KEY=your_private_key
    INFURA_URL=your_infura_url
    ETHERSCAN_API_KEY=your_etherscan_key

    Security Considerations

    Access Control

    • All critical functions are protected by role-based access control
    • Maximum commission rate: 20% (2000 basis points)
    • Maximum royalty rate: 30% (3000 basis points)

    Validation

    • Rental and transfer validation ensures royalties are always paid
    • Token existence checks prevent invalid operations
    • Rental expiration is automatically enforced

    Rounding & Dust Handling

    • Rounding errors are handled by sending dust to the first royalty receiver
    • All calculations use safe math operations
    • Comprehensive edge case testing

    Troubleshooting

    Common Issues

    "Caller is not an owner"

    • Ensure the caller has the OWNER_ROLE in the AccessControl contract
    • Check that the role was granted during contract initialization

    "Royalty payment required before transfer"

    • Use initiateTransferWithRoyalty and payTransferRoyalty functions
    • Ensure the buyer has approved sufficient tokens for the contract

    "Token is currently rented"

    • Check rental status with isRented(tokenId)
    • End rental with endRentalSimple if needed

    Rounding Errors

    • The library automatically handles rounding by sending dust to the first receiver
    • Use .to.be.closeTo() in tests for approximate equality checks

    Gas Optimization Tips

    • Use the simplified rental functions (endRentalSimple, updateRentalOnTransferSimple)
    • Batch operations when possible
    • Consider using ERC1155 for multiple token types

    License

    MIT License - see LICENSE file for details.

    Contributing

    1. Fork the repository
    2. Create a feature branch
    3. Make your changes
    4. Add tests for new functionality
    5. Ensure all tests pass
    6. Submit a pull request

    Support