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

kamiweb3-sdk

v0.1.2

Published

TypeScript SDK for KAMI721-C, KAMI721-AC, and KAMI1155-C smart contracts

Downloads

8

Readme

KAMI Web3 SDK

npm version GitHub repo

A TypeScript SDK for interacting with KAMI smart contracts (ERC721C, ERC721AC, ERC1155C), simplifying deployment and contract interactions using ethers.js v6.

This SDK provides:

  • Factories for deploying standard and upgradeable (Transparent Proxy) versions of the contracts.
  • Type-safe wrappers around the contract ABIs for easy interaction.
  • Support for common KAMI features like programmable royalties, USDC payments, platform commissions, rentals, and ERC721A/ERC1155 optimizations.
  • Comprehensive test suites with full coverage of contract functionality.
  • Full ethers v6 compatibility with improved type safety and error handling.
  • Robust rental system with proper payment handling and timing calculations.
  • Production-ready build with zero TypeScript compilation errors.

Installation

npm install kamiweb3-sdk ethers@^6
# or
yarn add kamiweb3-sdk ethers@^6

Note: This SDK requires ethers v6 as a peer dependency.

Setup

Import the necessary components and set up your ethers signer or provider.

import { ethers, Wallet, JsonRpcProvider } from 'ethers';
import {
	// Factories
	ERC721CFactory,
	ERC721ACFactory,
	ERC1155CFactory,
	// Wrappers (usually returned by factories)
	ERC721CWrapper,
	ERC721ACWrapper,
	ERC1155CWrapper,
	// Types
	SignerOrProvider,
	RoyaltyData,
	RentalDetails,
	RoyaltyInfo,
	// Deploy/Init Args (Import specific args as needed)
	ERC721CDeployArgs, // Or ERC721ACDeployArgs, ERC1155CDeployArgs
	ERC721CInitializeArgs, // Or ERC721ACInitializeArgs, ERC1155CInitializeArgs
} from 'kamiweb3-sdk';

// Example setup (replace with your actual provider/signer)
const provider = new JsonRpcProvider('http://localhost:8545'); // Or your RPC URL
const privateKey = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; // Replace with deployer/signer private key
const signer: Signer = new Wallet(privateKey, provider);

// Example Addresses (replace with actual addresses for your deployment)
// Use testnet addresses (e.g., Sepolia) or mainnet as appropriate
const USDC_ADDRESS = '0x...'; // e.g., Sepolia USDC: 0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8
const PLATFORM_ADDRESS = '0x...'; // Address for receiving platform fees
const OWNER_ADDRESS = await signer.getAddress(); // Often the deployer

Usage Examples

Below are common use cases for each contract type. Remember to approve the respective contract address for USDC spending from the interacting account before calling methods like mint, rentToken, claim, etc.


ERC721C (Standard NFT with Royalties & Rentals)

1. Deploying Standard Contract

import { ethers } from 'ethers';
import { ERC721CFactory, ERC721CDeployArgs } from 'kamiweb3-sdk';

async function deployERC721C(signer: Signer) {
	const deployArgs: ERC721CDeployArgs = {
		usdcAddress: USDC_ADDRESS,
		name: 'My KAMI 721C NFT',
		symbol: 'K721C',
		baseURI: 'https://api.example.com/nft/721c/',
		initialMintPrice: ethers.parseUnits('100', 6), // 100 USDC (assuming 6 decimals)
		platformAddress: PLATFORM_ADDRESS,
		platformCommissionPercentage: 500, // 5% (500 basis points)
	};

	console.log('Deploying standard ERC721C...');
	const erc721c: ERC721CWrapper = await ERC721CFactory.deploy(deployArgs, signer);
	const deployedAddress = await erc721c.contract.getAddress(); // Use wrapper.contract.getAddress()
	console.log(`Standard ERC721C deployed at: ${deployedAddress}`);
	return erc721c;
}

// const my721c = await deployERC721C(signer);

2. Deploying Upgradeable Contract (Transparent Proxy)

import { ethers } from 'ethers';
import { ERC721CFactory, ERC721CInitializeArgs } from 'kamiweb3-sdk';

