kamiweb3-sdk
v0.1.2
Published
TypeScript SDK for KAMI721-C, KAMI721-AC, and KAMI1155-C smart contracts
Downloads
8
Maintainers
Readme
KAMI Web3 SDK
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@^6Note: 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 deployerUsage 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/ERC1155CFactorystatic 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. RequiresDeployArgs(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. RequiresInitializeArgs(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/ERC1155CWrappercontract: ethers.Contract: The underlyingethers.Contractinstance.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.ProviderRoyaltyData:{ receiver: string; feeNumerator: BigNumberish }RoyaltyInfo:{ receiver: string; royaltyAmount: bigint }(Returned byroyaltyInfo)RentalDetails:{ renter: string; rentalEndTime: bigint }(Returned bygetRentalDetails)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(exportedBytesLikeconstants).
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
endTimecalculations 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:coverageBuild 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.
