diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6638f43..bf6a677 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,15 +92,15 @@ jobs: - run: docker image ls - name: Delete default runner images run: | - docker image rm node:20 - docker image rm node:20-alpine - docker image rm node:18 - docker image rm node:18-alpine - docker image rm debian:10 - docker image rm debian:11 - docker image rm ubuntu:22.04 - docker image rm ubuntu:20.04 - docker image rm moby/buildkit:latest + docker image rm -f node:20 + docker image rm -f node:20-alpine + docker image rm -f node:18 + docker image rm -f node:18-alpine + docker image rm -f debian:10 + docker image rm -f debian:11 + docker image rm -f ubuntu:22.04 + docker image rm -f ubuntu:20.04 + docker image rm -f moby/buildkit:latest rm -rf /usr/share/swift/ - name: Wait for contracts deployment and C2D cluster to be ready diff --git a/metadata/jsAlgo.json b/metadata/jsAlgo.json index 599c698..01be122 100644 --- a/metadata/jsAlgo.json +++ b/metadata/jsAlgo.json @@ -5,7 +5,7 @@ "id": "", "nftAddress": "", "version": "4.1.0", - "chainId": 137, + "chainId": null, "metadata": { "created": "2023-08-01T17:09:39Z", "updated": "2023-08-01T17:09:39Z", diff --git a/metadata/jsIPFSAlgo.json b/metadata/jsIPFSAlgo.json index 5dc46b3..901e6da 100644 --- a/metadata/jsIPFSAlgo.json +++ b/metadata/jsIPFSAlgo.json @@ -5,7 +5,7 @@ "id": "", "nftAddress": "", "version": "4.1.0", - "chainId": 8996, + "chainId": null, "metadata": { "created": "2023-08-01T17:09:39Z", "updated": "2023-08-01T17:09:39Z", diff --git a/metadata/pythonAlgo.json b/metadata/pythonAlgo.json index b9b7ff3..dc64675 100644 --- a/metadata/pythonAlgo.json +++ b/metadata/pythonAlgo.json @@ -5,7 +5,7 @@ "id": "", "nftAddress": "", "version": "4.1.0", - "chainId": 8996, + "chainId": null, "metadata": { "created": "2023-08-01T17:09:39Z", "updated": "2023-08-01T17:09:39Z", diff --git a/metadata/simpleComputeDataset.json b/metadata/simpleComputeDataset.json index 3aa0a71..19d9a3b 100644 --- a/metadata/simpleComputeDataset.json +++ b/metadata/simpleComputeDataset.json @@ -5,7 +5,7 @@ "id": "", "nftAddress": "", "version": "4.1.0", - "chainId": 8996, + "chainId": null, "metadata": { "created": "2021-12-20T14:35:20Z", "updated": "2021-12-20T14:35:20Z", diff --git a/metadata/simpleDownloadDataset.json b/metadata/simpleDownloadDataset.json index 21f78fd..1bd5a42 100644 --- a/metadata/simpleDownloadDataset.json +++ b/metadata/simpleDownloadDataset.json @@ -5,7 +5,7 @@ "id": "", "nftAddress": "", "version": "4.1.0", - "chainId": 80001, + "chainId": null, "metadata": { "created": "2021-12-20T14:35:20Z", "updated": "2021-12-20T14:35:20Z", diff --git a/metadata/simpleIPFSComputeDataset.json b/metadata/simpleIPFSComputeDataset.json index 3808a9d..bac4943 100644 --- a/metadata/simpleIPFSComputeDataset.json +++ b/metadata/simpleIPFSComputeDataset.json @@ -5,7 +5,7 @@ "id": "", "nftAddress": "", "version": "4.1.0", - "chainId": 8996, + "chainId": null, "metadata": { "created": "2021-12-20T14:35:20Z", "updated": "2021-12-20T14:35:20Z", diff --git a/package-lock.json b/package-lock.json index 66d2815..a4c5e67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "2.0.0", "license": "Apache-2.0", "dependencies": { + "@oasisprotocol/sapphire-paratime": "^1.3.2", "@oceanprotocol/contracts": "^2.0.4", "@oceanprotocol/lib": "^3.4.6", "cross-fetch": "^3.1.5", @@ -1021,7 +1022,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/@oasisprotocol/sapphire-paratime/-/sapphire-paratime-1.3.2.tgz", "integrity": "sha512-98EQ2BrT0942B0VY50PKcJ6xmUAcz71y8OBMizP6oBJIh0+ogw/z3r5z4veJitMXM4zQbh5wOFaS9eOcKWX5FA==", - "license": "Apache-2.0", "dependencies": { "@noble/hashes": "1.3.2", "@oasisprotocol/deoxysii": "0.0.5", diff --git a/package.json b/package.json index 1b0c8af..d11ba11 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "typescript-eslint": "^7.12.0" }, "dependencies": { + "@oasisprotocol/sapphire-paratime": "^1.3.2", "@oceanprotocol/contracts": "^2.0.4", "@oceanprotocol/lib": "^3.4.6", "cross-fetch": "^3.1.5", diff --git a/src/commands.ts b/src/commands.ts index 59d730d..4355b37 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -2,7 +2,7 @@ import fs from "fs"; import os from "os"; import util from "util"; import { - createAsset, + createAssetUtil, handleComputeOrder, updateAssetMetadata, downloadFile, @@ -97,17 +97,15 @@ export class Commands { const encryptDDO = args[2] === "false" ? false : true; try { // add some more checks - const urlAssetId = await createAsset( + const urlAssetId = await createAssetUtil( asset.nft.name, asset.nft.symbol, this.signer, asset.services[0].files, asset, - this.providerUrl, + this.providerUrl || this.macOsProviderUrl, this.config, this.aquarius, - 1, - this.macOsProviderUrl, encryptDDO ); console.log("Asset published. ID: " + urlAssetId); @@ -129,21 +127,26 @@ export class Commands { } const encryptDDO = args[2] === "false" ? false : true; // add some more checks - const algoDid = await createAsset( - algoAsset.nft.name, - algoAsset.nft.symbol, - this.signer, - algoAsset.services[0].files, - algoAsset, - this.providerUrl, - this.config, - this.aquarius, - 1, - this.macOsProviderUrl, - encryptDDO - ); - // add some more checks + try{ + const algoDid = await createAssetUtil( + algoAsset.nft.name, + algoAsset.nft.symbol, + this.signer, + algoAsset.services[0].files, + algoAsset, + this.providerUrl || this.macOsProviderUrl, + this.config, + this.aquarius, + encryptDDO + ); + // add some more checks console.log("Algorithm published. DID: " + algoDid); + } catch (e) { + console.error("Error when publishing dataset from file: " + args[1]); + console.error(e); + return; + } + } public async editAsset(args: string[]) { diff --git a/src/helpers.ts b/src/helpers.ts index 45286e3..20476ea 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,16 +1,12 @@ -import { SHA256 } from "crypto-js"; import { ethers, Signer } from "ethers"; import fetch from "cross-fetch"; import { promises as fs } from "fs"; -import { createHash } from "crypto"; import * as path from "path"; import * as sapphire from '@oasisprotocol/sapphire-paratime'; import { AccesslistFactory, Aquarius, - DatatokenCreateParams, Nft, - NftCreateData, NftFactory, ProviderInstance, ZERO_ADDRESS, @@ -22,16 +18,16 @@ import { DDO, orderAsset, getEventFromTx, - DispenserCreationParams, - FreCreationParams, DownloadResponse, Asset, ProviderFees, ComputeAlgorithm, LoggerInstance, - Datatoken4 + createAsset } from "@oceanprotocol/lib"; import { hexlify } from "ethers/lib/utils"; +import ERC20Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template.sol/ERC20Template.json'; + export async function downloadFile( url: string, @@ -67,7 +63,49 @@ export async function downloadFile( return { data, filename }; } -export async function createAsset( +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 + } + +export function getSignerAccordingSdk(signer: Signer, config: Config) { + return config && 'sdk' in config && config.sdk === 'oasis' + ? sapphire.wrap(signer) + : signer + } + +export async function createAssetUtil( name: string, symbol: string, owner: Signer, @@ -76,173 +114,39 @@ export async function createAsset( providerUrl: string, config: Config, aquariusInstance: Aquarius, - templateIndex: number = 1, - macOsProviderUrl?: string, encryptDDO: boolean = true, + templateIDorAddress: string | number = 1, // If string, it's template address , otherwise, it's templateId, + providerFeeToken: string = ZERO_ADDRESS, + accessListFactory?: string, + allowAccessList?: string, + denyAccessList?: string, + macOsProviderUrl?: string, + ) { + const isAddress = typeof templateIDorAddress === 'string' + const isTemplateIndex = typeof templateIDorAddress === 'number' + if (!isAddress && !isTemplateIndex) { + throw new Error('Invalid template! Must be a "number" or a "string"') + } const { chainId } = await owner.provider.getNetwork(); - const nft = new Nft(owner, chainId); - const nftFactory = new NftFactory(config.nftFactoryAddress, owner, chainId, config); - - let wrappedSigner - let allowListAddress - if(templateIndex === 4){ - // Wrap the signer for Sapphire - wrappedSigner = sapphire.wrap(owner); + const signer = getSignerAccordingSdk(owner, config); + if(config.sdk === 'oasis'){ // Create Access List Factory - const accessListFactory = new AccesslistFactory(config.accessListFactory, wrappedSigner, chainId, config); + const accessListFactoryObj = new AccesslistFactory(config.accessListFactory, signer, chainId); // Create Allow List - allowListAddress = await accessListFactory.deployAccessListContract( + await accessListFactoryObj.deployAccessListContract( 'AllowList', 'ALLOW', ['https://oceanprotocol.com/nft/'], false, await owner.getAddress(), [await owner.getAddress(), ZERO_ADDRESS] - ); - } - - ddo.chainId = parseInt(chainId.toString(10)); - const nftParamsAsset: NftCreateData = { - name, - symbol, - templateIndex, - tokenURI: "aaa", - transferable: true, - owner: await owner.getAddress(), - }; - const datatokenParams: DatatokenCreateParams = { - templateIndex, - cap: "100000", - feeAmount: "0", - paymentCollector: await owner.getAddress(), - // use ocean token as default for fees, only if we don't have another specific fee token - feeToken: ddo?.stats?.price?.tokenAddress ? ddo.stats.price.tokenAddress : config.oceanTokenAddress, - minter: await owner.getAddress(), - mpFeeAddress: ZERO_ADDRESS, - }; - - let bundleNFT; - const hasStatsPrice = ddo.stats && ddo.stats.price // are price stats defined? - const hasPriceValue = hasStatsPrice && !isNaN(ddo.stats.price.value) // avoid confusion with value '0' - // no price set - if (!hasPriceValue) { // !ddo.stats?.price?.value - bundleNFT = await nftFactory.createNftWithDatatoken( - nftParamsAsset, - datatokenParams - ); - // price is 0 - } else if (hasPriceValue && Number(ddo.stats.price.value) === 0) { // 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 { - // price is <> 0 - const fixedPriceParams: FreCreationParams = { - fixedRateAddress: config.fixedRateExchangeAddress, - baseTokenAddress: config.oceanTokenAddress, - owner: await owner.getAddress(), - marketFeeCollector: await owner.getAddress(), - baseTokenDecimals: 18, - datatokenDecimals: 18, - fixedRate: ddo.stats.price.value, // we have a fixed rate price here - marketFee: "0", - allowedConsumer: await owner.getAddress(), - 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; - ddo.services[0].files = templateIndex === 4 ? '' : await ProviderInstance.encrypt( - assetUrl, - chainId, - macOsProviderUrl || 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, - macOsProviderUrl || 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 + ) + return await createAsset(name, symbol, signer, assetUrl, templateIDorAddress, ddo, encryptDDO, providerUrl || macOsProviderUrl, providerFeeToken, aquariusInstance, accessListFactory, allowAccessList, denyAccessList); } - - await nft.setMetadata( - nftAddress, - await owner.getAddress(), - 0, - providerUrl, - "", - ethers.utils.hexlify(flags), - metadata, - metadataHash - ); - - if(templateIndex === 4){ // Use Datatoken4 for file object - const datatoken = new Datatoken4( - wrappedSigner, - ethers.utils.toUtf8Bytes(JSON.stringify(assetUrl.files)), - chainId, - config - ); - - // Set file object - await datatoken.setFileObject(datatokenAddressAsset, await wrappedSigner.getAddress()); - - // Set allow list for the datatoken - await datatoken.setAllowListContract( - datatokenAddressAsset, - allowListAddress, - await wrappedSigner.getAddress() - );} - - return ddo.id; + return await createAsset(name, symbol, signer, assetUrl, templateIDorAddress, ddo, encryptDDO, providerUrl || macOsProviderUrl, providerFeeToken, aquariusInstance); } diff --git a/src/publishAsset.ts b/src/publishAsset.ts index 921db2e..3a167a0 100644 --- a/src/publishAsset.ts +++ b/src/publishAsset.ts @@ -5,7 +5,7 @@ import { Aquarius, Asset } from '@oceanprotocol/lib'; -import { createAsset, updateAssetMetadata } from './helpers'; +import { createAssetUtil, updateAssetMetadata } from './helpers'; export interface PublishAssetParams { title: string; @@ -83,18 +83,17 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c files: [{ type: params.storageType, url: params.assetLocation, method: 'GET' }], }; - // Other networks - const did = await createAsset( - params.title, - 'OCEAN-NFT', - signer, - assetUrl, - metadata, - params.providerUrl, - config, - aquarius, - params.template || 1 - ); + // Other networks + const did = await createAssetUtil( + params.title, + 'OCEAN-NFT', + signer, + assetUrl, + metadata, + params.providerUrl, + config, + aquarius + ); console.log(`Asset successfully published with DID: ${did}`); diff --git a/test/consumeFlow.test.ts b/test/consumeFlow.test.ts index bf0c8c8..c7bf0f7 100644 --- a/test/consumeFlow.test.ts +++ b/test/consumeFlow.test.ts @@ -63,7 +63,6 @@ describe("Ocean CLI Publishing", function() { expect(stdout).to.contain("Asset published. ID:"); done() } catch (assertionError) { - console.log('assertionError', assertionError); done(assertionError); } }); @@ -71,7 +70,6 @@ describe("Ocean CLI Publishing", function() { it("should publish a compute dataset using 'npm run cli publish'", function(done) { const metadataFile = path.resolve(projectRoot, "metadata/simpleComputeDataset.json"); - // Ensure the metadata file exists if (!fs.existsSync(metadataFile)) { done(new Error("Metadata file not found: " + metadataFile));