UNPKG

@paulstinchcombe/kami721c-sdk

Version:

SDK for interacting with KAMI721C NFT contracts

396 lines (354 loc) 12.3 kB
import { Contract, ContractRunner } from 'ethers'; import KAMI721CABI from '../abis/KAMI721C/KAMI721C.json'; import { colorLog, logStyles, formatKeyValue } from '../utils/console-colors'; import { ethers } from 'ethers'; /** * Royalty data structure */ export interface RoyaltyData { receiver: string; feeNumerator: bigint; } /** * KAMI721C contract wrapper class */ export class KAMI721C { private contract: Contract; /** * Creates a new instance of the KAMI721C contract wrapper * @param runner An ethers.js ContractRunner (Provider or Signer) * @param contractAddress The address of the KAMI721C contract */ constructor(runner: ContractRunner, contractAddress: string) { // Use the ABI directly from the imported JSON this.contract = new Contract(contractAddress, KAMI721CABI.abi, runner); } /** * Connect a signer to the contract * @param signer The signer to connect * @returns A new instance of the KAMI721C contract with the connected signer */ connect(signer: ContractRunner): KAMI721C { return new KAMI721C(signer, this.contract.target as string); } /** * Get the contract address * @returns The contract address */ getAddress(): string { return this.contract.target as string; } /** * Get the contract name * @returns The name of the NFT collection */ async name(): Promise<string> { return await this.contract.name(); } /** * Get the contract symbol * @returns The symbol of the NFT collection */ async symbol(): Promise<string> { return await this.contract.symbol(); } /** * Get the total supply of tokens * @returns The total number of tokens minted */ async totalSupply(): Promise<bigint> { try { // Try to call totalSupply directly first return await this.contract.totalSupply(); } catch (error) { try { // If totalSupply is not available, estimate using token counter // In the new contract, we can check the current token ID counter const tokenIdCounter = await this.contract._tokenIdCounter(); return tokenIdCounter; } catch (error) { // If both methods fail, return 0 as no tokens have been minted yet return 0n; } } } /** * Get the token URI for a specific token ID * @param tokenId The ID of the token to query * @returns The token URI */ async tokenURI(tokenId: number | bigint): Promise<string> { return await this.contract.tokenURI(tokenId); } /** * Get the owner of a specific token * @param tokenId The ID of the token to query * @returns The address of the token owner */ async ownerOf(tokenId: number | bigint): Promise<string> { return await this.contract.ownerOf(tokenId); } /** * Get the balance of an address * @param owner The address to query * @returns The number of tokens owned by the address */ async balanceOf(owner: string): Promise<bigint> { return await this.contract.balanceOf(owner); } /** * Get the current mint price * @returns The mint price in USDC (with 6 decimals) */ async mintPrice(): Promise<bigint> { return await this.contract.mintPrice(); } /** * Set the mint price (requires OWNER_ROLE) * @param newMintPrice The new mint price in USDC (with 6 decimals) * @returns The transaction */ async setMintPrice(newMintPrice: bigint | string) { return await this.contract.setMintPrice(newMintPrice); } /** * Mint a new token (requires USDC approval) * @returns The transaction */ async mint() { return await this.contract.mint(); } /** * Set royalties for all newly minted tokens (requires OWNER_ROLE) * @param royalties Array of royalty receivers and fee numerators * @returns The transaction */ async setMintRoyalties(royalties: RoyaltyData[]) { return await this.contract.setMintRoyalties(royalties); } /** * Set royalties for a specific token's mint event (requires OWNER_ROLE) * @param tokenId The ID of the token to set royalties for * @param royalties Array of royalty receivers and fee numerators * @returns The transaction */ async setTokenMintRoyalties(tokenId: number | bigint, royalties: RoyaltyData[]) { return await this.contract.setTokenMintRoyalties(tokenId, royalties); } /** * Set global transfer royalties for all tokens (requires OWNER_ROLE) * @param royalties Array of royalty receivers and fee numerators * @returns The transaction */ async setTransferRoyalties(royalties: RoyaltyData[]) { return await this.contract.setTransferRoyalties(royalties); } /** * Set transfer royalties for a specific token (requires OWNER_ROLE) * @param tokenId The ID of the token to set royalties for * @param royalties Array of royalty receivers and fee numerators * @returns The transaction */ async setTokenTransferRoyalties(tokenId: number | bigint, royalties: RoyaltyData[]) { return await this.contract.setTokenTransferRoyalties(tokenId, royalties); } /** * Get the current royalty percentage for transfers * @returns The royalty percentage in basis points (e.g., 1000 = 10%) */ async royaltyPercentage(): Promise<number> { return await this.contract.royaltyPercentage(); } /** * Set the royalty percentage for transfers (requires OWNER_ROLE) * @param newRoyaltyPercentage New royalty percentage in basis points (e.g., 1000 = 10%) * @returns The transaction */ async setRoyaltyPercentage(newRoyaltyPercentage: number) { return await this.contract.setRoyaltyPercentage(newRoyaltyPercentage); } /** * Get the platform commission details * @returns The platform commission percentage and address */ async getPlatformCommission(): Promise<{ percentage: number; address: string }> { const percentage = await this.contract.platformCommissionPercentage(); const address = await this.contract.platformAddress(); return { percentage, address }; } /** * Set the platform commission details (requires OWNER_ROLE) * @param newPercentage New commission percentage in basis points (e.g., 500 = 5%) * @param newAddress New platform address to receive commission * @returns The transaction */ async setPlatformCommission(newPercentage: number, newAddress: string) { return await this.contract.setPlatformCommission(newPercentage, newAddress); } /** * Sell a token to another address with royalties handled automatically * @param to The buyer address * @param tokenId The token ID to sell * @param salePrice The sale price in USDC * @returns The transaction */ async sellToken(to: string, tokenId: number | bigint, salePrice: bigint | string) { try { // Verify the caller owns the token const owner = await this.ownerOf(tokenId); const signerAddress = await (this.contract.runner as ethers.Signer).getAddress(); if (owner.toLowerCase() !== signerAddress.toLowerCase()) { colorLog.error( `Error: Only the token owner can sell. Current owner: ${logStyles.address(owner)}, Caller: ${logStyles.address( signerAddress )}` ); throw new Error('Only the token owner can sell this token'); } // Verify that the contract is approved to transfer the token const isApproved = await this.contract.isApprovedForAll(signerAddress, this.getAddress()); if (!isApproved) { colorLog.warning(`Contract is not approved to transfer tokens. Please call setApprovalForAll first.`); } // Verify USDC allowance for the buyer try { const usdcAddress = await this.contract.usdcToken(); const usdcABI = [ 'function allowance(address owner, address spender) external view returns (uint256)', 'function balanceOf(address owner) external view returns (uint256)', ]; const usdc = new ethers.Contract(usdcAddress, usdcABI, this.contract.runner); const allowance = await usdc.allowance(to, this.getAddress()); if (allowance < salePrice) { colorLog.warning( `Buyer has insufficient USDC allowance. Required: ${ethers.formatUnits( salePrice, 6 )}, Current: ${ethers.formatUnits(allowance, 6)}` ); } const balance = await usdc.balanceOf(to); if (balance < salePrice) { colorLog.warning( `Buyer has insufficient USDC balance. Required: ${ethers.formatUnits(salePrice, 6)}, Current: ${ethers.formatUnits( balance, 6 )}` ); } } catch (error) { colorLog.warning(`Could not verify USDC allowance: ${error}`); } // Now call the sellToken function colorLog.info( `Calling sellToken with parameters: to=${logStyles.address(to)}, tokenId=${logStyles.value( tokenId.toString() )}, salePrice=${logStyles.value(salePrice.toString())}` ); return await this.contract.sellToken(to, tokenId, salePrice); } catch (error: any) { colorLog.error(`sellToken failed: ${error.message}`); throw error; } } /** * Get royalty information for a token sale * @param tokenId The ID of the token being sold * @param salePrice The sale price * @returns The royalty receiver address and amount */ async royaltyInfo(tokenId: number | bigint, salePrice: bigint | string): Promise<{ receiver: string; royaltyAmount: bigint }> { const [receiver, royaltyAmount] = await this.contract.royaltyInfo(tokenId, salePrice); return { receiver, royaltyAmount }; } /** * Get mint royalty receivers for a token * @param tokenId The ID of the token * @returns Array of royalty data */ async getMintRoyaltyReceivers(tokenId: number | bigint): Promise<RoyaltyData[]> { return await this.contract.getMintRoyaltyReceivers(tokenId); } /** * Get transfer royalty receivers for a token * @param tokenId The ID of the token * @returns Array of royalty data */ async getTransferRoyaltyReceivers(tokenId: number | bigint): Promise<RoyaltyData[]> { return await this.contract.getTransferRoyaltyReceivers(tokenId); } /** * Check if an address has a specific role * @param role The role to check (OWNER_ROLE, PLATFORM_ROLE, or RENTER_ROLE) * @param address The address to check * @returns True if the address has the role */ async hasRole(role: string, address: string): Promise<boolean> { return await this.contract.hasRole(role, address); } /** * Grant a role to an address (requires DEFAULT_ADMIN_ROLE) * @param role The role to grant (OWNER_ROLE, PLATFORM_ROLE, or RENTER_ROLE) * @param address The address to grant the role to * @returns The transaction */ async grantRole(role: string, address: string) { return await this.contract.grantRole(role, address); } /** * Revoke a role from an address (requires DEFAULT_ADMIN_ROLE) * @param role The role to revoke (OWNER_ROLE, PLATFORM_ROLE, or RENTER_ROLE) * @param address The address to revoke the role from * @returns The transaction */ async revokeRole(role: string, address: string) { return await this.contract.revokeRole(role, address); } /** * Get the OWNER_ROLE constant * @returns The OWNER_ROLE bytes32 value */ async OWNER_ROLE(): Promise<string> { return await this.contract.OWNER_ROLE(); } /** * Get the PLATFORM_ROLE constant * @returns The PLATFORM_ROLE bytes32 value */ async PLATFORM_ROLE(): Promise<string> { return await this.contract.PLATFORM_ROLE(); } /** * Get the RENTER_ROLE constant * @returns The RENTER_ROLE bytes32 value */ async RENTER_ROLE(): Promise<string> { return await this.contract.RENTER_ROLE(); } /** * Set the base URI for tokens (requires OWNER_ROLE) * @param baseURI The new base URI * @returns The transaction */ async setBaseURI(baseURI: string) { return await this.contract.setBaseURI(baseURI); } /** * Burn a token (requires token owner permission) * @param tokenId The ID of the token to burn * @returns The transaction */ async burn(tokenId: number | bigint) { return await this.contract.burn(tokenId); } /** * Set the security policy for the contract (requires OWNER_ROLE) * @param securityLevel The security level * @param operatorWhitelistId The operator whitelist ID * @param permittedContractReceiversAllowlistId The permitted contract receivers allowlist ID * @returns The transaction */ async setSecurityPolicy(securityLevel: number, operatorWhitelistId: number, permittedContractReceiversAllowlistId: number) { return await this.contract.setSecurityPolicy(securityLevel, operatorWhitelistId, permittedContractReceiversAllowlistId); } }