async function deployUpgradeableERC721C(signer: Signer) {
	const initArgs: ERC721CInitializeArgs = {
		usdcAddress: USDC_ADDRESS,
		name: 'My Upgradeable KAMI 721C NFT',
		symbol: 'UK721C',
		baseURI: 'https://api.example.com/nft/721c-upg/',
		initialMintPrice: ethers.parseUnits('150', 6), // 150 USDC
		platformAddress: PLATFORM_ADDRESS,
		platformCommissionPercentage: 500, // 5%
	};

	// Optional: Specify a different owner for the ProxyAdmin
	const proxyAdminOwner = '0x...'; // Address of the admin owner

	console.log('Deploying upgradeable ERC721C...');
	const erc721cProxy: ERC721CWrapper = await ERC721CFactory.deployUpgradeable(
		initArgs,
		signer
		// proxyAdminOwner // Uncomment to set a specific admin owner
	);
	const proxyAddress = await erc721cProxy.contract.getAddress();
	console.log(`Upgradeable ERC721C proxy deployed at: ${proxyAddress}`);
	// Note: The returned wrapper is already attached to the proxy address
	// and uses the KAMI721CUpgradeable ABI for interaction.
	return erc721cProxy;
}

// const my721cProxy = await deployUpgradeableERC721C(signer);

3. Attaching to Existing Contracts

import { ERC721CFactory, SignerOrProvider } from 'kamiweb3-sdk';

const existingStandardAddress = '0x...';
const existingProxyAddress = '0x...';
const signerOrProvider: SignerOrProvider = signer; // Or your provider

// Attach to standard contract (uses standard ABI)
const attached721c = ERC721CFactory.attach(existingStandardAddress, signerOrProvider);

// Attach to upgradeable contract (uses upgradeable ABI)
const attached721cProxy = ERC721CFactory.attachUpgradeable(existingProxyAddress, signerOrProvider);

console.log('Attached to standard contract:', await attached721c.name());
console.log('Attached to upgradeable contract:', await attached721cProxy.name());

4. Calling Methods (Example: Mint, Set Royalties, Rent)

import { ethers } from 'ethers';
import { ERC721CWrapper, RoyaltyData } from 'kamiweb3-sdk';

// Assume 'erc721c' is an ERC721CWrapper instance connected to a signer
async function interactWith721C(erc721c: ERC721CWrapper, signerAddress: string) {
	// Ensure you have USDC approved for the contract address for minting/renting

	// --- Minting ---
	console.log('Minting token...');
	const mintTx = await erc721c.mint();
	const mintReceipt = await mintTx.wait();
	// Extract tokenId from events (e.g., Transfer event from ZeroAddress)
	const transferTopic = erc721c.contract.getEvent('Transfer').fragment.topicHash;
	const transferLog = mintReceipt?.logs.find(
		(log) => log.topics[0] === transferTopic && log.topics[1] === ethers.zeroPadValue(ethers.ZeroAddress, 32)
	);
	const tokenId = transferLog ? erc721c.contract.interface.parseLog(transferLog as any)?.args.tokenId : null;
	console.log(`Minted token ID: ${tokenId}`);

	if (!tokenId) return;

	// --- Setting Royalties (Requires OWNER_ROLE or appropriate role) ---
	const royaltyRecipient = '0x...'; // Artist/Creator address
	const mintRoyalties: RoyaltyData[] = [
		{ receiver: royaltyRecipient, feeNumerator: 9500 }, // 95% (Platform share is added automatically)
	];
	console.log('Setting mint royalties...');
	// Requires appropriate role (e.g., OWNER_ROLE) granted to the signer
	const setMintRoyaltyTx = await erc721c.setMintRoyalties(mintRoyalties);
	await setMintRoyaltyTx.wait();
	console.log('Mint royalties set.');

	const transferRoyalties: RoyaltyData[] = [
		{ receiver: royaltyRecipient, feeNumerator: 1000 }, // 10%
	];
	console.log('Setting transfer royalties...');
	const setTransferRoyaltyTx = await erc721c.setTransferRoyalties(transferRoyalties);
	await setTransferRoyaltyTx.wait();
	console.log('Transfer royalties set.');

	// --- Renting (Example assumes caller is renter) ---
	const rentalDurationSeconds = 60 * 60 * 24; // 1 day
	const rentalPrice = ethers.parseUnits('10', 6); // 10 USDC
	console.log(`Renting token ${tokenId}...`);
	// Make sure the signer has approved USDC spending for the contract
	const rentTx = await erc721c.rentToken(tokenId, rentalDurationSeconds, rentalPrice);
	await rentTx.wait();
	console.log(`Token ${tokenId} rented.`);

	// --- Get Rental Details ---
	const details = await erc721c.getRentalDetails(tokenId);
	console.log('Rental Details:', details); // { renter: '...', rentalEndTime: ... }
}

