UNPKG

arnacon-sdk

Version:

A comprehensive SDK for deploying and managing Arnacon smart contracts across multiple networks

1,100 lines (939 loc) 51.3 kB
const { ethers } = require("ethers"); const keccak256 = require("keccak256"); const namehash = require("../utils/namehash"); class ServiceProviderManager { constructor(deploymentManager) { this.deploymentManager = deploymentManager; this.signer = deploymentManager.signer; this.factoryLoader = deploymentManager.factoryLoader; } /** * Register as a service provider */ async registerAsServiceProvider(name = undefined) { console.log(`Registering as service provider with name: ${name}`); const emptyAddress = "0x0000000000000000000000000000000000000000"; // Deploy required contracts const globalRegistrarController = await this.deploymentManager.deployIfNeeded("GlobalRegistrarController"); const signatureVerifier = await this.deploymentManager.deployIfNeeded("SignatureVerifier"); // Deploy second level interactor const secondLevelInteractor = await this.deploySecondLevelInteractor(globalRegistrarController.address, signatureVerifier.address); // Deploy Arnacon resolver const arnaconResolver = await this.deployArnaconResolver(secondLevelInteractor.address); const secondLevelInteractorResolver = await secondLevelInteractor.arnaconResolver(); console.log("secondLevelInteractor resolver: ", secondLevelInteractorResolver); // Set arnacon resolver address in interactor if(secondLevelInteractorResolver !== arnaconResolver.address){ await this.deploymentManager.executeTransaction( secondLevelInteractor, "setArnaconResolver", [arnaconResolver.address], "Setting arnacon resolver in interactor" ); } else { console.log("Arnacon resolver already set correctly"); } // Deploy and configure second level controller const secondLevelControllerAddress = (await this.deploySecondLevelController(globalRegistrarController, secondLevelInteractor)).address const secondLevelControllerABI = this.factoryLoader.getContractABI("SecondLevelController"); const secondLevelController = new ethers.Contract(secondLevelControllerAddress, secondLevelControllerABI, this.signer); const interactorInController = await secondLevelController.getSecondLevelInteractor(); console.log("interactor in controller: ", interactorInController); if (interactorInController !== secondLevelInteractor.address){ await this.deploymentManager.executeTransaction( secondLevelInteractor, "setSecondLevelController", [secondLevelController.address], "Setting second level controller in interactor" ); const secondLevelControllerABI2 = this.factoryLoader.getContractABI("SecondLevelController"); const secondLevelControllerContract = new ethers.Contract(secondLevelController.address, secondLevelControllerABI2, this.signer); await this.deploymentManager.executeTransaction( secondLevelControllerContract, "setSecondLevelInteractor", [secondLevelInteractor.address], "Setting second level interactor in controller" ); const interactor = await secondLevelControllerContract.getSecondLevelInteractor(); console.log("Second level interactor set in controller ", interactor); } if(name && (await arnaconResolver.getProductTypeRegistry(name) === emptyAddress)){ console.log(`Deploying ProductTypeRegistry for name: ${name}`); await this.deploymentManager.executeTransaction( arnaconResolver, "deployProductTypeRegistry", [name], "Deploying ProductTypeRegistry" ); // Deploy ProductsNFT console.log("Deploying ProductsNFT..."); const productsNFT = await this.deploymentManager.deployIfNeeded("ProductsNFT", arnaconResolver.address); console.log("ProductsNFT deployed at:", productsNFT.address); await this.deploymentManager.executeTransaction( arnaconResolver, "addNFTContract", [name, productsNFT.address], "Setting ProductsNFT in ArnaconResolver" ); } else { console.log("no name given so not deploying ProductTypeRegistry and ProductsNFT"); } console.log("Service provider registration completed successfully!"); return { globalRegistrarController: globalRegistrarController.address, signatureVerifier: signatureVerifier.address, secondLevelInteractor: secondLevelInteractor.address, arnaconResolver: arnaconResolver.address, secondLevelController: secondLevelController.address }; } /** * Deploy second level interactor */ async deploySecondLevelInteractor(globalRegistrarControllerAddress, signatureVerifierAddress) { const nameWrapper = await this.deploymentManager.deployIfNeeded("NameWrapper"); const provisionRegistry = await this.deploymentManager.deployIfNeeded("ProvisionRegistry", 1); const emptyAddress = "0x0000000000000000000000000000000000000000"; const publicResolver = await this.deploymentManager.deployIfNeeded("PublicResolver"); const secondLevelInteractor = await this.deploymentManager.deployIfNeeded("SecondLevelInteractor", globalRegistrarControllerAddress, nameWrapper.address, emptyAddress, publicResolver.address, signatureVerifierAddress, provisionRegistry.address ); console.log("Second level interactor deployed at:", secondLevelInteractor.address); return secondLevelInteractor; } /** * Deploy Arnacon resolver */ async deployArnaconResolver(secondLevelInteractorAddress) { const ArnaconResolver = await this.deploymentManager.deployIfNeeded("ArnaconResolver", secondLevelInteractorAddress); console.log("Arnacon resolver deployed at:", ArnaconResolver.address); return ArnaconResolver; } /** * Deploy second level controller */ async deploySecondLevelController(GlobalRegistrarControllerContract, SecondLevelInteractorContract) { // const tx2 = await GlobalRegistrarControllerContract.becomeOwner(); // await tx2.wait(); // console.log("tx: ", tx2) const node = await GlobalRegistrarControllerContract.getNodeFor(this.signer.address); console.log("node: ", node) const deployedControllerAddress = await GlobalRegistrarControllerContract.get2LDControllerFor(this.signer.address); if(deployedControllerAddress !== "0x0000000000000000000000000000000000000000"){ console.log("Second level controller already deployed at:", deployedControllerAddress); this.deploymentManager.contracts["SecondLevelController"] = deployedControllerAddress; this.deploymentManager.saveContracts(); return {new: false, address: deployedControllerAddress}; } const price = await GlobalRegistrarControllerContract.PRICE(); console.log("price: ", price) // Try to get gas settings, but use a higher fallback for contract creation let gasSettings; try { // throw new Error("test"); gasSettings = await this.deploymentManager.getGasSettings(GlobalRegistrarControllerContract, "deploy2LDController", [SecondLevelInteractorContract.address], price); console.log("Gas estimation successful:", gasSettings.gasLimit.toString()); } catch (error) { console.warn("Gas estimation failed, using fallback gas limit for contract creation:", error.message); gasSettings = { gasLimit: ethers.BigNumber.from("2000000"), // 2M gas for contract creation maxPriorityFeePerGas: ethers.BigNumber.from("50000000000"), // 50 gwei maxFeePerGas: ethers.BigNumber.from("100000000000") // 100 gwei }; } // Ensure minimum gas limit for contract creation if (gasSettings.gasLimit.lt("1500000")) { console.log("Gas limit too low for contract creation, using minimum 1.5M gas"); gasSettings.gasLimit = ethers.BigNumber.from("1500000"); } const tx = await GlobalRegistrarControllerContract.deploy2LDController(SecondLevelInteractorContract.address, { ...gasSettings, value: price }); await tx.wait(); console.log("Second level controller deployed:", tx.hash); const secondLevelControllerAddress = await GlobalRegistrarControllerContract.get2LDControllerFor(this.signer.address); console.log("Second level controller address:", secondLevelControllerAddress); this.deploymentManager.contracts["SecondLevelController"] = secondLevelControllerAddress; this.deploymentManager.saveContracts(); return {new: true, address: secondLevelControllerAddress}; } /** * Purchase a top-level domain name */ async purchaseName(name, durationInDays = 365) { console.log(`Purchasing domain name: ${name}.global for ${durationInDays} days...`); try { // Convert days to seconds for duration const duration = durationInDays * 24 * 60 * 60; // Get required contracts const globalRegistrarController = await this.deploymentManager.deployIfNeeded("GlobalRegistrarController"); const ethRegistrarController = await this.deploymentManager.deployIfNeeded("ETHRegistrarController"); const publicResolver = await this.deploymentManager.deployIfNeeded("PublicResolver"); // Get second level controller for the current signer const secondLevelController = await globalRegistrarController.get2LDControllerFor(this.signer.address); console.log("Second level controller address:", secondLevelController); if (secondLevelController === "0x0000000000000000000000000000000000000000") { throw new Error("Second level controller not found. Please register as a service provider first."); } // Generate a secret for the commitment const secret = "0x" + keccak256("secret").toString('hex'); // Prepare commitment parameters const resolver = publicResolver.address; const data = []; const reverseRecord = false; const ownerControlledFuses = 0; // Get price for the domain const price = await ethRegistrarController.rentPrice(name, duration); const totalPrice = price.base.add(price.premium); console.log(`Domain price: ${ethers.utils.formatEther(totalPrice)} ETH`); // Create commitment hash - use second level controller as owner const commitment = await ethRegistrarController.makeCommitment( name, secondLevelController, // Use second level controller as owner duration, secret, resolver, data, reverseRecord, ownerControlledFuses ); const exists = (await ethRegistrarController.commitments(commitment)).toNumber() !== 0; if(!exists){ // Send commitment transaction console.log("Sending commitment transaction..."); const gasSettings = await this.deploymentManager.getGasSettings(ethRegistrarController, "commit", [commitment]); const commitTx = await ethRegistrarController.commit(commitment, gasSettings); console.log("Commitment transaction sent:", commitTx.hash); await commitTx.wait(); console.log("Commitment confirmed!"); // Wait for the minimum required time const minCommitmentAge = await ethRegistrarController.minCommitmentAge(); const waitTimeMs = Math.max(minCommitmentAge.toNumber() * 1000, 10000); console.log(`Waiting ${waitTimeMs/1000} seconds before registration...`); await new Promise(resolve => setTimeout(resolve, waitTimeMs)); } // Register the domain - use second level controller as owner console.log("Registering domain..."); // Register the domain with the global registrar controller console.log("Registering with global registrar controller..."); const globalRegisterGasSettings = await this.deploymentManager.getGasSettings( globalRegistrarController, "register", [name, duration, secret, resolver, data, reverseRecord, ownerControlledFuses], totalPrice ); const globalRegisterTx = await globalRegistrarController.register( name, duration, secret, resolver, data, reverseRecord, ownerControlledFuses, { ...globalRegisterGasSettings, value: totalPrice } ); console.log("Global registration transaction sent:", globalRegisterTx.hash); await globalRegisterTx.wait(); console.log("Global registration successful!"); return { name: `${name}.global`, owner: secondLevelController, // Return second level controller as owner signer: this.signer.address, // Include signer address for reference duration: duration, transactionHash: globalRegisterTx.hash, price: ethers.utils.formatEther(totalPrice), secondLevelController: secondLevelController }; } catch (error) { console.error("Error purchasing domain:", error.message); throw error; } } async purchaseNameETHController(name, durationInDays = 365) { console.log(`Purchasing domain name: ${name}.global for ${durationInDays} days...`); try { // Convert days to seconds for duration const duration = durationInDays * 24 * 60 * 60; // Get required contracts const ethRegistrarController = await this.deploymentManager.deployIfNeeded("ETHRegistrarController"); const publicResolver = await this.deploymentManager.deployIfNeeded("PublicResolver"); const owner = await ethRegistrarController.address; console.log("Owner: ", owner); return // Generate a secret for the commitment const secret = "0x" + keccak256("secret").toString('hex'); // Prepare commitment parameters const resolver = publicResolver.address; const data = []; const reverseRecord = false; const ownerControlledFuses = 0; // Get price for the domain const price = await ethRegistrarController.rentPrice(name, duration); const totalPrice = price.base.add(price.premium); console.log(`Domain price: ${ethers.utils.formatEther(totalPrice)} ETH`); // Create commitment hash - use second level controller as owner const commitment = await ethRegistrarController.makeCommitment( name, this.signer.address, // Use second level controller as owner duration, secret, resolver, data, reverseRecord, ownerControlledFuses ); // Send commitment transaction console.log("Sending commitment transaction..."); const gasSettings = await this.deploymentManager.getGasSettings(ethRegistrarController, "commit", [commitment]); const commitTx = await ethRegistrarController.commit(commitment, gasSettings); console.log("Commitment transaction sent:", commitTx.hash); await commitTx.wait(); console.log("Commitment confirmed!"); // Wait for the minimum required time const minCommitmentAge = await ethRegistrarController.minCommitmentAge(); const waitTimeMs = Math.max(minCommitmentAge.toNumber() * 1000, 10000); console.log(`Waiting ${waitTimeMs/1000} seconds before registration...`); await new Promise(resolve => setTimeout(resolve, waitTimeMs)); // Register the domain - use second level controller as owner console.log("Registering domain..."); // Register the domain with the global registrar controller const ethRegisterGasSettings = await this.deploymentManager.getGasSettings( ethRegistrarController, "register", name, this.signer.address, duration, secret, resolver, data, reverseRecord, ownerControlledFuses ); const ethRegisterTx = await ethRegistrarController.register( name, this.signer.address, duration, secret, resolver, data, reverseRecord, ownerControlledFuses, { ...ethRegisterGasSettings, value: totalPrice } ); console.log("Global registration transaction sent:", ethRegisterTx.hash); await ethRegisterTx.wait(); console.log("Global registration successful!"); return { name: `${name}.global`, owner: this.signer.address, // Return second level controller as owner signer: this.signer.address, // Include signer address for reference duration: duration, transactionHash: ethRegisterTx.hash, price: ethers.utils.formatEther(totalPrice) }; } catch (error) { console.error("Error purchasing domain:", error.message); throw error; } } /** * Get second level controller address for a service provider */ async getSecondLevelController(spAddress) { const GlobalRegistrarController = await this.deploymentManager.deployIfNeeded("GlobalRegistrarController"); const secondLevelControllerAddress = await GlobalRegistrarController.get2LDControllerFor(spAddress); if (secondLevelControllerAddress === "0x0000000000000000000000000000000000000000") { throw new Error("No second level controller found for this address"); } return await this.deploymentManager.getDeployedContract("SecondLevelController", secondLevelControllerAddress); } /** * Get second level interactor for a service provider */ async getSecondLevelInteractor(spAddress) { const secondLevelController = await this.getSecondLevelController(spAddress); const secondLevelInteractorAddress = await secondLevelController.getSecondLevelInteractor(); if (secondLevelInteractorAddress === "0x0000000000000000000000000000000000000000") { throw new Error("No second level interactor found for this service provider"); } return await this.deploymentManager.getDeployedContract("SecondLevelInteractor", secondLevelInteractorAddress); } /** * Get product type registry for a service provider and name */ async getProductRegistry(spAddress, name) { try { // Get the second level interactor const secondLevelInteractor = await this.getSecondLevelInteractor(spAddress); // Get the ArnaconResolver address from the interactor const arnaconResolverAddress = await secondLevelInteractor.arnaconResolver(); if (!arnaconResolverAddress || arnaconResolverAddress === "0x0000000000000000000000000000000000000000") { throw new Error("No ArnaconResolver found for this service provider"); } // Get the ArnaconResolver contract const arnaconResolver = await this.deploymentManager.getDeployedContract("ArnaconResolver", arnaconResolverAddress); // Get the ProductTypeRegistry address from the resolver const productTypeRegistryAddress = await arnaconResolver.getProductTypeRegistry(name); if (!productTypeRegistryAddress || productTypeRegistryAddress === "0x0000000000000000000000000000000000000000") { throw new Error("No ProductTypeRegistry found in ArnaconResolver"); } // Return the ProductTypeRegistry contract return await this.deploymentManager.getDeployedContract("ProductTypeRegistry", productTypeRegistryAddress); } catch (error) { console.error("Error getting ProductTypeRegistry:", error.message); throw error; } } /** * Get product types from registry for a service provider and name */ async getProductTypes(spAddress, name) { try { console.log(`Getting product types for name: ${name} from spAddress: ${spAddress}`); // Get product type registry through the contract chain const productTypeRegistry = await this.getProductRegistry(spAddress, name); console.log("productTypeRegistry: ", productTypeRegistry.address); // Call the getProductTypes function on the contract const productTypes = await productTypeRegistry.getProductTypes(); console.log("Product types retrieved:", productTypes); return productTypes; } catch (error) { console.error("Error getting product types from registry:", error.message); throw error; } } /** * Get all products with metadata from ProductTypeRegistry */ async getAllProductTypesWithMetadata(spAddress, name) { try { console.log(`Getting all products with metadata from spAddress: ${spAddress} for name: ${name}`); // Get product type registry through the contract chain const productTypeRegistry = await this.getProductRegistry(spAddress, name); // Get list of product types for the specific name const productTypes = await productTypeRegistry.getProductTypes(); console.log("Product types found:", productTypes); if (productTypes.length === 0) { return []; } // Fetch metadata for each product type (from IPFS) const products = []; for (const productType of productTypes) { try { const metadataUri = await productTypeRegistry.getMetadata(productType); let parsedMetadata; // Check if metadata is an IPFS URI if (metadataUri.startsWith('ipfs://')) { const ipfsHash = metadataUri.replace('ipfs://', ''); const response = await this.deploymentManager.ipfs.fetchFromIPFS(metadataUri); parsedMetadata = response; } else { // Fallback to parsing as JSON string (original behavior) parsedMetadata = JSON.parse(metadataUri); } products.push({ ...parsedMetadata }); } catch (error) { console.error(`Failed to fetch metadata for ${productType}:`, error.message); // Add a fallback product entry products.push({ type: productType, name: productType, description: 'Product details unavailable', error: true }); } } console.log("Products with metadata retrieved:", products.length); return products; } catch (error) { console.error("Error getting all products with metadata:", error.message); throw error; } } /** * Get all products from a specific NFT contract */ async getProductsFromContract(nftContractAddress) { try { console.log(`Getting products from NFT contract: ${nftContractAddress}`); // Get the NFT contract instance const nftContract = await this.deploymentManager.getDeployedContract("ProductsNFT", nftContractAddress); // Get all token IDs from the contract const tokenIds = await nftContract.getTokens(); console.log(`Found ${tokenIds.length} tokens in contract ${nftContractAddress}`); // Fetch metadata for each token const products = await Promise.all(tokenIds.map(async (tokenId) => { let owner = "0x0000000000000000000000000000000000000000"; try { // Get the token URI (IPFS URL or JSON string) const tokenURI = await nftContract.tokenURI(tokenId); console.log(`Token ${tokenId} URI: ${tokenURI}`); // Get the owner of the token owner = await nftContract.ownerOf(tokenId); console.log("Owner: ", owner); // Fetch metadata from IPFS or parse JSON string let metadata; if (tokenURI.startsWith('ipfs://')) { const ipfsHash = tokenURI.replace('ipfs://', ''); const response = await this.deploymentManager.ipfs.fetchFromIPFS(tokenURI); metadata = response; } else { metadata = JSON.parse(tokenURI); } return { tokenId: tokenId.toString(), owner: owner, metadata: metadata, tokenURI: tokenURI }; } catch (error) { console.error(`Error fetching metadata for token ${tokenId}:`, error.message); return { tokenId: tokenId.toString(), owner: owner, metadata: { name: `Token ${tokenId}`, description: "Metadata unavailable", error: true }, tokenURI: "", error: error.message }; } })); console.log(`Successfully fetched ${products.length} products from contract`); return products; } catch (error) { console.error("Error getting products from contract:", error.message); throw error; } } /** * Get all products from all NFT contracts for a service provider and name */ async getAllProducts(spAddress, name) { try { console.log(`Getting all products from all NFT contracts for spAddress: ${spAddress} and name: ${name}`); // Get all NFT contracts for this name const nftContracts = await this.getNFTContracts(spAddress, name); if (!nftContracts || nftContracts.length === 0) { console.log("No NFT contracts found for this name"); return []; } // Get products from each contract const allProducts = []; for (const nftContractAddress of nftContracts) { try { const products = await this.getProductsFromContract(nftContractAddress); if (products && products.length > 0) { allProducts.push(...products); } } catch (error) { console.error(`Error getting products from contract ${nftContractAddress}:`, error.message); } } console.log(`Total products found across all contracts: ${allProducts.length}`); return allProducts; } catch (error) { console.error("Error getting all products:", error.message); throw error; } } /** * Create provision and update resolver */ async createProvisionAndUpdateResolver(spAddress, key, tokenURI, name, identifier) { try { console.log(`Creating provision and updating resolver for key: ${key}, name: ${name}, identifier: ${identifier}`); // Get the ProvisionRegistry contract instance const provisionRegistry = await this.deploymentManager.deployIfNeeded("ProvisionRegistry", 1); // Get the mint price from the contract const mintPrice = await provisionRegistry.mintPrice(); console.log("Mint price:", mintPrice.toString()); // Get the PublicResolver contract const publicResolver = await this.deploymentManager.deployIfNeeded("PublicResolver"); // Check if provision registry is already set for this name const node = namehash(`${name}.global`); const currentProvisionRegistry = await publicResolver.text(node, "provisionRegistry"); console.log("Current provision registry:", currentProvisionRegistry); let setProvisionRegistry = true; if (currentProvisionRegistry === provisionRegistry.address) { setProvisionRegistry = false; console.log("Provision registry already set, skipping set operation"); } // Upload metadata to IPFS if it's an object, otherwise use as-is let metadataUri; if (typeof tokenURI === 'object' && tokenURI !== null) { metadataUri = await this.deploymentManager.ipfs.uploadToIPFS(tokenURI); } else { console.log("Using tokenURI as-is"); metadataUri = tokenURI; } // Get the second level interactor const secondLevelInteractor = await this.getSecondLevelInteractor(spAddress); console.log("Second level interactor:", secondLevelInteractor.address); const gasSettings = await this.deploymentManager.getGasSettings(secondLevelInteractor, "createProvisionAndUpdateResolver", [key, metadataUri, name, identifier, setProvisionRegistry], mintPrice + 1); // Create the provision and update resolver const tx = await secondLevelInteractor.createProvisionAndUpdateResolver( key, metadataUri, name, identifier, setProvisionRegistry, { value: mintPrice + 1, ...gasSettings } ); await tx.wait(); console.log(`Provision created and resolver updated successfully for key: ${key}`); return { key, tokenURI: metadataUri, name, identifier, setProvisionRegistry, mintPrice: mintPrice.toString(), secondLevelInteractor: secondLevelInteractor.address }; } catch (error) { console.error("Error creating provision and updating resolver:", error.message); throw error; } } /** * Create a text record */ async createRecord(spAddress, key, value, name) { try { console.log(`Creating text record for key: ${key}, value: ${value}, name: ${name}`); // Get the second level interactor const secondLevelInteractor = await this.getSecondLevelInteractor(spAddress); // Create the text record await this.deploymentManager.executeTransaction( secondLevelInteractor, "setText", [name, key, value], "Creating text record" ); console.log(`Text record created successfully for key: ${key}`); return { key, value, name, secondLevelInteractor: secondLevelInteractor.address }; } catch (error) { console.error("Error creating text record:", error.message); throw error; } } /** * Get a text record */ async getRecord(key, name) { try { const resolver = await this.deploymentManager.deployIfNeeded("PublicResolver"); const value = await resolver.text(namehash(`${name}.global`), key); console.log(`Text record for key: ${key}, name: ${name} is: ${value}`); return value; } catch (error) { console.error("Error getting text record:", error.message); throw error; } } /** * Create a product type */ async createProductType(spAddress, name, productType, productInfo) { try { console.log(`Creating product type: ${productType} for name: ${name}`); // Get the product type registry const productTypeRegistry = await this.getProductRegistry(spAddress, name); // Update productInfo with additional fields const enhancedProductInfo = { ...productInfo, type: productType, timestamp: Math.floor(Date.now() / 1000), // Current timestamp in seconds productTypeRegistry: productTypeRegistry.address }; // Upload metadata to IPFS and get URI const metadata = await this.deploymentManager.ipfs.uploadToIPFS(enhancedProductInfo); // Create the product type await this.deploymentManager.executeTransaction( productTypeRegistry, "createProductType", [productType, metadata], "Creating product type" ); console.log(`Product type ${productType} created successfully`); return { productType, metadata, productTypeRegistry: productTypeRegistry.address }; } catch (error) { console.error("Error creating product type:", error.message); throw error; } } /** * Deploy NFT contract and add it to the resolver */ async deployNFTContract(spAddress, name) { try { console.log(`Deploying NFT contract for name: ${name}`); // Get the second level interactor const secondLevelInteractor = await this.getSecondLevelInteractor(spAddress); // Get the ArnaconResolver address from the interactor const arnaconResolverAddress = await secondLevelInteractor.arnaconResolver(); if (!arnaconResolverAddress || arnaconResolverAddress === "0x0000000000000000000000000000000000000000") { throw new Error("No ArnaconResolver found for this service provider"); } // Get the ArnaconResolver contract const arnaconResolver = await this.deploymentManager.getDeployedContract("ArnaconResolver", arnaconResolverAddress); // Check if NFT contracts already exist for this name const existingNFTContracts = await arnaconResolver.getNFTContracts(name); if (existingNFTContracts && existingNFTContracts.length > 0) { console.log(`NFT contracts already exist for ${name}:`, existingNFTContracts); return { nftContractAddress: existingNFTContracts[0], // Return the first one name: name, arnaconResolver: arnaconResolverAddress, existing: true }; } // Deploy ProductsNFT const productsNFT = await this.deploymentManager.deployIfNeeded("ProductsNFT", arnaconResolverAddress); console.log("ProductsNFT deployed at:", productsNFT.address); // Add NFT contract to the resolver await this.deploymentManager.executeTransaction( arnaconResolver, "addNFTContract", [name, productsNFT.address], "Adding NFT contract to resolver" ); console.log(`NFT contract deployed and added successfully for ${name}`); return { nftContractAddress: productsNFT.address, name: name, arnaconResolver: arnaconResolverAddress, existing: false }; } catch (error) { console.error("Error deploying NFT contract:", error.message); throw error; } } /** * Get NFT contracts for a service provider and name */ async getNFTContracts(spAddress, name) { try { // Get the second level interactor const secondLevelInteractor = await this.getSecondLevelInteractor(spAddress); // Get the ArnaconResolver address from the interactor const arnaconResolverAddress = await secondLevelInteractor.arnaconResolver(); if (!arnaconResolverAddress || arnaconResolverAddress === "0x0000000000000000000000000000000000000000") { throw new Error("No ArnaconResolver found for this service provider"); } // Get the ArnaconResolver contract const arnaconResolver = await this.deploymentManager.getDeployedContract("ArnaconResolver", arnaconResolverAddress); // Get the NFT contracts array for the specified name const nftContracts = await arnaconResolver.getNFTContracts(name); return nftContracts; } catch (error) { console.error("Error getting NFT contracts:", error.message); throw error; } } /** * Create a product (mint and attach) */ async createProduct(spAddress, label, name, productInfo, productType) { try { console.log(`Creating product for ENS: type: ${productType}`); console.log(`Product info received:`, JSON.stringify(productInfo, null, 2)); console.log(`Product type received:`, productType); console.log(`SP Address:`, spAddress); console.log(`Parameter types - ens: productInfo: ${typeof productInfo}, productType: ${typeof productType}`); // Get the second level interactor const secondLevelInteractor = await this.getSecondLevelInteractor(spAddress); // Get the ArnaconResolver address from the interactor const arnaconResolverAddress = await secondLevelInteractor.arnaconResolver(); if (!arnaconResolverAddress || arnaconResolverAddress === "0x0000000000000000000000000000000000000000") { throw new Error("No ArnaconResolver found for this service provider"); } // Get the ArnaconResolver contract const arnaconResolver = await this.deploymentManager.getDeployedContract("ArnaconResolver", arnaconResolverAddress); // Get NFT contracts for this ENS const nftContracts = await arnaconResolver.getNFTContracts(name); if (!nftContracts || nftContracts.length === 0) { throw new Error(`No NFT contracts found for yes`); } // Use the first NFT contract const nftContractAddress = nftContracts[0]; const nftContract = await this.deploymentManager.getDeployedContract("ProductsNFT", nftContractAddress); // Try to get the product type registry address if possible let enhancedProductInfo = { ...productInfo }; try { // Get the ProductTypeRegistry address from the resolver const productTypeRegistryAddress = await arnaconResolver.getProductTypeRegistry(name); if (productTypeRegistryAddress && productTypeRegistryAddress !== "0x0000000000000000000000000000000000000000") { enhancedProductInfo.productTypeRegistry = productTypeRegistryAddress; console.log("Added product type registry to metadata:", productTypeRegistryAddress); } } catch (error) { console.warn("Could not get product type registry address, creating product without it"); } // Upload metadata to IPFS const metadata = await this.deploymentManager.ipfs.uploadToIPFS(enhancedProductInfo); // Step 1: Mint the product console.log("Step 1: Minting product..."); await this.deploymentManager.executeTransaction( nftContract, "mint", [this.signer.address, metadata], "Minting product NFT" ); // Get the minted token ID const tokenIds = await nftContract.getTokens(); const tokenId = tokenIds[tokenIds.length - 1]; // Get the latest minted token console.log(`Product minted with token ID: ${tokenId}`); // Step 2: Attach the product to the resolver console.log("Step 2: Attaching product to resolver..."); const fullName = `${label}.${name}.global` const node = namehash(fullName); await this.deploymentManager.executeTransaction( arnaconResolver, "createProduct", [node, nftContractAddress, tokenId, 0], // duration = 0 for now "Attaching product to resolver" ); console.log(`Product created successfully for ${name}`); return { fullName, productType, productInfo: enhancedProductInfo, tokenId: tokenId.toString(), nftContractAddress: nftContractAddress, transactionHash: "completed" }; } catch (error) { console.error("Error creating product:", error.message); throw error; } } async burnNFT(spAddress, name, tokenId) { try { console.log(`Burning NFT for ${tokenId}`); const secondLevelInteractor = await this.getSecondLevelInteractor(spAddress); const arnaconResolverAddress = await secondLevelInteractor.arnaconResolver(); if (!arnaconResolverAddress || arnaconResolverAddress === "0x0000000000000000000000000000000000000000") { throw new Error("No ArnaconResolver found for this service provider"); } const arnaconResolver = await this.deploymentManager.getDeployedContract("ArnaconResolver", arnaconResolverAddress); const nftContracts = await arnaconResolver.getNFTContracts(name); const nftContractAddress = nftContracts[0]; const nftContract = await this.deploymentManager.getDeployedContract("ProductsNFT", nftContractAddress); await this.deploymentManager.executeTransaction(nftContract, "burn", [tokenId], "Burning NFT"); console.log(`NFT burned successfully for ${tokenId}`); return { tokenId: tokenId.toString(), transactionHash: "completed" }; } catch (error) { console.error("Error burning NFT:", error.message); throw error; } } /** * Register a subdomain */ async registerSubdomain(spAddress, owner, label, name) { try { console.log(`Registering subdomain: ${label}.${name} for owner: ${owner}`); // Get the second level interactor const secondLevelInteractor = await this.getSecondLevelInteractor(spAddress); // Register the subdomain await this.deploymentManager.executeTransaction( secondLevelInteractor, "registerSubnode", [owner, label, name], "Registering subdomain" ); console.log(`Subdomain ${label}.${name} registered successfully for owner: ${owner}`); return { owner, label, name, fullDomain: `${label}.${name}`, secondLevelInteractor: secondLevelInteractor.address }; } catch (error) { console.error("Error registering subdomain:", error.message); throw error; } } /** * Batch create products */ async batchCreateProduct(spAddress, ens, productInfos, productTypes) { try { console.log(`Batch creating ${productInfos.length} products for ENS: ${ens}`); if (productInfos.length !== productTypes.length) { throw new Error("Product infos and product types arrays must have the same length"); } // Get the second level interactor const secondLevelInteractor = await this.getSecondLevelInteractor(spAddress); // Get the ArnaconResolver address from the interactor const arnaconResolverAddress = await secondLevelInteractor.arnaconResolver(); if (!arnaconResolverAddress || arnaconResolverAddress === "0x0000000000000000000000000000000000000000") { throw new Error("No ArnaconResolver found for this service provider"); } // Get the ArnaconResolver contract const arnaconResolver = await this.deploymentManager.getDeployedContract("ArnaconResolver", arnaconResolverAddress); // Get NFT contracts for this ENS const nftContracts = await arnaconResolver.getNFTContracts(ens); if (!nftContracts || nftContracts.length === 0) { throw new Error(`No NFT contracts found for ENS: ${ens}`); } // Use the first NFT contract const nftContractAddress = nftContracts[0]; const nftContract = await this.deploymentManager.getDeployedContract("ProductsNFT", nftContractAddress); // Try to get the product type registry address if possible let enhancedProductInfos = productInfos.map(productInfo => ({ ...productInfo })); try { // Get the ProductTypeRegistry address from the resolver const productTypeRegistryAddress = await arnaconResolver.getProductTypeRegistry(ens); if (productTypeRegistryAddress && productTypeRegistryAddress !== "0x0000000000000000000000000000000000000000") { enhancedProductInfos = enhancedProductInfos.map(productInfo => ({ ...productInfo, productTypeRegistry: productTypeRegistryAddress })); console.log("Added product type registry to metadata:", productTypeRegistryAddress); } } catch (error) { console.warn("Could not get product type registry address:", error.message); } // Upload all metadata to IPFS const metadataArray = await Promise.all( enhancedProductInfos.map(productInfo => this.deploymentManager.ipfs.uploadToIPFS(productInfo) ) ); // Step 1: Batch mint the products console.log("Step 1: Batch minting products..."); await this.deploymentManager.executeTransaction( nftContract, "batchMint", [this.signer.address, metadataArray], "Batch minting product NFTs" ); // Get the minted token IDs const tokenIds = await nftContract.getTokens(); const newTokenIds = tokenIds.slice(-productInfos.length); // Get the latest min