kamiweb3-sdk
Version:
TypeScript SDK for KAMI721-C, KAMI721-AC, and KAMI1155-C smart contracts
648 lines (514 loc) • 30.6 kB
Markdown
[](https://badge.fury.io/js/kamiweb3-sdk)
[](https://github.com/KAMI-Github/kamiweb3-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
```bash
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.
```typescript
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
```
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.
---
```typescript
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);
```
```typescript
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);
```
```typescript
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());
```
```typescript
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);
```
---
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)
```typescript
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);
```
---
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)
```typescript
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);
```
---
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.
```typescript
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);
```
---
This SDK exports Factories for deployment/attachment, Wrappers for contract interaction, and associated Types.
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).
---
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
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
```bash
# 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.