npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@paulstinchcombe/kaminftlibrary

v0.0.3

Published

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.

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