// Example call:
// Assuming my721cProxy is an instance attached to a deployed contract and signer
// const signerAddr = await signer.getAddress();
// await interactWith721C(my721cProxy, signerAddr);

ERC721AC (ERC721A based NFT with Royalties & Claim)

Deployment (ERC721ACFactory.deploy, ERC721ACFactory.deployUpgradeable) and attachment (ERC721ACFactory.attach, ERC721ACFactory.attachUpgradeable) follow the same pattern as ERC721C, using ERC721ACFactory, ERC721ACDeployArgs / ERC721ACInitializeArgs, and result in an ERC721ACWrapper.

Calling Methods (Example: Mint Batch, Claim, Get Royalties)

import { ethers } from 'ethers';
import { ERC721ACWrapper, RoyaltyData } from 'kamiweb3-sdk';

// Assume 'erc721ac' is an ERC721ACWrapper instance connected to a signer
async function interactWith721AC(erc721ac: ERC721ACWrapper) {
	// Ensure USDC approval for minting/claiming

	// --- Minting Batch (ERC721A feature) ---
	const quantityToMint = 3;
	console.log(`Minting ${quantityToMint} tokens...`);
	// Assumes mint price is set and USDC is approved
	const mintTx = await erc721ac.mint(quantityToMint);
	const mintReceipt = await mintTx.wait();
	console.log('Mint successful. Tx:', mintReceipt?.hash);

	// Determine minted IDs (ERC721A emits consecutive Transfer events)
	const transferTopic = erc721ac.contract.getEvent('Transfer').fragment.topicHash;
	const transferLogs = mintReceipt?.logs.filter((log) => log.topics[0] === transferTopic);
	const mintedIds = transferLogs?.map((log) => erc721ac.contract.interface.parseLog(log as any)?.args.tokenId);
	console.log('Minted Token IDs:', mintedIds);
	const firstMintedId = mintedIds?.[0];

	if (!firstMintedId) return;

	// --- Claiming (Example, assumes a 'claim' function exists and is configured) ---
	// This method might not exist on all ERC721AC implementations, or might have different parameters.
	// Adjust based on your specific contract's claim logic.
	try {
		if (typeof (erc721ac.contract as any).claim === 'function') {
			const quantityToClaim = 2;
			console.log(`Attempting to claim ${quantityToClaim} tokens...`);
			// Requires USDC approval if claim has a cost
			const claimTx = await erc721ac.claim(quantityToClaim); // Adjust parameters as needed
			await claimTx.wait();
			console.log('Claim successful.');
		} else {
			console.log('Claim function not found on this contract instance.');
		}
	} catch (error) {
		console.error('Claim failed:', error);
	}

	// --- Getting Royalty Info (ERC2981 Standard) ---
	const salePrice = ethers.parseUnits('500', 6); // Example sale price: 500 USDC
	const royaltyInfo = await erc721ac.royaltyInfo(firstMintedId, salePrice);
	console.log(`Royalty Info for Token ${firstMintedId} at ${ethers.formatUnits(salePrice, 6)} USDC:`, {
		receiver: royaltyInfo.receiver,
		amount: ethers.formatUnits(royaltyInfo.royaltyAmount, 6) + ' USDC',
	});

	// --- Setting Royalties (Requires appropriate role) ---
	// Similar to ERC721C example using setMintRoyalties, setTransferRoyalties, etc.
	// const royaltyRecipient = '0x...';
	// const transferRoyalties: RoyaltyData[] = [{ receiver: royaltyRecipient, feeNumerator: 750 }]; // 7.5%
	// const setTx = await erc721ac.setTransferRoyalties(transferRoyalties);
	// await setTx.wait();
	// console.log('Transfer royalties set.');
}

