@paulstinchcombe/kami721c-sdk
Version:
SDK for interacting with KAMI721C NFT contracts
213 lines (177 loc) • 9.2 kB
text/typescript
import { ethers, Wallet, Contract, Signer, ContractRunner } from 'ethers';
import { KAMI721CFactory } from '../factories/KAMI721CFactory';
import { KAMI721C, RoyaltyData } from '../contracts/KAMI721C';
import { colorLog, logStyles, formatKeyValue } from '../utils/console-colors';
import dotenv from 'dotenv';
dotenv.config();
// === Configuration ===
const RPC_URL = process.env.RPC_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const USDC_ADDRESS = process.env.USDC_ADDRESS;
const RECEIVER_1 = process.env.ROYALTY_RECEIVER_1;
const RECEIVER_2 = process.env.ROYALTY_RECEIVER_2;
const RECEIVER_3 = process.env.ROYALTY_RECEIVER_3;
const BASE_RENTAL_PRICE = process.env.BASE_RENTAL_PRICE_PER_DAY ? BigInt(process.env.BASE_RENTAL_PRICE_PER_DAY) : 2000000n; // Default 2 USDC
// Minimal ABI for USDC interactions
const USDC_ABI = [
'function approve(address spender, uint256 amount) external returns (bool)',
'function allowance(address owner, address spender) external view returns (uint256)',
'function decimals() external view returns (uint8)',
];
// --- 1. Deploy Contract Function ---
async function deployContract(wallet: Wallet): Promise<KAMI721C> {
colorLog.section('1. Deploying Contract');
if (!USDC_ADDRESS) throw new Error('USDC_ADDRESS environment variable is required.');
const factory = new KAMI721CFactory(wallet);
const platformAddress = process.env.PLATFORM_ADDRESS || wallet.address;
const platformCommission = process.env.PLATFORM_COMMISSION_PERCENTAGE ? parseInt(process.env.PLATFORM_COMMISSION_PERCENTAGE) : 500; // 5%
const initialMintPrice = process.env.MINT_PRICE ? BigInt(process.env.MINT_PRICE) : 1000000n; // 1 USDC
const name = process.env.CONTRACT_NAME || 'KAMI Setup Example';
const symbol = process.env.CONTRACT_SYMBOL || 'KSE';
const baseURI = process.env.BASE_URI || 'https://api.example.com/setup/';
console.log('Deploying upgradeable contract via proxy with parameters:');
console.log(formatKeyValue('USDC Address', USDC_ADDRESS));
console.log(formatKeyValue('Name', name));
console.log(formatKeyValue('Symbol', symbol));
console.log(formatKeyValue('Base URI', baseURI));
console.log(formatKeyValue('Initial Mint Price', `${ethers.formatUnits(initialMintPrice, 6)} USDC`));
console.log(formatKeyValue('Platform Address', platformAddress));
console.log(formatKeyValue('Platform Commission', `${platformCommission / 100}%`));
console.log(formatKeyValue('Initial Owner', wallet.address));
const nftContract = await factory.deployUpgradeable(
USDC_ADDRESS,
name,
symbol,
baseURI,
initialMintPrice,
platformAddress,
platformCommission,
wallet.address
);
const proxyAddress = nftContract.getAddress();
colorLog.success(`Contract deployed successfully at proxy address: ${logStyles.address(proxyAddress)}`);
return nftContract;
}
// --- 2. Set Royalties Function ---
async function setRoyalties(nftContract: KAMI721C, receivers: string[]): Promise<void> {
colorLog.section('2. Setting Royalties');
if (receivers.length !== 3) {
throw new Error('This function expects exactly 3 receiver addresses.');
}
// Assuming equal split (3333 basis points each, summing to 9999)
const royaltyData: RoyaltyData[] = [
{ receiver: receivers[0], feeNumerator: 3333n },
{ receiver: receivers[1], feeNumerator: 3333n },
{ receiver: receivers[2], feeNumerator: 3333n },
];
const transferRoyaltyPercentage = 1000; // 10% for transfers
console.log(`Setting mint royalties for 3 receivers: ${receivers.join(', ')}`);
const mintTx = await nftContract.setMintRoyalties(royaltyData);
console.log('Mint royalties transaction sent:', mintTx.hash);
await mintTx.wait();
colorLog.success('Mint royalties set successfully.');
console.log(`Setting transfer royalties for 3 receivers: ${receivers.join(', ')}`);
const transferTx = await nftContract.setTransferRoyalties(royaltyData);
console.log('Transfer royalties transaction sent:', transferTx.hash);
await transferTx.wait();
colorLog.success('Transfer royalties set successfully.');
console.log(`Setting transfer royalty percentage to ${transferRoyaltyPercentage / 100}%...`);
const percentageTx = await nftContract.setRoyaltyPercentage(transferRoyaltyPercentage);
console.log('Royalty percentage transaction sent:', percentageTx.hash);
await percentageTx.wait();
colorLog.success('Transfer royalty percentage set successfully.');
}
// --- 3. Mint Token Function ---
async function mintToken(nftContract: KAMI721C, wallet: Wallet): Promise<bigint> {
colorLog.section('3. Minting Token');
if (!USDC_ADDRESS) throw new Error('USDC_ADDRESS needed for minting.');
const mintPrice = await nftContract.mintPrice();
console.log(`Mint price: ${ethers.formatUnits(mintPrice, 6)} USDC`);
const usdcContract = new Contract(USDC_ADDRESS, USDC_ABI, wallet);
const decimals = await usdcContract.decimals();
// Check allowance
const allowance = await usdcContract.allowance(wallet.address, nftContract.getAddress());
console.log(`Current USDC allowance: ${ethers.formatUnits(allowance, decimals)} USDC`);
if (allowance < mintPrice) {
console.log('Approving USDC spend...');
const approveTx = await usdcContract.approve(nftContract.getAddress(), mintPrice);
console.log('Approval transaction sent:', approveTx.hash);
await approveTx.wait();
colorLog.success('USDC Approved');
} else {
console.log('Sufficient USDC allowance already set.');
}
// Mint
console.log('Sending mint transaction...');
const mintTx = await nftContract.mint();
console.log('Mint transaction sent:', mintTx.hash);
const receipt = await mintTx.wait();
colorLog.success(`Token minted successfully in block ${receipt?.blockNumber}`);
// Determine token ID (simplistic approach)
const totalSupply = await nftContract.totalSupply();
const tokenId = totalSupply > 0n ? totalSupply - 1n : 0n;
console.log(`Minted token ID (estimated): ${tokenId}`);
return tokenId;
}
// --- 4. Prepare for Sale Function ---
async function prepareForSale(nftContract: KAMI721C, tokenId: bigint): Promise<void> {
colorLog.section('4. Preparing Token for Sale');
console.log(`Approving contract ${nftContract.getAddress()} to manage token ID ${tokenId}...`);
// The owner calls approve, allowing the contract to transfer the token during sellToken
const approveTx = await nftContract.approve(nftContract.getAddress(), tokenId);
console.log('Approval transaction sent:', approveTx.hash);
await approveTx.wait();
colorLog.success(`Token ${tokenId} approved for sale via contract.`);
console.log('(Note: Actual sale price is set when calling sellToken)');
}
// --- 5. Log Rental Parameters Function ---
async function logRentalParameters(nftContract: KAMI721C, basePricePerDay: bigint): Promise<void> {
colorLog.section('5. Logging Rental Parameters');
const platformCommPercentage = await nftContract.getPlatformCommissionPercentage();
const commissionAmount = (basePricePerDay * platformCommPercentage) / 10000n;
const totalRentalPricePerDay = basePricePerDay + commissionAmount;
console.log('Example Rental Parameters (for 1 day):');
console.log(formatKeyValue('Base Rental Price', `${ethers.formatUnits(basePricePerDay, 6)} USDC`));
console.log(formatKeyValue('Platform Commission', `${Number(platformCommPercentage) / 100}%`));
console.log(formatKeyValue('Commission Amount', `${ethers.formatUnits(commissionAmount, 6)} USDC`));
console.log(formatKeyValue('Total Price (Renter Pays)', `${ethers.formatUnits(totalRentalPricePerDay, 6)} USDC`));
console.log('(Note: Actual rental is initiated by calling rentToken with duration and total price)');
}
// --- Main Orchestration Function ---
async function main() {
console.log('Starting KAMI721C Deployment and Setup Script...');
if (!RPC_URL || !PRIVATE_KEY) {
throw new Error('RPC_URL and PRIVATE_KEY environment variables are required.');
}
if (!RECEIVER_1 || !RECEIVER_2 || !RECEIVER_3) {
throw new Error('ROYALTY_RECEIVER_1, ROYALTY_RECEIVER_2, and ROYALTY_RECEIVER_3 environment variables are required.');
}
const provider = new ethers.JsonRpcProvider(RPC_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
colorLog.info(`Using wallet: ${logStyles.address(wallet.address)} on network ${(await provider.getNetwork()).name}`);
try {
// 1. Deploy
const nftContract = await deployContract(wallet);
// Wait a moment for chain/indexer
await new Promise((resolve) => setTimeout(resolve, 3000));
// 2. Set Royalties
const royaltyReceivers = [RECEIVER_1, RECEIVER_2, RECEIVER_3];
await setRoyalties(nftContract, royaltyReceivers);
// 3. Mint Token
const mintedTokenId = await mintToken(nftContract, wallet);
// 4. Prepare for Sale
await prepareForSale(nftContract, mintedTokenId);
// 5. Log Rental Parameters
await logRentalParameters(nftContract, BASE_RENTAL_PRICE);
colorLog.section('Setup Complete!');
console.log(`Contract Address: ${nftContract.getAddress()}`);
console.log(`Minted Token ID: ${mintedTokenId}`);
} catch (error: any) {
colorLog.error('Script failed:');
console.error(error.message);
if (error.reason) console.error('Reason:', error.reason);
if (error.data) console.error('Data:', error.data);
process.exit(1);
}
}
main();