import { Config, DDO, FreCreationParams, generateDid, DatatokenCreateParams, DispenserCreationParams, getHash, LoggerInstance, Metadata, NftCreateData, NftFactory, Service, ZERO_ADDRESS } from '@oceanprotocol/lib' import { mapTimeoutStringToSeconds } from '@utils/ddo' import { generateNftCreateData } from '@utils/nft' import { getEncryptedFiles } from '@utils/provider' import slugify from 'slugify' import Web3 from 'web3' import { algorithmContainerPresets } from './_constants' import { FormPublishData, MetadataAlgorithmContainer } from './_types' import { marketFeeAddress, publisherMarketOrderFee, publisherMarketFixedSwapFee, defaultDatatokenTemplateIndex } from '../../../app.config' import { sanitizeUrl } from '@utils/url' import { getContainerChecksum } from '@utils/docker' function getUrlFileExtension(fileUrl: string): string { const splittedFileUrl = fileUrl.split('.') return splittedFileUrl[splittedFileUrl.length - 1] } async function getAlgorithmContainerPreset( dockerImage: string ): Promise { if (dockerImage === '') return const preset = algorithmContainerPresets.find( (preset) => `${preset.image}:${preset.tag}` === dockerImage ) preset.checksum = await ( await getContainerChecksum(preset.image, preset.tag) ).checksum return preset } function dateToStringNoMS(date: Date): string { return date.toISOString().replace(/\.[0-9]{3}Z/, 'Z') } function transformTags(originalTags: string[]): string[] { const transformedTags = originalTags?.map((tag) => slugify(tag).toLowerCase()) return transformedTags } export async function transformPublishFormToDdo( values: FormPublishData, // Those 2 are only passed during actual publishing process // so we can always assume if they are not passed, we are on preview. datatokenAddress?: string, nftAddress?: string ): Promise { const { metadata, services, user } = values const { chainId, accountId } = user const { type, name, description, tags, author, termsAndConditions, dockerImage, dockerImageCustom, dockerImageCustomTag, dockerImageCustomEntrypoint, dockerImageCustomChecksum } = metadata const { access, files, links, providerUrl, timeout } = services[0] const did = nftAddress ? generateDid(nftAddress, chainId) : '0x...' const currentTime = dateToStringNoMS(new Date()) const isPreview = !datatokenAddress && !nftAddress const algorithmContainerPresets = type === 'algorithm' && dockerImage !== '' && dockerImage !== 'custom' ? await getAlgorithmContainerPreset(dockerImage) : null // Transform from files[0].url to string[] assuming only 1 file const filesTransformed = files?.length && files[0].valid && [sanitizeUrl(files[0].url)] const linksTransformed = links?.length && links[0].valid && [sanitizeUrl(links[0].url)] const newMetadata: Metadata = { created: currentTime, updated: currentTime, type, name, description, tags: transformTags(tags), author, license: 'https://market.oceanprotocol.com/terms', links: linksTransformed, additionalInformation: { termsAndConditions }, ...(type === 'algorithm' && dockerImage !== '' && { algorithm: { language: filesTransformed?.length ? getUrlFileExtension(filesTransformed[0]) : '', version: '0.1', container: { entrypoint: dockerImage === 'custom' ? dockerImageCustomEntrypoint : algorithmContainerPresets.entrypoint, image: dockerImage === 'custom' ? dockerImageCustom : algorithmContainerPresets.image, tag: dockerImage === 'custom' ? dockerImageCustomTag : algorithmContainerPresets.tag, checksum: dockerImage === 'custom' ? dockerImageCustomChecksum : algorithmContainerPresets.checksum } } }) } // this is the default format hardcoded const file = { nftAddress, datatokenAddress, files: [ { type: 'url', index: 0, url: files[0].url, method: 'GET' } ] } const filesEncrypted = !isPreview && files?.length && files[0].valid && (await getEncryptedFiles(file, providerUrl.url)) const newService: Service = { id: getHash(datatokenAddress + filesEncrypted), type: access, files: filesEncrypted || '', datatokenAddress, serviceEndpoint: providerUrl.url, timeout: mapTimeoutStringToSeconds(timeout), ...(access === 'compute' && { compute: values.services[0].computeOptions }) } const newDdo: DDO = { '@context': ['https://w3id.org/did/v1'], id: did, nftAddress, version: '4.1.0', chainId, metadata: newMetadata, services: [newService], // Only added for DDO preview, reflecting Asset response, // again, we can assume if `datatokenAddress` is not passed, // we are on preview. ...(!datatokenAddress && { datatokens: [ { name: values.services[0].dataTokenOptions.name, symbol: values.services[0].dataTokenOptions.symbol } ], nft: { ...generateNftCreateData(values?.metadata.nft, accountId) } }) } return newDdo } export async function createTokensAndPricing( values: FormPublishData, accountId: string, config: Config, nftFactory: NftFactory, web3: Web3 ) { const nftCreateData: NftCreateData = generateNftCreateData( values.metadata.nft, accountId, values.metadata.transferable ) LoggerInstance.log('[publish] Creating NFT with metadata', nftCreateData) // TODO: cap is hardcoded for now to 1000, this needs to be discussed at some point const ercParams: DatatokenCreateParams = { templateIndex: defaultDatatokenTemplateIndex, minter: accountId, paymentCollector: accountId, mpFeeAddress: marketFeeAddress, feeToken: values.pricing.baseToken.address, feeAmount: publisherMarketOrderFee, // max number cap: '115792089237316195423570985008687907853269984665640564039457', name: values.services[0].dataTokenOptions.name, symbol: values.services[0].dataTokenOptions.symbol } LoggerInstance.log('[publish] Creating datatoken with ercParams', ercParams) let erc721Address, datatokenAddress, txHash switch (values.pricing.type) { case 'fixed': { const freParams: FreCreationParams = { fixedRateAddress: config.fixedRateExchangeAddress, baseTokenAddress: values.pricing.baseToken.address, owner: accountId, marketFeeCollector: marketFeeAddress, baseTokenDecimals: values.pricing.baseToken.decimals, datatokenDecimals: 18, fixedRate: values.pricing.price.toString(), marketFee: publisherMarketFixedSwapFee, withMint: true } LoggerInstance.log( '[publish] Creating fixed pricing with freParams', freParams ) const result = await nftFactory.createNftWithDatatokenWithFixedRate( accountId, nftCreateData, ercParams, freParams ) erc721Address = result.events.NFTCreated.returnValues[0] datatokenAddress = result.events.TokenCreated.returnValues[0] txHash = result.transactionHash LoggerInstance.log('[publish] createNftErcWithFixedRate tx', txHash) break } case 'free': { // maxTokens - how many tokens cand be dispensed when someone requests . If maxTokens=2 then someone can't request 3 in one tx // maxBalance - how many dt the user has in it's wallet before the dispenser will not dispense dt // both will be just 1 for the market const dispenserParams: DispenserCreationParams = { dispenserAddress: config.dispenserAddress, maxTokens: web3.utils.toWei('1'), maxBalance: web3.utils.toWei('1'), withMint: true, allowedSwapper: ZERO_ADDRESS } LoggerInstance.log( '[publish] Creating free pricing with dispenserParams', dispenserParams ) const result = await nftFactory.createNftWithDatatokenWithDispenser( accountId, nftCreateData, ercParams, dispenserParams ) erc721Address = result.events.NFTCreated.returnValues[0] datatokenAddress = result.events.TokenCreated.returnValues[0] txHash = result.transactionHash LoggerInstance.log('[publish] createNftErcWithDispenser tx', txHash) break } } return { erc721Address, datatokenAddress, txHash } }