// Example call:
// Assuming my721acProxy is an instance attached to a deployed contract and signer
// await interactWith721AC(my721acProxy);

ERC1155C (Multi-Token with Royalties & Rentals)

Deployment (ERC1155CFactory.deploy, ERC1155CFactory.deployUpgradeable) and attachment (ERC1155CFactory.attach, ERC1155CFactory.attachUpgradeable) follow the same pattern, using ERC1155CFactory, ERC1155CDeployArgs / ERC1155CInitializeArgs, and result in an ERC1155CWrapper. Note that initialization args typically include a base uri for metadata.

Calling Methods (Example: Mint, Check Balance, Set Token Royalties, Rent)

import { ethers } from 'ethers';
import { ERC1155CWrapper, RoyaltyData } from 'kamiweb3-sdk';

// Assume 'erc1155c' is an ERC1155CWrapper instance connected to a signer
async function interactWith1155C(erc1155c: ERC1155CWrapper, signerAddress: string) {
	// Ensure USDC approval

	const tokenIdToMint = 1; // Example Token ID
	const amountToMint = 10; // Mint 10 copies of Token ID 1
	const mintData = '0x'; // Optional data

	// --- Minting Tokens (Requires MINTER_ROLE or owner) ---
	console.log(`Minting ${amountToMint} of token ID ${tokenIdToMint}...`);
	// Requires appropriate role and USDC approval if mint price is set
	const mintTx = await erc1155c.mint(signerAddress, tokenIdToMint, amountToMint, mintData);
	await mintTx.wait();
	console.log('Mint successful.');

	// --- Checking Balance ---
	const balance = await erc1155c.balanceOf(signerAddress, tokenIdToMint);
	console.log(`Balance of token ID ${tokenIdToMint} for ${signerAddress}: ${balance}`);

	// --- Setting Token-Specific Transfer Royalties (Requires OWNER_ROLE or similar) ---
	const royaltyRecipient = '0x...';
	const tokenRoyalties: RoyaltyData[] = [{ receiver: royaltyRecipient, feeNumerator: 1500 }]; // 15%
	console.log(`Setting transfer royalties for token ID ${tokenIdToMint}...`);
	const setTokenRoyaltyTx = await erc1155c.setTokenTransferRoyalties(tokenIdToMint, tokenRoyalties);
	await setTokenRoyaltyTx.wait();
	console.log('Token transfer royalties set.');

	// --- Getting Royalty Info (ERC2981) ---
	// Note: Royalty info is typically set per-token for ERC1155
	const salePrice = ethers.parseUnits('50', 6); // 50 USDC
	const royaltyInfo = await erc1155c.royaltyInfo(tokenIdToMint, salePrice); // Use the specific token ID
	console.log(`Royalty Info for Token ${tokenIdToMint} at ${ethers.formatUnits(salePrice, 6)} USDC:`, {
		receiver: royaltyInfo.receiver,
		amount: ethers.formatUnits(royaltyInfo.royaltyAmount, 6) + ' USDC',
	});

	// --- Renting (Applies to the specific token ID) ---
	const rentalTokenId = tokenIdToMint; // Rent one of the copies of token ID 1
	const rentalDurationSeconds = 60 * 60; // 1 hour
	const rentalPrice = ethers.parseUnits('5', 6); // 5 USDC
	console.log(`Renting token ID ${rentalTokenId}...`);
	// Requires USDC approval
	const rentTx = await erc1155c.rentToken(rentalTokenId, rentalDurationSeconds, rentalPrice);
	await rentTx.wait();
	console.log(`Token ID ${rentalTokenId} rented.`);

	// --- Get Rental Details (Per Token ID) ---
	const rentalDetails = await erc1155c.getRentalDetails(rentalTokenId);
	console.log(`Rental Details for Token ID ${rentalTokenId}:`, rentalDetails);

	// --- Batch Operations (Example: Check balances) ---
	const otherTokenId = 2;
	const accounts = [signerAddress, PLATFORM_ADDRESS];
	const tokenIds = [tokenIdToMint, otherTokenId];
	const balances = await erc1155c.balanceOfBatch(accounts, tokenIds);
	console.log(
		'Batch Balances:',
		balances.map((b) => b.toString())
	); // [balanceOf(signer, T1), balanceOf(signer, T2), balanceOf(platform, T1), balanceOf(platform, T2)] - order depends on contract implementation logic if flattened. Check contract for exact order. Usually: [balance(account1, id1), balance(account1, id2), ..., balance(account2, id1), balance(account2, id2), ...]
}

