import { SHA256 } from 'crypto-js' import { ethers, Signer } from 'ethers' import { ConfigHelper } from '../../src/config' import { hexlify } from 'ethers/lib/utils' import { createHash } from 'crypto' import fs from 'fs' // eslint-disable-next-line import/no-named-default import { default as Addresses } from '@oceanprotocol/contracts/addresses/address.json' import { Aquarius } from '../services/Aquarius' import { NftFactory } from '../contracts/NFTFactory' import { Nft } from '../contracts/NFT' import { DatatokenCreateParams } from '../@types/Datatoken' import { NftCreateData } from '../@types/NFTFactory' import { ZERO_ADDRESS } from './Constants' import { DispenserCreationParams } from '../@types/Dispenser' import { FreCreationParams } from '../@types/FixedPrice' import { getEventFromTx } from './ContractUtils' import { ProviderInstance } from '../services/Provider' // eslint-disable-next-line import/no-named-default import ERC20Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template.sol/ERC20Template.json' // import * as hre from 'hardhat' export const DEVELOPMENT_CHAIN_ID = 8996 // template address OR templateId export function isConfidentialEVM(network: string | number): boolean { const config = new ConfigHelper().getConfig(network) return config && config.confidentialEVM } /** * Get the artifacts address from the address.json file * either from the env or from the ocean-contracts dir * @returns data or null */ export function getOceanArtifactsAdresses(): any { try { if (process.env.ADDRESS_FILE) { // eslint-disable-next-line security/detect-non-literal-fs-filename const data = fs.readFileSync(process.env.ADDRESS_FILE, 'utf8') return JSON.parse(data) } return Addresses } catch (error) { return Addresses } } /** * Get the artifacts address from the address.json file, for the given chain * either from the env or from the ocean-contracts dir, safer than above, because sometimes the network name * is mispeled, best example "optimism_sepolia" vs "optimism-sepolia" * @returns data or null */ export function getOceanArtifactsAdressesByChainId(chain: number): any { try { // eslint-disable-next-line security/detect-non-literal-fs-filename const data = getOceanArtifactsAdresses() if (data) { const networks = Object.keys(data) for (const network of networks) { if (data[network].chainId === chain) { return data[network] } } } } catch (error) { console.error(error) } return null } /** * Use this function if don't need to check if the template if active * @param chainID the chain identifier * @param template the id or the template address * @returns the index of the template from the 'ERC20Template' object */ export async function calculateTemplateIndex( chainID: number, template: string | number ): Promise { let index = -1 const artifacts = await getOceanArtifactsAdressesByChainId(chainID) if (artifacts) { const templatesAvailable = artifacts.ERC20Template if (templatesAvailable) { // template address? if (typeof template === 'string') { const templateAddresses: any[] = Object.values(templatesAvailable) index = templateAddresses.findIndex(function (item) { return item === template }) } else { const templateIndexes = Object.keys(templatesAvailable) index = templateIndexes.findIndex(function (item) { return item.indexOf(template.toString()) !== -1 }) } if (index !== -1) { index += 1 } } } // index or -1 if not found return index } /** * Use this function to accurately calculate the template index, and also checking if the template is active * @param owner the signer account * @param nftContractAddress the nft contract address, usually artifactsAddresses.ERC721Factory * @param template the template ID or template address * @returns index of the template */ export async function calculateActiveTemplateIndex( owner: Signer, nftContractAddress: string, // addresses.ERC721Factory, template: string | number ): Promise { // is an ID number? const isTemplateID = typeof template === 'number' const factoryERC721 = new NftFactory(nftContractAddress, owner) const currentTokenCount = await factoryERC721.getCurrentTokenTemplateCount() for (let i = 1; i <= currentTokenCount; i++) { const tokenTemplate = await factoryERC721.getTokenTemplate(i) const erc20Template = new ethers.Contract( tokenTemplate.templateAddress, ERC20Template.abi, owner ) // check for ID if (isTemplateID) { const id = await erc20Template.connect(owner).getId() if (tokenTemplate.isActive && id.toString() === template.toString()) { return i } } else if ( tokenTemplate.isActive && tokenTemplate.templateAddress === template.toString() ) { return i } } // if nothing is found it returns -1 return -1 } /** * * @param name asset name * @param symbol asse symbol * @param owner owner address * @param assetUrl asset url * @param templateIndex 1,2 or 4 * @param ddo ddo * @param encryptDDO encrypt or not? * @param providerUrl the provider URL * @param providerFeeToken the provider fee token * @param nftContractAddress the nft contract address * @param aquariusInstance aquarius, could be node instance url * @param dispenserAddress dispenser address * @param fixedRateAddress fixed rate exchange address * @param baseTokenAddress base token address (ocean) * @returns ddo id as string */ export async function createAsset( name: string, symbol: string, owner: Signer, assetUrl: any, template: string | number, // If string, it's template address , otherwise, it's templateId ddo: any, encryptDDO: boolean = true, // default is true providerUrl: string, providerFeeToken: string, nftContractAddress: string, // addresses.ERC721Factory, aquariusInstance: Aquarius, filesObject?: any // fixed rate // dispenserAddress?: string, // fixedRateAddress?: string, // baseTokenAddress?: string // ocean token? ): Promise { const isAddress = typeof template === 'string' const isTemplateIndex = typeof template === 'number' if (!isAddress && !isTemplateIndex) { throw new Error('Invalid template! Must be a "number" or a "string"') } const chainID = (await owner.provider.getNetwork()).chainId const config = new ConfigHelper().getConfig(parseInt(String(chainID))) // This function does not consider the fact the template could be disabled // let templateIndex = await calculateTemplateIndex(chainID, template) let templateIndex = await calculateActiveTemplateIndex( owner, nftContractAddress, template ) if (templateIndex < 1) { // for testing purposes only if (chainID === DEVELOPMENT_CHAIN_ID) { templateIndex = 1 } else throw new Error(`Invalid template index: ${templateIndex}`) } const nft = new Nft(owner, chainID) const nftFactory = new NftFactory(nftContractAddress, owner) // get nft owner const account = await owner.getAddress() // from hex to number format ddo.chainId = parseInt(chainID.toString(10)) const nftParamsAsset: NftCreateData = { name, symbol, templateIndex, tokenURI: 'aaa', transferable: true, owner: account } const datatokenParams: DatatokenCreateParams = { templateIndex, cap: '100000', feeAmount: '0', paymentCollector: account, feeToken: providerFeeToken || config.oceanTokenAddress, minter: account, mpFeeAddress: ZERO_ADDRESS } // include fileObject in the DT constructor if (config.confidentialEVM) { datatokenParams.filesObject = filesObject } let bundleNFT if (!ddo.stats?.price?.value) { bundleNFT = await nftFactory.createNftWithDatatoken(nftParamsAsset, datatokenParams) } else if (ddo.stats?.price?.value === '0') { const dispenserParams: DispenserCreationParams = { dispenserAddress: config.dispenserAddress, maxTokens: '1', maxBalance: '100000000', withMint: true, allowedSwapper: ZERO_ADDRESS } bundleNFT = await nftFactory.createNftWithDatatokenWithDispenser( nftParamsAsset, datatokenParams, dispenserParams ) } else { // fixed price const fixedPriceParams: FreCreationParams = { fixedRateAddress: config.fixedRateExchangeAddress, baseTokenAddress: config.oceanTokenAddress, owner: account, marketFeeCollector: account, baseTokenDecimals: 18, datatokenDecimals: 18, fixedRate: ddo.stats.price.value, marketFee: '0', allowedConsumer: account, withMint: true } bundleNFT = await nftFactory.createNftWithDatatokenWithFixedRate( nftParamsAsset, datatokenParams, fixedPriceParams ) } const trxReceipt = await bundleNFT.wait() // events have been emitted const nftCreatedEvent = getEventFromTx(trxReceipt, 'NFTCreated') const tokenCreatedEvent = getEventFromTx(trxReceipt, 'TokenCreated') const nftAddress = nftCreatedEvent.args.newTokenAddress const datatokenAddressAsset = tokenCreatedEvent.args.newTokenAddress // create the files encrypted string assetUrl.datatokenAddress = datatokenAddressAsset assetUrl.nftAddress = nftAddress // TODO if template 4 no need to encrypt it?? if (config.confidentialEVM) { ddo.services[0].files = await ProviderInstance.encrypt(assetUrl, chainID, providerUrl) } ddo.services[0].datatokenAddress = datatokenAddressAsset ddo.services[0].serviceEndpoint = providerUrl ddo.nftAddress = nftAddress ddo.id = 'did:op:' + SHA256(ethers.utils.getAddress(nftAddress) + chainID.toString(10)) let metadata let metadataHash let flags if (encryptDDO) { metadata = await ProviderInstance.encrypt(ddo, chainID, providerUrl) const validateResult = await aquariusInstance.validate(ddo) metadataHash = validateResult.hash flags = 2 } else { const stringDDO = JSON.stringify(ddo) const bytes = Buffer.from(stringDDO) metadata = hexlify(bytes) metadataHash = '0x' + createHash('sha256').update(metadata).digest('hex') flags = 0 } await nft.setMetadata( nftAddress, await owner.getAddress(), 0, providerUrl, '', ethers.utils.hexlify(flags), metadata, metadataHash ) return ddo.id }