// Example call:
// Assuming my1155cProxy is an instance attached to a deployed contract and signer
// const signerAddr = await signer.getAddress();
// await interactWith1155C(my1155cProxy, signerAddr);

Complete ERC721C Lifecycle Example

This example demonstrates deploying, configuring, minting, and selling an ERC721C NFT, highlighting the automatic distribution of funds based on mint/transfer royalties and platform fees.

import { ethers, Wallet, Contract, parseUnits, formatUnits } from 'ethers';
import {
	ERC721CFactory,
	ERC721CWrapper,
	ERC721CDeployArgs,
	RoyaltyData,
	SignerOrProvider, // Assuming Signer is used below
} from 'kamiweb3-sdk';

// --- Setup (Replace with your actual values) ---
const provider = new ethers.JsonRpcProvider('http://localhost:8545'); // Your RPC URL
const deployerPrivateKey = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
const minterPrivateKey = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'; // Needs Sepolia ETH & USDC
const buyerPrivateKey = '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a'; // Needs Sepolia ETH & USDC

const deployerSigner = new Wallet(deployerPrivateKey, provider);
const minterSigner = new Wallet(minterPrivateKey, provider);
const buyerSigner = new Wallet(buyerPrivateKey, provider);

const USDC_ADDRESS_SEPOLIA = '0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8'; // Example Sepolia USDC
const PLATFORM_ADDRESS = '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'; // Example Platform Address
const ROYALTY_RECIPIENT_ADDRESS = '0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c'; // Example Royalty Recipient

const USDC_ABI = [
	// Minimal ABI for ERC20 approve and balanceOf
	'function approve(address spender, uint256 amount) external returns (bool)',
	'function balanceOf(address account) external view returns (uint256)',
];
const usdcContract = new Contract(USDC_ADDRESS_SEPOLIA, USDC_ABI, provider);

async function erc721cLifecycle() {
	console.log('--- Starting ERC721C Lifecycle Example ---');

	// --- 1. Deployment ---
	const deployArgs: ERC721CDeployArgs = {
		usdcAddress: USDC_ADDRESS_SEPOLIA,
		name: 'KAMI Lifecycle NFT',
		symbol: 'KLIFE',
		baseURI: 'ipfs://your-metadata-cid/', // Replace with your actual metadata URI base
		initialMintPrice: parseUnits('100', 6), // 100 USDC
		platformAddress: PLATFORM_ADDRESS,
		platformCommissionPercentage: 500, // 5% platform fee (on mint and transfer)
	};

	console.log('Deploying ERC721C contract...');
	const erc721c: ERC721CWrapper = await ERC721CFactory.deploy(deployArgs, deployerSigner);
	const contractAddress = await erc721c.contract.getAddress();
	console.log(`ERC721C deployed at: ${contractAddress}`);
	// Connect the wrapper to the deployer/owner signer for owner actions
	const erc721cOwner = erc721c.connect(deployerSigner);

	// --- 2. Configuration (Setting Royalties - Requires OWNER_ROLE) ---
	const mintRoyalties: RoyaltyData[] = [
		{ receiver: ROYALTY_RECIPIENT_ADDRESS, feeNumerator: 9500 }, // 95% to recipient (5% platform fee is implicit)
	];
	const transferRoyalties: RoyaltyData[] = [
		{ receiver: ROYALTY_RECIPIENT_ADDRESS, feeNumerator: 1000 }, // 10% to recipient (5% platform fee is implicit)
	];

	console.log('Setting Mint Royalties...');
	const setMintTx = await erc721cOwner.setMintRoyalties(mintRoyalties);
	await setMintTx.wait();
	console.log('Mint Royalties set.');

	console.log('Setting Transfer Royalties...');
	const setTransferTx = await erc721cOwner.setTransferRoyalties(transferRoyalties);
	await setTransferTx.wait();
	console.log('Transfer Royalties set.');

	// --- 3. Minting ---
	const erc721cMinter = erc721c.connect(minterSigner); // Connect wrapper to the minter
	const mintPrice = await erc721cMinter.getMintPrice();
	console.log(`Mint Price: ${formatUnits(mintPrice, 6)} USDC`);

	// ** IMPORTANT: Minter must approve the contract to spend USDC **
	console.log(`Approving ${formatUnits(mintPrice, 6)} USDC for minting...`);
	const approveMintTx = await usdcContract.connect(minterSigner).approve(contractAddress, mintPrice);
	await approveMintTx.wait();
	console.log('USDC approved for minting.');

	console.log('Minting token...');
	const minterBalanceBefore = await usdcContract.balanceOf(minterSigner.address);
	const platformBalanceBeforeMint = await usdcContract.balanceOf(PLATFORM_ADDRESS);
	const royaltyRecipientBalanceBeforeMint = await usdcContract.balanceOf(ROYALTY_RECIPIENT_ADDRESS);

	const mintTx = await erc721cMinter.mint();
	const mintReceipt = await mintTx.wait();
	console.log(`Mint transaction successful: ${mintReceipt?.hash}`);

	// Find tokenId from Transfer event
	const transferTopic = erc721cMinter.contract.getEvent('Transfer').fragment.topicHash;
	const transferLog = mintReceipt?.logs.find(
		(log: any) => log.topics[0] === transferTopic && log.topics[1] === ethers.zeroPadValue(ethers.ZeroAddress, 32)
	);
	const tokenId = transferLog ? erc721cMinter.contract.interface.parseLog(transferLog as any)?.args.tokenId : null;

	if (!tokenId) {
		console.error('Could not find minted tokenId!');
		return;
	}
	console.log(`Token ID ${tokenId} minted to ${minterSigner.address}`);

	// Check balances after mint
	const minterBalanceAfter = await usdcContract.balanceOf(minterSigner.address);
	const platformBalanceAfterMint = await usdcContract.balanceOf(PLATFORM_ADDRESS);
	const royaltyRecipientBalanceAfterMint = await usdcContract.balanceOf(ROYALTY_RECIPIENT_ADDRESS);
	console.log(`Minter USDC change: ${formatUnits(minterBalanceAfter - minterBalanceBefore, 6)}`); // Should be -100
	console.log(`Platform USDC change: +${formatUnits(platformBalanceAfterMint - platformBalanceBeforeMint, 6)}`); // Should be +5 (5% of 100)
	console.log(`Royalty Recipient USDC change: +${formatUnits(royaltyRecipientBalanceAfterMint - royaltyRecipientBalanceBeforeMint, 6)}`); // Should be +95 (95% of 100)

	// --- 4. Selling ---
	const salePrice = parseUnits('200', 6); // Sell for 200 USDC
	const erc721cBuyer = erc721c.connect(buyerSigner); // Connect wrapper to the buyer for read operations if needed

	// Seller (minter) needs to approve the contract for the token
	// Although sellToken allows approved operators, direct owner selling is common.
	// Note: In KAMI contracts, sellToken transfers from msg.sender if they are owner/approved.
	// No separate NFT approval step is strictly needed if the minter calls sellToken.

	// ** IMPORTANT: Buyer must approve the contract to spend USDC **
	console.log(`Approving ${formatUnits(salePrice, 6)} USDC for buying...`);
	const approveBuyTx = await usdcContract.connect(buyerSigner).approve(contractAddress, salePrice);
	await approveBuyTx.wait();
	console.log('USDC approved for buying.');

	console.log(`Selling token ${tokenId} from ${minterSigner.address} to ${buyerSigner.address} for ${formatUnits(salePrice, 6)} USDC...`);

	const sellerBalanceBeforeSale = await usdcContract.balanceOf(minterSigner.address);
	const buyerBalanceBeforeSale = await usdcContract.balanceOf(buyerSigner.address);
	const platformBalanceBeforeSale = await usdcContract.balanceOf(PLATFORM_ADDRESS); // = platformBalanceAfterMint
	const royaltyRecipientBalanceBeforeSale = await usdcContract.balanceOf(ROYALTY_RECIPIENT_ADDRESS); // = royaltyRecipientBalanceAfterMint

	// The seller (minter in this case) calls sellToken
	const sellTx = await erc721cMinter.sellToken(buyerSigner.address, tokenId, salePrice);
	const sellReceipt = await sellTx.wait();
	console.log(`Sell transaction successful: ${sellReceipt?.hash}`);

	// Verify ownership transfer
	const newOwner = await erc721c.ownerOf(tokenId);
	console.log(`New owner of token ${tokenId}: ${newOwner} (Expected: ${buyerSigner.address})`);

	// Check balances after sale
	const sellerBalanceAfterSale = await usdcContract.balanceOf(minterSigner.address);
	const buyerBalanceAfterSale = await usdcContract.balanceOf(buyerSigner.address);
	const platformBalanceAfterSale = await usdcContract.balanceOf(PLATFORM_ADDRESS);
	const royaltyRecipientBalanceAfterSale = await usdcContract.balanceOf(ROYALTY_RECIPIENT_ADDRESS);

	// Calculations:
	// Sale Price = 200
	// Platform Fee = 5% of 200 = 10
	// Transfer Royalty = 10% of 200 = 20
	// Seller Receives = 200 - 10 - 20 = 170

	console.log(`Buyer USDC change: ${formatUnits(buyerBalanceAfterSale - buyerBalanceBeforeSale, 6)}`); // Should be -200
	console.log(`Seller USDC change: +${formatUnits(sellerBalanceAfterSale - sellerBalanceBeforeSale, 6)}`); // Should be +170
	console.log(`Platform USDC change: +${formatUnits(platformBalanceAfterSale - platformBalanceBeforeSale, 6)}`); // Should be +10
	console.log(`Royalty Recipient USDC change: +${formatUnits(royaltyRecipientBalanceAfterSale - royaltyRecipientBalanceBeforeSale, 6)}`); // Should be +20

	console.log('--- ERC721C Lifecycle Example Complete ---');
}

// Run the example
erc721cLifecycle().catch(console.error);

API Reference

This SDK exports Factories for deployment/attachment, Wrappers for contract interaction, and associated Types.

Factories

Factories provide static methods to deploy new contracts or attach to existing ones.

  • ERC721CFactory / ERC721ACFactory / ERC1155CFactory
    • static attach(address, signerOrProvider): Wrapper: Attaches to a standard contract instance. Returns the corresponding Wrapper (ERC721CWrapper, etc.).
    • static attachUpgradeable(proxyAddress, signerOrProvider): Wrapper: Attaches to an upgradeable contract proxy. Returns the corresponding Wrapper configured with the upgradeable ABI.
    • static deploy(args, signer): Promise<Wrapper>: Deploys a new standard contract. Requires DeployArgs (e.g., ERC721CDeployArgs). Returns a Promise resolving to the Wrapper.
    • static deployUpgradeable(initArgs, signer, proxyAdminOwner?): Promise<Wrapper>: Deploys an upgradeable contract using a Transparent Proxy. Requires InitializeArgs (e.g., ERC721CInitializeArgs). Returns a Promise resolving to the Wrapper attached to the proxy.
    • static deployNewImplementation?(signer): Promise<string>: (Present on upgradeable factories) Deploys a new implementation contract for a potential upgrade. Returns the address of the new implementation. Does not perform the upgrade itself.

Wrappers

Wrappers provide a type-safe interface to interact with the deployed contract's methods. Each wrapper takes the contract address and an ethers Signer or Provider in its constructor. Use the attach or deploy methods from the Factories to get instances.

  • ERC721CWrapper / ERC721ACWrapper / ERC1155CWrapper
    • contract: ethers.Contract: The underlying ethers.Contract instance.
    • address: string: The contract address.
    • connect(signerOrProvider): Wrapper: Returns a new wrapper instance connected to a different Signer or Provider. Useful for switching between read-only and signing modes.
    • Common Methods (Examples, check specific wrapper for full list and signatures):
      • name(), symbol(), tokenURI(tokenId) / uri(tokenId)
      • balanceOf(owner) / balanceOf(owner, tokenId) (ERC1155), balanceOfBatch(owners, tokenIds) (ERC1155)
      • ownerOf(tokenId) (ERC721 variants)
      • approve(to, tokenId), getApproved(tokenId), setApprovalForAll(operator, approved), isApprovedForAll(owner, operator)
      • transferFrom(from, to, tokenId) / safeTransferFrom(...) (ERC721), safeTransferFrom(from, to, id, amount, data) (ERC1155), safeBatchTransferFrom(...) (ERC1155)
      • mint(...): (Signature varies, e.g., mint() for ERC721C, mint(quantity) for ERC721AC, mint(to, id, amount, data) for ERC1155C)
      • claim(...): (If applicable, e.g., claim(quantity) on ERC721AC)
      • burn(...): (e.g., burn(tokenId) on ERC721, burn(account, id, amount) on ERC1155)
      • royaltyInfo(tokenId/id, salePrice) (ERC2981)
      • setMintRoyalties(royalties), setTransferRoyalties(royalties), setTokenMintRoyalties(id, royalties), setTokenTransferRoyalties(id, royalties) (Signatures may vary)
      • rentToken(id, duration, price), endRental(id), extendRental(id, duration, price), getRentalDetails(id) (Where applicable)
      • setMintPrice(price), getMintPrice(), setPlatformCommission(percentage, address), etc.
      • pause(), unpause(), paused() (Pausable)
      • hasRole(role, account), grantRole(role, account), revokeRole(role, account), renounceRole(role) (AccessControl)
      • totalSupply() (ERC721Enumerable/ERC721A), nextTokenId() (ERC721A)

Types

Key types used by the SDK:

  • SignerOrProvider: ethers.Signer | ethers.Provider
  • RoyaltyData: { receiver: string; feeNumerator: BigNumberish }
  • RoyaltyInfo: { receiver: string; royaltyAmount: bigint } (Returned by royaltyInfo)
  • RentalDetails: { renter: string; rentalEndTime: bigint } (Returned by getRentalDetails)
  • ERC721CDeployArgs, ERC721ACDeployArgs, ERC1155CDeployArgs: Arguments for standard contract deployment.
  • ERC721CInitializeArgs, ERC721ACInitializeArgs, ERC1155CInitializeArgs: Arguments for upgradeable contract initialization.
  • Role Constants: DEFAULT_ADMIN_ROLE, OWNER_ROLE, PLATFORM_ROLE, RENTER_ROLE, MINTER_ROLE, PAUSER_ROLE, UPGRADER_ROLE (exported BytesLike constants).

Testing

The SDK includes comprehensive test suites for all contract types. The tests cover:

  • Deployment: Standard and upgradeable contract deployment
  • Minting: Single and batch minting operations
  • Royalties: Setting and retrieving mint and transfer royalties
  • Rentals: Token rental functionality including extension and end operations
  • Access Control: Role management and permissions
  • Pausability: Pause and unpause functionality
  • ERC Standards: Full ERC721/ERC1155 compliance testing

Test Improvements

Recent updates to the test suite include:

  • Fixed Timing Logic: Corrected endTime calculations in rental tests to match contract behavior (endTime = block.timestamp + duration)
  • Enhanced Tolerance: Increased timing tolerance to ±15 seconds for more reliable test execution
  • Ethers v6 Compatibility: Updated all wrapper and factory files to use ethers v6 types and imports
  • Type Safety: Fixed BigNumber to BigInt conversions and improved type consistency
  • Error Handling: Added proper error handling for contract operations and edge cases
  • Build Compatibility: Resolved all TypeScript compilation errors for ethers v6

Running Tests

# Run all tests
npm test

# Run specific test file
npm test test/KAMI721C.test.ts

# Run tests with coverage
npm run test:coverage

Build Status

All 73 tests passing
TypeScript compilation successful
Ethers v6 compatibility achieved

Contributing

Please refer to the CONTRIBUTING.md file for guidelines. (Create this file if needed)

License

This project is licensed under the MIT License - see the LICENSE file for details.