From 91d0d0f8ab0035ecec211ad4321e5720c553fc70 Mon Sep 17 00:00:00 2001 From: paulo-ocean Date: Thu, 4 Jul 2024 11:05:33 +0100 Subject: [PATCH 01/31] add check for computeoutput, ips --- src/commands.ts | 11 ++++++++- src/helpers.ts | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/commands.ts b/src/commands.ts index c094982..65e8697 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -7,12 +7,14 @@ import { updateAssetMetadata, downloadFile, isOrderable, + getMetadataURI, } from "./helpers"; import { Aquarius, Asset, ComputeAlgorithm, ComputeJob, + ComputeOutput, Config, ConfigHelper, Datatoken, @@ -225,6 +227,7 @@ export class Commands { } public async computeStart(args: string[]) { + const inputDatasetsString = args[1]; let inputDatasets = []; @@ -396,6 +399,11 @@ export class Commands { " with additional datasets:" + (!additionalDatasets ? "none" : additionalDatasets[0].documentId) ); + + const output: ComputeOutput = { + metadataUri: await getMetadataURI() + } + const computeJobs = await ProviderInstance.computeStart( providerURI, this.signer, @@ -403,7 +411,8 @@ export class Commands { assets[0], algo, null, - additionalDatasets + additionalDatasets, + output ); if (computeJobs && computeJobs[0]) { const { jobId, agreementId } = computeJobs[0]; diff --git a/src/helpers.ts b/src/helpers.ts index 9af8459..f4ab7dd 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -326,3 +326,67 @@ export async function isOrderable( } return true; } + + +// The ranges and the amount of usable IP's: + +// 10.0.0.0 - 10.255.255.255 Addresses: 16,777,216 +// 172.16.0.0 - 172.31.255.255 Addresses: 1,048,576 +// 192.168.0.0 - 192.168.255.255 Addresses: 65,536 + +// check if IP is private or not +export function isPrivateIP(ip): boolean { + + const reg = /^(127\.[\d.]+|[0:]+1|localhost)$/ + const result = ip.match(reg) + if(result!==null) { + // is loopback address + return true + } + const parts = ip.split('.'); + return parts[0] === '10' || + (parts[0] === '172' && (parseInt(parts[1], 10) >= 16 && parseInt(parts[1], 10) <= 31)) || + (parts[0] === '192' && parts[1] === '168'); + } + + // get public IP address using free service API + export async function getPublicIP(): Promise { + + try { + const response = await fetch('https://api.ipify.org?format=json') + const data = await response.json() + if(data) { + return data.ip + } + }catch(err) { + console.error('Erro getting public IP: ',err.message) + } + + return null + } + + export async function getMetadataURI() { + const metadataURI = process.env.AQUARIUS_URL + const parsed = new URL(metadataURI); + let ip = metadataURI // by default + // has port number? + const hasPort = parsed.port && !isNaN( Number(parsed.port)) + if(hasPort) { + // remove the port, just get the host part + ip = parsed.hostname + } + // check if is private or loopback + if(isPrivateIP(ip)) { + // get public V4 ip address + ip = await getPublicIP() + if(!ip) { + return metadataURI + } + } + // if we removed the port add it back + if(hasPort) { + ip = `http://${ip}:${parsed.port}` + } + return ip + } + From db93568cd2e1672267e6037e73bbdedbd70f2c80 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Wed, 4 Sep 2024 15:15:03 +0300 Subject: [PATCH 02/31] Setting up cli interactive flow --- package-lock.json | 32 ++++++++--- package.json | 1 + src/interactiveFlow.ts | 124 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 src/interactiveFlow.ts diff --git a/package-lock.json b/package-lock.json index 552c482..3c91eab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "cross-fetch": "^3.1.5", "crypto-js": "^4.1.1", "decimal.js": "^10.4.1", + "enquirer": "^2.4.1", "ethers": "^5.7.2", "ts-node": "^10.9.1" }, @@ -1564,7 +1565,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, "engines": { "node": ">=6" } @@ -1573,7 +1573,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2851,6 +2850,18 @@ "once": "^1.4.0" } }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -7561,7 +7572,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -10184,14 +10194,12 @@ "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -11182,6 +11190,15 @@ "once": "^1.4.0" } }, + "enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "requires": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + } + }, "es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -14691,7 +14708,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } diff --git a/package.json b/package.json index bf51c9d..0058e62 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "cross-fetch": "^3.1.5", "crypto-js": "^4.1.1", "decimal.js": "^10.4.1", + "enquirer": "^2.4.1", "ethers": "^5.7.2", "ts-node": "^10.9.1" } diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts new file mode 100644 index 0000000..81c4c43 --- /dev/null +++ b/src/interactiveFlow.ts @@ -0,0 +1,124 @@ +import { prompt } from 'enquirer'; + +interface Answers { + title: string; + description: string; + author: string; + tags: string; + accessDuration: string; + storageType: 'IPFS' | 'Arweave' | 'URL'; + assetLocation: string; + isCharged: boolean; + token?: string; + price?: string; + network: 'Oasis Sapphire' | 'Ethereum' | 'Polygon'; + template?: string; + showAdvanced: boolean; + customParameter?: string; +} + +async function askQuestions() { + try { + // Prompting for basic information + const basicAnswers = await prompt([ + { + type: 'input', + name: 'title', + message: 'What is the title of your asset?', + }, + { + type: 'input', + name: 'description', + message: 'Please provide a description of your asset:', + }, + { + type: 'input', + name: 'author', + message: 'Who is the Author of this asset?', + }, + { + type: 'input', + name: 'tags', + message: 'Please provide tags to make this asset more easily discoverable (comma separated):', + }, + { + type: 'input', + name: 'accessDuration', + message: 'After purchasing your asset, how long should the consumer be allowed to access it for?', + }, + ]); + + // Prompting for technical details + const technicalAnswers = await prompt([ + { + type: 'select', + name: 'storageType', + message: 'How is your asset stored?', + choices: ['IPFS', 'Arweave', 'URL'], + }, + { + type: 'input', + name: 'assetLocation', + message: 'Please provide the URL / hash / txID for your asset:', + }, + { + type: 'confirm', + name: 'isCharged', + message: 'Will you charge for this asset?', + initial: false, + }, + { + type: 'input', + name: 'token', + message: 'What token will you accept payments in?', + skip: (answers: Answers) => !answers.isCharged, + }, + { + type: 'input', + name: 'price', + message: 'What is the price to access your asset?', + skip: (answers: Answers) => !answers.isCharged, + }, + { + type: 'select', + name: 'network', + message: 'What network will your asset be available for purchase through?', + choices: ['Oasis Sapphire', 'Ethereum', 'Polygon'], + initial: 0, + }, + { + type: 'select', + name: 'template', + message: 'Which template would you like to use?', + choices: ['Template A', 'Template B', 'Template C'], + skip: (answers: Answers) => answers.network === 'Oasis Sapphire', + }, + ]); + + // Prompting for advanced options + const advancedAnswers = await prompt([ + { + type: 'confirm', + name: 'showAdvanced', + message: 'Would you like to consider all of the advanced options for your asset?', + initial: false, + }, + { + type: 'input', + name: 'customParameter', + message: 'Please provide any user-defined parameters:', + skip: (answers: Answers) => !answers.showAdvanced, + }, + ]); + + // Combine all answers + const allAnswers = { ...basicAnswers, ...technicalAnswers, ...advancedAnswers }; + + console.log('\nHere are your responses:'); + console.log(allAnswers); + } catch (error) { + console.error('An error occurred during the prompt:', error); + } +} + +askQuestions(); From b2d7d367f3177bb45513496cb0e60c1c9c7695dc Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Wed, 4 Sep 2024 15:43:44 +0300 Subject: [PATCH 03/31] Adding start command to start the interactive publish flow --- src/commands.ts | 8 ++++++++ src/interactiveFlow.ts | 4 +--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index c4f8818..8effce0 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -23,6 +23,7 @@ import { sendTx, } from "@oceanprotocol/lib"; import { Signer, ethers } from "ethers"; +import { interactiveFlow } from "./interactiveFlow"; export class Commands { public signer: Signer; @@ -60,6 +61,13 @@ export class Commands { process.env.AQUARIUS_URL || this.config.metadataCacheUri ); } + + // start the interactive publish flow + public async start() { + console.log("Starting the CLI flow..."); + await interactiveFlow(); // Call the CLI logic + } + // utils public async sleep(ms: number) { return new Promise((resolve) => { diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index 81c4c43..94f1e06 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -17,7 +17,7 @@ interface Answers { customParameter?: string; } -async function askQuestions() { +export async function interactiveFlow() { try { // Prompting for basic information const basicAnswers = await prompt([ @@ -120,5 +120,3 @@ async function askQuestions() { console.error('An error occurred during the prompt:', error); } } - -askQuestions(); From a14f11e33b10dd6955a6ce0600813cda6362d4e0 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Wed, 4 Sep 2024 15:52:38 +0300 Subject: [PATCH 04/31] Adding start command to the list of commands --- src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.ts b/src/index.ts index 90ee3e4..c3ff20d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -72,6 +72,9 @@ async function start() { const commands = new Commands(signer, chainId); const myArgs = process.argv.slice(2); switch (myArgs[0]) { + case "start": + await commands.start() + break case "getDDO": await commands.getDDO(myArgs); break; From ec2b737d3635d20fbdf8d6ce1e453e6d7775c4ae Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Thu, 5 Sep 2024 13:51:56 +0300 Subject: [PATCH 05/31] Updating the input types --- src/commands.ts | 2 +- src/interactiveFlow.ts | 35 ++++++++++++++++++----------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 8effce0..785967e 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -64,7 +64,7 @@ export class Commands { // start the interactive publish flow public async start() { - console.log("Starting the CLI flow..."); + console.log("Starting the CLI flow...\n\n"); await interactiveFlow(); // Call the CLI logic } diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index 94f1e06..6d5bb21 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -5,7 +5,7 @@ interface Answers { description: string; author: string; tags: string; - accessDuration: string; + accessDuration: 'Forever' | '1 day' | '1 week' | '1 month' | '1 year'; storageType: 'IPFS' | 'Arweave' | 'URL'; assetLocation: string; isCharged: boolean; @@ -24,27 +24,28 @@ export async function interactiveFlow() { { type: 'input', name: 'title', - message: 'What is the title of your asset?', + message: 'What is the title of your asset?\n', }, { type: 'input', name: 'description', - message: 'Please provide a description of your asset:', + message: 'Please provide a description of your asset:\n', }, { type: 'input', name: 'author', - message: 'Who is the Author of this asset?', + message: 'Who is the Author of this asset?\n', }, { - type: 'input', + type: 'list', name: 'tags', - message: 'Please provide tags to make this asset more easily discoverable (comma separated):', + message: 'Please provide tags to make this asset more easily discoverable (comma separated):\n', }, { - type: 'input', + type: 'select', name: 'accessDuration', - message: 'After purchasing your asset, how long should the consumer be allowed to access it for?', + message: 'After purchasing your asset, how long should the consumer be allowed to access it for?\n', + choices: ['Forever', '1 day', '1 week', '1 month', '1 year'], }, ]); @@ -53,43 +54,43 @@ export async function interactiveFlow() { { type: 'select', name: 'storageType', - message: 'How is your asset stored?', + message: 'How is your asset stored?\n', choices: ['IPFS', 'Arweave', 'URL'], }, { type: 'input', name: 'assetLocation', - message: 'Please provide the URL / hash / txID for your asset:', + message: 'Please provide the URL / hash / txID for your asset:\n', }, { type: 'confirm', name: 'isCharged', - message: 'Will you charge for this asset?', + message: 'Will you charge for this asset?\n', initial: false, }, { type: 'input', name: 'token', - message: 'What token will you accept payments in?', + message: 'What token will you accept payments in?\n', skip: (answers: Answers) => !answers.isCharged, }, { type: 'input', name: 'price', - message: 'What is the price to access your asset?', + message: 'What is the price to access your asset?\n', skip: (answers: Answers) => !answers.isCharged, }, { type: 'select', name: 'network', - message: 'What network will your asset be available for purchase through?', + message: 'What network will your asset be available for purchase through?\n', choices: ['Oasis Sapphire', 'Ethereum', 'Polygon'], initial: 0, }, { type: 'select', name: 'template', - message: 'Which template would you like to use?', + message: 'Which template would you like to use?\n', choices: ['Template A', 'Template B', 'Template C'], skip: (answers: Answers) => answers.network === 'Oasis Sapphire', }, @@ -100,13 +101,13 @@ export async function interactiveFlow() { { type: 'confirm', name: 'showAdvanced', - message: 'Would you like to consider all of the advanced options for your asset?', + message: 'Would you like to consider all of the advanced options for your asset?\n', initial: false, }, { type: 'input', name: 'customParameter', - message: 'Please provide any user-defined parameters:', + message: 'Please provide any user-defined parameters:\n', skip: (answers: Answers) => !answers.showAdvanced, }, ]); From de5f1f4cd31c04e51f1b29d8fd1cafa28ebee5d7 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Thu, 5 Sep 2024 14:29:33 +0300 Subject: [PATCH 06/31] adding conditional logic for storage location question --- src/interactiveFlow.ts | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index 6d5bb21..eab6770 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -20,7 +20,7 @@ interface Answers { export async function interactiveFlow() { try { // Prompting for basic information - const basicAnswers = await prompt([ + const basicAnswers = await prompt>([ { type: 'input', name: 'title', @@ -49,19 +49,37 @@ export async function interactiveFlow() { }, ]); - // Prompting for technical details - const technicalAnswers = await prompt([ + // Prompting for technical details - first, get storage type + const { storageType } = await prompt<{ storageType: Answers['storageType'] }>([ { type: 'select', name: 'storageType', message: 'How is your asset stored?\n', choices: ['IPFS', 'Arweave', 'URL'], }, + ]); + + // Determine assetLocation message based on storageType + let assetLocationMessage = 'Please provide the location of your asset:\n'; + if (storageType === 'IPFS') { + assetLocationMessage = 'Please provide the IPFS hash for your asset:\n'; + } else if (storageType === 'Arweave') { + assetLocationMessage = 'Please provide the Arweave transaction ID for your asset:\n'; + } else if (storageType === 'URL') { + assetLocationMessage = 'Please provide the URL for your asset:\n'; + } + + // Prompt for asset location + const { assetLocation } = await prompt<{ assetLocation: string }>([ { type: 'input', name: 'assetLocation', - message: 'Please provide the URL / hash / txID for your asset:\n', + message: assetLocationMessage, }, + ]); + + // Continue with further technical details + const technicalAnswers = await prompt>([ { type: 'confirm', name: 'isCharged', @@ -72,13 +90,13 @@ export async function interactiveFlow() { type: 'input', name: 'token', message: 'What token will you accept payments in?\n', - skip: (answers: Answers) => !answers.isCharged, + skip: (answers: Partial) => !answers.isCharged, }, { type: 'input', name: 'price', message: 'What is the price to access your asset?\n', - skip: (answers: Answers) => !answers.isCharged, + skip: (answers: Partial) => !answers.isCharged, }, { type: 'select', @@ -92,12 +110,12 @@ export async function interactiveFlow() { name: 'template', message: 'Which template would you like to use?\n', choices: ['Template A', 'Template B', 'Template C'], - skip: (answers: Answers) => answers.network === 'Oasis Sapphire', + skip: (answers: Partial) => answers.network === 'Oasis Sapphire', }, ]); // Prompting for advanced options - const advancedAnswers = await prompt([ + const advancedAnswers = await prompt>([ { type: 'confirm', name: 'showAdvanced', @@ -108,12 +126,12 @@ export async function interactiveFlow() { type: 'input', name: 'customParameter', message: 'Please provide any user-defined parameters:\n', - skip: (answers: Answers) => !answers.showAdvanced, + skip: (answers: Partial) => !answers.showAdvanced, }, ]); // Combine all answers - const allAnswers = { ...basicAnswers, ...technicalAnswers, ...advancedAnswers }; + const allAnswers = { ...basicAnswers, storageType, assetLocation, ...technicalAnswers, ...advancedAnswers }; console.log('\nHere are your responses:'); console.log(allAnswers); From 9ac7147949ab0d4e8426b4298e55d037acb40bb1 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Thu, 5 Sep 2024 14:35:43 +0300 Subject: [PATCH 07/31] Adding togle question for Paid / free --- src/interactiveFlow.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index eab6770..8025c97 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -8,7 +8,7 @@ interface Answers { accessDuration: 'Forever' | '1 day' | '1 week' | '1 month' | '1 year'; storageType: 'IPFS' | 'Arweave' | 'URL'; assetLocation: string; - isCharged: boolean; + isCharged: 'Paid' | 'Free'; token?: string; price?: string; network: 'Oasis Sapphire' | 'Ethereum' | 'Polygon'; @@ -81,22 +81,24 @@ export async function interactiveFlow() { // Continue with further technical details const technicalAnswers = await prompt>([ { - type: 'confirm', + type: 'toggle', name: 'isCharged', message: 'Will you charge for this asset?\n', - initial: false, + initial: 'Paid', + enabled: 'Paid', + disabled: 'Free', }, { type: 'input', name: 'token', message: 'What token will you accept payments in?\n', - skip: (answers: Partial) => !answers.isCharged, + skip: (answers: Partial) => answers.isCharged === 'Free', }, { type: 'input', name: 'price', message: 'What is the price to access your asset?\n', - skip: (answers: Partial) => !answers.isCharged, + skip: (answers: Partial) => answers.isCharged === 'Free', }, { type: 'select', From aa9dbe18b6b101e11d099e5377d95f0af347741e Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Thu, 5 Sep 2024 14:40:14 +0300 Subject: [PATCH 08/31] Adding conditional logic for price and token questions --- src/interactiveFlow.ts | 45 ++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index 8025c97..de10256 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -78,8 +78,8 @@ export async function interactiveFlow() { }, ]); - // Continue with further technical details - const technicalAnswers = await prompt>([ + // Prompt for whether the asset is charged or free + const { isCharged } = await prompt<{ isCharged: Answers['isCharged'] }>([ { type: 'toggle', name: 'isCharged', @@ -88,18 +88,25 @@ export async function interactiveFlow() { enabled: 'Paid', disabled: 'Free', }, - { - type: 'input', - name: 'token', - message: 'What token will you accept payments in?\n', - skip: (answers: Partial) => answers.isCharged === 'Free', - }, - { - type: 'input', - name: 'price', - message: 'What is the price to access your asset?\n', - skip: (answers: Partial) => answers.isCharged === 'Free', - }, + ]); + + // Continue with further technical details, conditional prompts based on isCharged + const paymentDetails = isCharged === 'Paid' + ? await prompt>([ + { + type: 'input', + name: 'token', + message: 'What token will you accept payments in?\n', + }, + { + type: 'input', + name: 'price', + message: 'What is the price to access your asset?\n', + }, + ]) + : {}; + + const technicalAnswers = await prompt>([ { type: 'select', name: 'network', @@ -133,7 +140,15 @@ export async function interactiveFlow() { ]); // Combine all answers - const allAnswers = { ...basicAnswers, storageType, assetLocation, ...technicalAnswers, ...advancedAnswers }; + const allAnswers = { + ...basicAnswers, + storageType, + assetLocation, + isCharged, + ...paymentDetails, + ...technicalAnswers, + ...advancedAnswers + }; console.log('\nHere are your responses:'); console.log(allAnswers); From 31a5a04600faa673723c03b4fc05a8a2a6d917cc Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Thu, 5 Sep 2024 15:06:36 +0300 Subject: [PATCH 09/31] Updated flow --- src/interactiveFlow.ts | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index de10256..31f4c7a 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -9,7 +9,7 @@ interface Answers { storageType: 'IPFS' | 'Arweave' | 'URL'; assetLocation: string; isCharged: 'Paid' | 'Free'; - token?: string; + token?: 'OCEAN' | 'H2O'; price?: string; network: 'Oasis Sapphire' | 'Ethereum' | 'Polygon'; template?: string; @@ -94,9 +94,10 @@ export async function interactiveFlow() { const paymentDetails = isCharged === 'Paid' ? await prompt>([ { - type: 'input', + type: 'select', name: 'token', message: 'What token will you accept payments in?\n', + choices: ['OCEAN', 'H2O'], }, { type: 'input', @@ -106,7 +107,8 @@ export async function interactiveFlow() { ]) : {}; - const technicalAnswers = await prompt>([ + // Prompt for network selection + const { network } = await prompt<{ network: Answers['network'] }>([ { type: 'select', name: 'network', @@ -114,15 +116,20 @@ export async function interactiveFlow() { choices: ['Oasis Sapphire', 'Ethereum', 'Polygon'], initial: 0, }, - { - type: 'select', - name: 'template', - message: 'Which template would you like to use?\n', - choices: ['Template A', 'Template B', 'Template C'], - skip: (answers: Partial) => answers.network === 'Oasis Sapphire', - }, ]); + // Conditionally prompt for template if the network is not 'Oasis Sapphire' + const templateAnswer = network !== 'Oasis Sapphire' + ? await prompt>([ + { + type: 'select', + name: 'template', + message: 'Which template would you like to use?\n', + choices: ['Template A', 'Template B', 'Template C'], + }, + ]) + : {}; + // Prompting for advanced options const advancedAnswers = await prompt>([ { @@ -146,7 +153,8 @@ export async function interactiveFlow() { assetLocation, isCharged, ...paymentDetails, - ...technicalAnswers, + network, + ...templateAnswer, ...advancedAnswers }; From 5e89890a3361ca4521f6c2e7abfb21f2ec1b0c06 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Thu, 5 Sep 2024 15:43:49 +0300 Subject: [PATCH 10/31] Validating input for url / txid and IPFS hash --- src/interactiveFlow.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index 31f4c7a..5421b40 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -17,6 +17,11 @@ interface Answers { customParameter?: string; } +// Validation functions +const validateIPFS = (input: string) => /^Qm[1-9A-HJ-NP-Za-km-z]{44}$/.test(input) || 'Invalid IPFS hash format.'; +const validateArweave = (input: string) => /^[a-zA-Z0-9_-]{43,}$/.test(input) || 'Invalid Arweave transaction ID format.'; +const validateURL = (input: string) => /^(https?:\/\/)[^\s/$.?#].[^\s]*$/.test(input) || 'Invalid URL format.'; + export async function interactiveFlow() { try { // Prompting for basic information @@ -59,22 +64,27 @@ export async function interactiveFlow() { }, ]); - // Determine assetLocation message based on storageType + // Determine assetLocation message and validation based on storageType let assetLocationMessage = 'Please provide the location of your asset:\n'; + let validateFunction; if (storageType === 'IPFS') { assetLocationMessage = 'Please provide the IPFS hash for your asset:\n'; + validateFunction = validateIPFS; } else if (storageType === 'Arweave') { assetLocationMessage = 'Please provide the Arweave transaction ID for your asset:\n'; + validateFunction = validateArweave; } else if (storageType === 'URL') { assetLocationMessage = 'Please provide the URL for your asset:\n'; + validateFunction = validateURL; } - // Prompt for asset location + // Prompt for asset location with validation const { assetLocation } = await prompt<{ assetLocation: string }>([ { type: 'input', name: 'assetLocation', message: assetLocationMessage, + validate: validateFunction, }, ]); From bd077b4f139b04083bdc27a064cdbae2d050c865 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Thu, 5 Sep 2024 15:44:53 +0300 Subject: [PATCH 11/31] Removing advanced options for now --- src/interactiveFlow.ts | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index 5421b40..2177d96 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -141,20 +141,20 @@ export async function interactiveFlow() { : {}; // Prompting for advanced options - const advancedAnswers = await prompt>([ - { - type: 'confirm', - name: 'showAdvanced', - message: 'Would you like to consider all of the advanced options for your asset?\n', - initial: false, - }, - { - type: 'input', - name: 'customParameter', - message: 'Please provide any user-defined parameters:\n', - skip: (answers: Partial) => !answers.showAdvanced, - }, - ]); + // const advancedAnswers = await prompt>([ + // { + // type: 'confirm', + // name: 'showAdvanced', + // message: 'Would you like to consider all of the advanced options for your asset?\n', + // initial: false, + // }, + // { + // type: 'input', + // name: 'customParameter', + // message: 'Please provide any user-defined parameters:\n', + // skip: (answers: Partial) => !answers.showAdvanced, + // }, + // ]); // Combine all answers const allAnswers = { @@ -164,8 +164,7 @@ export async function interactiveFlow() { isCharged, ...paymentDetails, network, - ...templateAnswer, - ...advancedAnswers + ...templateAnswer }; console.log('\nHere are your responses:'); From f7c27de23377ddc6230d9b43e7550dc97ebd511c Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Thu, 5 Sep 2024 15:52:06 +0300 Subject: [PATCH 12/31] updating template options --- src/interactiveFlow.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index 2177d96..d11ddf4 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -135,7 +135,8 @@ export async function interactiveFlow() { type: 'select', name: 'template', message: 'Which template would you like to use?\n', - choices: ['Template A', 'Template B', 'Template C'], + choices: ['Template 1 - user can buy, sell & hold datatokens.', + 'Template 2 - assets are purchased with basetokens and the effective supply of datatokens is is always zero.'], }, ]) : {}; From 369f3b0a7effe7923cc5152e64031cd563b81393 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Mon, 9 Sep 2024 14:42:27 +0300 Subject: [PATCH 13/31] Updating the flow to fix bug in asking for payment details --- src/interactiveFlow.ts | 72 ++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index d11ddf4..0a8d1dd 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -100,22 +100,24 @@ export async function interactiveFlow() { }, ]); - // Continue with further technical details, conditional prompts based on isCharged - const paymentDetails = isCharged === 'Paid' - ? await prompt>([ - { - type: 'select', - name: 'token', - message: 'What token will you accept payments in?\n', - choices: ['OCEAN', 'H2O'], - }, - { - type: 'input', - name: 'price', - message: 'What is the price to access your asset?\n', - }, - ]) - : {}; + // Check if isCharged is 'Paid' to ask further questions about payment + let paymentDetails = {}; + + if (isCharged) { + paymentDetails = await prompt>([ + { + type: 'select', + name: 'token', + message: 'What token will you accept payments in?\n', + choices: ['OCEAN', 'H2O'], + }, + { + type: 'input', + name: 'price', + message: 'What is the price to access your asset?\n', + }, + ]); + } // Prompt for network selection const { network } = await prompt<{ network: Answers['network'] }>([ @@ -135,37 +137,23 @@ export async function interactiveFlow() { type: 'select', name: 'template', message: 'Which template would you like to use?\n', - choices: ['Template 1 - user can buy, sell & hold datatokens.', - 'Template 2 - assets are purchased with basetokens and the effective supply of datatokens is is always zero.'], + choices: [ + 'Template 1 - user can buy, sell & hold datatokens.', + 'Template 2 - assets are purchased with basetokens and the effective supply of datatokens is is always zero.', + ], }, ]) : {}; - // Prompting for advanced options - // const advancedAnswers = await prompt>([ - // { - // type: 'confirm', - // name: 'showAdvanced', - // message: 'Would you like to consider all of the advanced options for your asset?\n', - // initial: false, - // }, - // { - // type: 'input', - // name: 'customParameter', - // message: 'Please provide any user-defined parameters:\n', - // skip: (answers: Partial) => !answers.showAdvanced, - // }, - // ]); - // Combine all answers - const allAnswers = { - ...basicAnswers, - storageType, - assetLocation, - isCharged, - ...paymentDetails, - network, - ...templateAnswer + const allAnswers = { + ...basicAnswers, + storageType, + assetLocation, + isCharged, + ...paymentDetails, + network, + ...templateAnswer, }; console.log('\nHere are your responses:'); From 2d22f8ea74c6e0d2ccb35188241b2cbe189312f9 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Mon, 9 Sep 2024 15:58:56 +0300 Subject: [PATCH 14/31] Updating the flow to publish the asset and update the metadata --- src/commands.ts | 7 ++- src/interactiveFlow.ts | 62 +++++++++--------- src/publishAsset.ts | 140 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 34 deletions(-) create mode 100644 src/publishAsset.ts diff --git a/src/commands.ts b/src/commands.ts index 785967e..4f48bf3 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -24,6 +24,7 @@ import { } from "@oceanprotocol/lib"; import { Signer, ethers } from "ethers"; import { interactiveFlow } from "./interactiveFlow"; +import { publishAsset } from "./publishAsset"; export class Commands { public signer: Signer; @@ -62,10 +63,10 @@ export class Commands { ); } - // start the interactive publish flow public async start() { - console.log("Starting the CLI flow...\n\n"); - await interactiveFlow(); // Call the CLI logic + console.log('Starting the CLI flow...\n\n'); + const data = await interactiveFlow(this.providerUrl); // Collect data via CLI + await publishAsset(data, this.signer, this.config); // Publish asset with collected data } // utils diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index 0a8d1dd..e047672 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -1,66 +1,60 @@ +// interactiveFlow.ts import { prompt } from 'enquirer'; - -interface Answers { - title: string; - description: string; - author: string; - tags: string; - accessDuration: 'Forever' | '1 day' | '1 week' | '1 month' | '1 year'; - storageType: 'IPFS' | 'Arweave' | 'URL'; - assetLocation: string; - isCharged: 'Paid' | 'Free'; - token?: 'OCEAN' | 'H2O'; - price?: string; - network: 'Oasis Sapphire' | 'Ethereum' | 'Polygon'; - template?: string; - showAdvanced: boolean; - customParameter?: string; -} +import { PublishAssetParams } from './publishAsset'; // Import the correct type // Validation functions -const validateIPFS = (input: string) => /^Qm[1-9A-HJ-NP-Za-km-z]{44}$/.test(input) || 'Invalid IPFS hash format.'; -const validateArweave = (input: string) => /^[a-zA-Z0-9_-]{43,}$/.test(input) || 'Invalid Arweave transaction ID format.'; -const validateURL = (input: string) => /^(https?:\/\/)[^\s/$.?#].[^\s]*$/.test(input) || 'Invalid URL format.'; +const validateIPFS = (input: string) => + /^Qm[1-9A-HJ-NP-Za-km-z]{44}$/.test(input) || 'Invalid IPFS hash format.'; +const validateArweave = (input: string) => + /^[a-zA-Z0-9_-]{43,}$/.test(input) || 'Invalid Arweave transaction ID format.'; +const validateURL = (input: string) => + /^(https?:\/\/)[^\s/$.?#].[^\s]*$/.test(input) || 'Invalid URL format.'; -export async function interactiveFlow() { +export async function interactiveFlow(providerUrl: string): Promise { try { // Prompting for basic information - const basicAnswers = await prompt>([ + const basicAnswers = await prompt([ { type: 'input', name: 'title', message: 'What is the title of your asset?\n', + required: true }, { type: 'input', name: 'description', message: 'Please provide a description of your asset:\n', + required: true }, { type: 'input', name: 'author', message: 'Who is the Author of this asset?\n', + required: true }, { type: 'list', name: 'tags', message: 'Please provide tags to make this asset more easily discoverable (comma separated):\n', + required: true }, { type: 'select', name: 'accessDuration', message: 'After purchasing your asset, how long should the consumer be allowed to access it for?\n', choices: ['Forever', '1 day', '1 week', '1 month', '1 year'], + required: true }, ]); // Prompting for technical details - first, get storage type - const { storageType } = await prompt<{ storageType: Answers['storageType'] }>([ + const { storageType } = await prompt<{ storageType: PublishAssetParams['storageType'] }>([ { type: 'select', name: 'storageType', message: 'How is your asset stored?\n', choices: ['IPFS', 'Arweave', 'URL'], + required: true }, ]); @@ -85,11 +79,12 @@ export async function interactiveFlow() { name: 'assetLocation', message: assetLocationMessage, validate: validateFunction, + required: true }, ]); // Prompt for whether the asset is charged or free - const { isCharged } = await prompt<{ isCharged: Answers['isCharged'] }>([ + const { isCharged } = await prompt<{ isCharged: PublishAssetParams['isCharged'] }>([ { type: 'toggle', name: 'isCharged', @@ -97,14 +92,14 @@ export async function interactiveFlow() { initial: 'Paid', enabled: 'Paid', disabled: 'Free', + required: true }, ]); // Check if isCharged is 'Paid' to ask further questions about payment let paymentDetails = {}; - - if (isCharged) { - paymentDetails = await prompt>([ + if (isCharged === 'Paid') { + paymentDetails = await prompt>([ { type: 'select', name: 'token', @@ -120,33 +115,34 @@ export async function interactiveFlow() { } // Prompt for network selection - const { network } = await prompt<{ network: Answers['network'] }>([ + const { network } = await prompt<{ network: PublishAssetParams['network'] }>([ { type: 'select', name: 'network', message: 'What network will your asset be available for purchase through?\n', choices: ['Oasis Sapphire', 'Ethereum', 'Polygon'], initial: 0, + required: true }, ]); // Conditionally prompt for template if the network is not 'Oasis Sapphire' const templateAnswer = network !== 'Oasis Sapphire' - ? await prompt>([ + ? await prompt>([ { type: 'select', name: 'template', message: 'Which template would you like to use?\n', choices: [ 'Template 1 - user can buy, sell & hold datatokens.', - 'Template 2 - assets are purchased with basetokens and the effective supply of datatokens is is always zero.', + 'Template 2 - assets are purchased with basetokens and the effective supply of datatokens is always zero.', ], }, ]) : {}; // Combine all answers - const allAnswers = { + const allAnswers: PublishAssetParams = { ...basicAnswers, storageType, assetLocation, @@ -154,11 +150,15 @@ export async function interactiveFlow() { ...paymentDetails, network, ...templateAnswer, + providerUrl // Add provider URL directly here }; console.log('\nHere are your responses:'); console.log(allAnswers); + + return allAnswers; } catch (error) { console.error('An error occurred during the prompt:', error); + throw error; } } diff --git a/src/publishAsset.ts b/src/publishAsset.ts new file mode 100644 index 0000000..433c5ca --- /dev/null +++ b/src/publishAsset.ts @@ -0,0 +1,140 @@ +// publishAsset.ts + +import { Signer, providers, ethers } from 'ethers'; +import { + Config, + Nft, + NftFactory, + DatatokenCreateParams, + Aquarius, + ProviderInstance, + getEventFromTx, + Files, + DDO, +} from '@oceanprotocol/lib'; + +export interface PublishAssetParams { + title: string; + description: string; + author: string; + tags: string[]; + accessDuration: string; + storageType: 'IPFS' | 'Arweave' | 'URL'; + assetLocation: string; + isCharged: 'Paid' | 'Free'; + token?: 'OCEAN' | 'H2O'; + price?: string; + network: 'Oasis Sapphire' | 'Ethereum' | 'Polygon'; + template?: string; + providerUrl: string; +} + +export async function publishAsset(params: PublishAssetParams, signer: Signer, config: Config) { + // Load configuration + const provider = signer.provider as providers.JsonRpcProvider; + const aquarius = new Aquarius(config.metadataCacheUri); + + // Set the asset files information + const assetFiles: Files = { + nftAddress: '0x0', + datatokenAddress: '0x0', + files: [{ type: 'url', url: params.assetLocation, method: 'GET' }], + }; + + // Prepare metadata for the asset + const metadata: DDO = { + '@context': ['https://w3id.org/did/v1'], + id: '', + version: '4.1.0', + chainId: await provider.getNetwork().then(n => n.chainId), + nftAddress: '0x0', + metadata: { + created: new Date().toISOString(), + updated: new Date().toISOString(), + type: 'dataset', + name: params.title, + description: params.description, + author: params.author, + license: 'MIT', + tags: params.tags, + additionalInformation: { + accessDuration: params.accessDuration, + isCharged: params.isCharged, + token: params.token, + price: params.price, + }, + }, + services: [ + { + id: 'access', + type: 'access', + description: 'Access service', + files: '', + datatokenAddress: '0x0', + serviceEndpoint: params.providerUrl, + timeout: 0, + }, + ], + }; + + // Create NFT and Datatoken + const nftFactory = new NftFactory(config.erc721FactoryAddress, signer); + const nftCreateData = { + name: params.title, + symbol: 'DATATOKEN', + templateIndex: 1, + tokenURI: '', + transferable: true, + owner: await signer.getAddress(), + }; + + const datatokenCreateParams: DatatokenCreateParams = { + templateIndex: 1, + cap: '100000', + feeAmount: '0', + paymentCollector: ethers.constants.AddressZero, + feeToken: ethers.constants.AddressZero, + minter: await signer.getAddress(), + mpFeeAddress: ethers.constants.AddressZero, + }; + + const nftWithDatatoken = await nftFactory.createNftWithDatatoken( + nftCreateData, + datatokenCreateParams + ); + const nftCreatedEvent = getEventFromTx(await nftWithDatatoken.wait(), 'NFTCreated'); + const datatokenCreatedEvent = getEventFromTx(await nftWithDatatoken.wait(), 'TokenCreated'); + + // Set addresses in the asset files and metadata + assetFiles.nftAddress = nftCreatedEvent.args.newTokenAddress; + assetFiles.datatokenAddress = datatokenCreatedEvent.args.newTokenAddress; + metadata.nftAddress = nftCreatedEvent.args.newTokenAddress; + + // Encrypt the files using provider + metadata.services[0].files = await ProviderInstance.encrypt( + assetFiles, + metadata.chainId, + params.providerUrl + ); + + // Validate metadata + const validation = await aquarius.validate(metadata); + if (!validation.valid) { + throw new Error('Invalid asset metadata'); + } + + // Set metadata on the NFT + const nft = new Nft(signer, metadata.chainId); + await nft.setMetadata( + metadata.nftAddress, + await signer.getAddress(), + 0, + params.providerUrl, + '', + ethers.utils.hexlify(2), + metadata, + validation.hash + ); + + console.log(`Asset published with DID: ${metadata.id}`); +} From 7c0f672842d6ad6018c4db5ee326d128af6263d0 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Mon, 9 Sep 2024 17:09:42 +0300 Subject: [PATCH 15/31] Successfully publishing asset --- src/commands.ts | 2 +- src/helpers.ts | 4 +- src/publishAsset.ts | 172 ++++++++++++++++---------------------------- 3 files changed, 65 insertions(+), 113 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 4f48bf3..35d30b8 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -64,7 +64,7 @@ export class Commands { } public async start() { - console.log('Starting the CLI flow...\n\n'); + console.log('Starting the interactive CLI flow...\n\n'); const data = await interactiveFlow(this.providerUrl); // Collect data via CLI await publishAsset(data, this.signer, this.config); // Publish asset with collected data } diff --git a/src/helpers.ts b/src/helpers.ts index 8ff6f45..59f5458 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -101,12 +101,12 @@ export async function createAsset( }; let bundleNFT; - if (!ddo.stats.price.value) { + if (!ddo.stats?.price?.value) { bundleNFT = await nftFactory.createNftWithDatatoken( nftParamsAsset, datatokenParams ); - } else if (ddo.stats.price.value === "0") { + } else if (ddo?.stats?.price?.value === "0") { const dispenserParams: DispenserCreationParams = { dispenserAddress: config.dispenserAddress, maxTokens: "1", diff --git a/src/publishAsset.ts b/src/publishAsset.ts index 433c5ca..ed7f4d9 100644 --- a/src/publishAsset.ts +++ b/src/publishAsset.ts @@ -1,17 +1,10 @@ -// publishAsset.ts - -import { Signer, providers, ethers } from 'ethers'; +import { Signer, providers } from 'ethers'; import { Config, - Nft, - NftFactory, - DatatokenCreateParams, Aquarius, - ProviderInstance, - getEventFromTx, - Files, DDO, } from '@oceanprotocol/lib'; +import { createAsset } from './helpers'; // Import the helper function export interface PublishAssetParams { title: string; @@ -30,111 +23,70 @@ export interface PublishAssetParams { } export async function publishAsset(params: PublishAssetParams, signer: Signer, config: Config) { - // Load configuration - const provider = signer.provider as providers.JsonRpcProvider; - const aquarius = new Aquarius(config.metadataCacheUri); - - // Set the asset files information - const assetFiles: Files = { - nftAddress: '0x0', - datatokenAddress: '0x0', - files: [{ type: 'url', url: params.assetLocation, method: 'GET' }], - }; - - // Prepare metadata for the asset - const metadata: DDO = { - '@context': ['https://w3id.org/did/v1'], - id: '', - version: '4.1.0', - chainId: await provider.getNetwork().then(n => n.chainId), - nftAddress: '0x0', - metadata: { - created: new Date().toISOString(), - updated: new Date().toISOString(), - type: 'dataset', - name: params.title, - description: params.description, - author: params.author, - license: 'MIT', - tags: params.tags, - additionalInformation: { - accessDuration: params.accessDuration, - isCharged: params.isCharged, - token: params.token, - price: params.price, - }, - }, - services: [ - { - id: 'access', - type: 'access', - description: 'Access service', - files: '', - datatokenAddress: '0x0', - serviceEndpoint: params.providerUrl, - timeout: 0, - }, - ], - }; + try { + console.log('Publishing asset using helper function...'); - // Create NFT and Datatoken - const nftFactory = new NftFactory(config.erc721FactoryAddress, signer); - const nftCreateData = { - name: params.title, - symbol: 'DATATOKEN', - templateIndex: 1, - tokenURI: '', - transferable: true, - owner: await signer.getAddress(), - }; + const provider = signer.provider as providers.JsonRpcProvider; + const aquarius = new Aquarius(config.metadataCacheUri); - const datatokenCreateParams: DatatokenCreateParams = { - templateIndex: 1, - cap: '100000', - feeAmount: '0', - paymentCollector: ethers.constants.AddressZero, - feeToken: ethers.constants.AddressZero, - minter: await signer.getAddress(), - mpFeeAddress: ethers.constants.AddressZero, - }; - - const nftWithDatatoken = await nftFactory.createNftWithDatatoken( - nftCreateData, - datatokenCreateParams - ); - const nftCreatedEvent = getEventFromTx(await nftWithDatatoken.wait(), 'NFTCreated'); - const datatokenCreatedEvent = getEventFromTx(await nftWithDatatoken.wait(), 'TokenCreated'); + // Prepare metadata for the asset + const metadata: DDO = { + '@context': ['https://w3id.org/did/v1'], + id: '', + version: '4.1.0', + chainId: await provider.getNetwork().then(n => n.chainId), + nftAddress: '0x0', + metadata: { + created: new Date().toISOString(), + updated: new Date().toISOString(), + type: 'dataset', + name: params.title, + description: params.description, + author: params.author, + license: 'MIT', + tags: params.tags + }, + stats: { + allocated: "0", + orders: "0", + price: { + value: "0" + } + }, + services: [ + { + id: 'access', + type: 'access', + description: 'Access service', + files: '', + datatokenAddress: '0x0', + serviceEndpoint: params.providerUrl, + timeout: 0, + }, + ], + }; - // Set addresses in the asset files and metadata - assetFiles.nftAddress = nftCreatedEvent.args.newTokenAddress; - assetFiles.datatokenAddress = datatokenCreatedEvent.args.newTokenAddress; - metadata.nftAddress = nftCreatedEvent.args.newTokenAddress; + // Asset URL setup based on storage type + const assetUrl = { + nftAddress: '0x0', + datatokenAddress: '0x0', + files: [{ type: 'url', url: params.assetLocation, method: 'GET' }], + }; - // Encrypt the files using provider - metadata.services[0].files = await ProviderInstance.encrypt( - assetFiles, - metadata.chainId, - params.providerUrl - ); + // Call the helper function to create the asset + const did = await createAsset( + params.title, + 'DATATOKEN', // Assuming a standard symbol for now + signer, + assetUrl, + metadata, + params.providerUrl, + config, + aquarius + ); - // Validate metadata - const validation = await aquarius.validate(metadata); - if (!validation.valid) { - throw new Error('Invalid asset metadata'); + console.log(`Asset successfully published with DID: ${did}`); + } catch (error) { + console.error('Error publishing asset:', error); } - - // Set metadata on the NFT - const nft = new Nft(signer, metadata.chainId); - await nft.setMetadata( - metadata.nftAddress, - await signer.getAddress(), - 0, - params.providerUrl, - '', - ethers.utils.hexlify(2), - metadata, - validation.hash - ); - - console.log(`Asset published with DID: ${metadata.id}`); } From 637401baeb2c355fe080d0499e7f99e7965e839f Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Mon, 9 Sep 2024 17:22:07 +0300 Subject: [PATCH 16/31] Updatting metadata --- src/publishAsset.ts | 48 +++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/publishAsset.ts b/src/publishAsset.ts index ed7f4d9..e9d022b 100644 --- a/src/publishAsset.ts +++ b/src/publishAsset.ts @@ -4,7 +4,7 @@ import { Aquarius, DDO, } from '@oceanprotocol/lib'; -import { createAsset } from './helpers'; // Import the helper function +import { createAsset, updateAssetMetadata } from './helpers'; // Import helper functions export interface PublishAssetParams { title: string; @@ -24,18 +24,18 @@ export interface PublishAssetParams { export async function publishAsset(params: PublishAssetParams, signer: Signer, config: Config) { try { - console.log('Publishing asset using helper function...'); + console.log('Publishing asset using helper functions...'); const provider = signer.provider as providers.JsonRpcProvider; const aquarius = new Aquarius(config.metadataCacheUri); - // Prepare metadata for the asset + // Prepare initial metadata for the asset const metadata: DDO = { '@context': ['https://w3id.org/did/v1'], - id: '', + id: '', // Will be updated after creating asset version: '4.1.0', chainId: await provider.getNetwork().then(n => n.chainId), - nftAddress: '0x0', + nftAddress: '0x0', // Will be updated after creating asset metadata: { created: new Date().toISOString(), updated: new Date().toISOString(), @@ -44,22 +44,21 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c description: params.description, author: params.author, license: 'MIT', - tags: params.tags + tags: params.tags, + additionalInformation: { + accessDuration: params.accessDuration, + isCharged: params.isCharged, + token: params.token, + price: params.price, + }, }, - stats: { - allocated: "0", - orders: "0", - price: { - value: "0" - } - }, services: [ { id: 'access', type: 'access', description: 'Access service', files: '', - datatokenAddress: '0x0', + datatokenAddress: '0x0', // Will be updated after creating asset serviceEndpoint: params.providerUrl, timeout: 0, }, @@ -68,12 +67,12 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c // Asset URL setup based on storage type const assetUrl = { - nftAddress: '0x0', - datatokenAddress: '0x0', + nftAddress: '0x0', // Will be updated after creating asset + datatokenAddress: '0x0', // Will be updated after creating asset files: [{ type: 'url', url: params.assetLocation, method: 'GET' }], }; - // Call the helper function to create the asset + // Create the asset using the helper function const did = await createAsset( params.title, 'DATATOKEN', // Assuming a standard symbol for now @@ -86,6 +85,21 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c ); console.log(`Asset successfully published with DID: ${did}`); + + // Update the metadata with the asset's DID + metadata.id = did; + metadata.nftAddress = assetUrl.nftAddress; // Update with the correct NFT address + metadata.services[0].datatokenAddress = assetUrl.datatokenAddress; // Update with the correct Datatoken address + + // Use the helper function to update the metadata on the NFT + await updateAssetMetadata( + signer, + metadata, + params.providerUrl, + aquarius + ); + + console.log(`Metadata successfully updated for DID: ${did}`); } catch (error) { console.error('Error publishing asset:', error); } From 30c23429818d8d4d6328b9d70a88ba242b89578d Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Mon, 9 Sep 2024 17:36:14 +0300 Subject: [PATCH 17/31] DDO improvements --- src/publishAsset.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/publishAsset.ts b/src/publishAsset.ts index e9d022b..b6c220b 100644 --- a/src/publishAsset.ts +++ b/src/publishAsset.ts @@ -44,14 +44,15 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c description: params.description, author: params.author, license: 'MIT', - tags: params.tags, - additionalInformation: { - accessDuration: params.accessDuration, - isCharged: params.isCharged, - token: params.token, - price: params.price, - }, + tags: params.tags }, + stats: { + allocated: 0, + orders: 0, + price: { + value: "0" + } + }, services: [ { id: 'access', @@ -63,6 +64,15 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c timeout: 0, }, ], + nft: { + address: "", + name: "Ocean Data NFT", + symbol: "OCEAN-NFT", + state: 5, + tokenURI: "", + owner: "", + created: "" + } }; // Asset URL setup based on storage type From d53be660b521675ffe7976dd41cd362e0d0e000d Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Tue, 10 Sep 2024 15:50:34 +0300 Subject: [PATCH 18/31] Fixing the template --- src/commands.ts | 2 ++ src/helpers.ts | 7 ++++--- src/interactiveFlow.ts | 4 ++-- src/publishAsset.ts | 5 +++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 35d30b8..3c85936 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -99,6 +99,7 @@ export class Commands { this.providerUrl, this.config, this.aquarius, + 1, this.macOsProviderUrl, encryptDDO ); @@ -130,6 +131,7 @@ export class Commands { this.providerUrl, this.config, this.aquarius, + 1, this.macOsProviderUrl, encryptDDO ); diff --git a/src/helpers.ts b/src/helpers.ts index 59f5458..1328b88 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -74,8 +74,9 @@ export async function createAsset( providerUrl: string, config: Config, aquariusInstance: Aquarius, + templateIndex: number = 1, macOsProviderUrl?: string, - encryptDDO: boolean = true + encryptDDO: boolean = true, ) { const { chainId } = await owner.provider.getNetwork(); const nft = new Nft(owner, chainId); @@ -85,13 +86,13 @@ export async function createAsset( const nftParamsAsset: NftCreateData = { name, symbol, - templateIndex: 1, + templateIndex, tokenURI: "aaa", transferable: true, owner: await owner.getAddress(), }; const datatokenParams: DatatokenCreateParams = { - templateIndex: 1, + templateIndex, cap: "100000", feeAmount: "0", paymentCollector: await owner.getAddress(), diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index e047672..caa9855 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -134,8 +134,8 @@ export async function interactiveFlow(providerUrl: string): Promise Date: Wed, 11 Sep 2024 12:56:31 +0300 Subject: [PATCH 19/31] Updating chainId and timeout values --- src/interactiveFlow.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index caa9855..89c9a80 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -40,9 +40,15 @@ export async function interactiveFlow(providerUrl: string): Promise([ { type: 'select', - name: 'network', + name: 'chainId', message: 'What network will your asset be available for purchase through?\n', - choices: ['Oasis Sapphire', 'Ethereum', 'Polygon'], + choices: [{name: 'Oasis Sapphire', value: 23294}, {name: 'Ethereum', value: 1}, {name: 'Polygon', value: 137}], initial: 0, required: true }, From 094c1867d94e33a82ba26bbb98beb3f5972fe36a Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Wed, 11 Sep 2024 14:26:09 +0300 Subject: [PATCH 20/31] Updating the values that are passed from the interactive CLI --- src/interactiveFlow.ts | 149 +++++++++++++++++++++-------------------- src/publishAsset.ts | 13 ++-- 2 files changed, 82 insertions(+), 80 deletions(-) diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index 89c9a80..d0334f2 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -1,6 +1,6 @@ // interactiveFlow.ts import { prompt } from 'enquirer'; -import { PublishAssetParams } from './publishAsset'; // Import the correct type +import { PublishAssetParams } from './publishAsset'; // Validation functions const validateIPFS = (input: string) => @@ -13,7 +13,7 @@ const validateURL = (input: string) => export async function interactiveFlow(providerUrl: string): Promise { try { // Prompting for basic information - const basicAnswers = await prompt([ + const basicAnswers = await prompt>([ { type: 'input', name: 'title', @@ -38,42 +38,46 @@ export async function interactiveFlow(providerUrl: string): Promise([ - { - type: 'select', - name: 'storageType', - message: 'How is your asset stored?\n', - choices: [{name: 'IPFS', value: 'ipfs'}, {name: 'Arweave', value: 'arweave'}, {name: 'URL', value: 'url'}], - required: true - }, - ]); + // Timeout prompt + const { timeout } = await prompt<{ timeout: number }>({ + type: 'select', + name: 'timeout', + message: 'After purchasing your asset, how long should the consumer be allowed to access it for?\n', + choices: [ + {name: 'Forever', value: 0}, + {name: '1 day', value: 86400}, + {name: '1 week', value: 604800}, + {name: '1 month', value: 2592000}, + {name: '1 year', value: 31536000} + ], + result(value) { + return this.choices.find(choice => choice.name === value).value; + } + }); + + // Storage type prompt + const { storageType } = await prompt<{ storageType: PublishAssetParams['storageType'] }>({ + type: 'select', + name: 'storageType', + message: 'How is your asset stored?\n', + choices: [{name: 'IPFS', value: 'ipfs'}, {name: 'Arweave', value: 'arweave'}, {name: 'URL', value: 'url'}], + result(value) { + return this.choices.find(choice => choice.name === value).value; + } + }); // Determine assetLocation message and validation based on storageType let assetLocationMessage = 'Please provide the location of your asset:\n'; let validateFunction; - if (storageType === 'IPFS') { + if (storageType === 'ipfs') { assetLocationMessage = 'Please provide the IPFS hash for your asset:\n'; validateFunction = validateIPFS; - } else if (storageType === 'Arweave') { + } else if (storageType === 'arweave') { assetLocationMessage = 'Please provide the Arweave transaction ID for your asset:\n'; validateFunction = validateArweave; - } else if (storageType === 'URL') { + } else if (storageType === 'url') { assetLocationMessage = 'Please provide the URL for your asset:\n'; validateFunction = validateURL; } @@ -89,23 +93,19 @@ export async function interactiveFlow(providerUrl: string): Promise([ - { - type: 'toggle', - name: 'isCharged', - message: 'Will you charge for this asset?\n', - initial: 'Paid', - enabled: 'Paid', - disabled: 'Free', - required: true - }, - ]); + // Is charged prompt + const { isCharged } = await prompt<{ isCharged: boolean }>({ + type: 'toggle', + name: 'isCharged', + message: 'Will you charge for this asset?\n', + enabled: 'Paid', + disabled: 'Free', + }); - // Check if isCharged is 'Paid' to ask further questions about payment - let paymentDetails = {}; - if (isCharged === 'Paid') { - paymentDetails = await prompt>([ + // Check if isCharged is true to ask further questions about payment + let paymentDetails: { token?: 'OCEAN' | 'H2O'; price?: string } = {}; + if (isCharged) { + paymentDetails = await prompt<{ token: 'OCEAN' | 'H2O'; price: string }>([ { type: 'select', name: 'token', @@ -120,43 +120,46 @@ export async function interactiveFlow(providerUrl: string): Promise([ - { - type: 'select', - name: 'chainId', - message: 'What network will your asset be available for purchase through?\n', - choices: [{name: 'Oasis Sapphire', value: 23294}, {name: 'Ethereum', value: 1}, {name: 'Polygon', value: 137}], - initial: 0, - required: true - }, - ]); + // Chain ID prompt + const { chainId } = await prompt<{ chainId: number }>({ + type: 'select', + name: 'chainId', + message: 'What network will your asset be available for purchase through?\n', + choices: [{name: 'Oasis Sapphire', value: 23294}, {name: 'Ethereum', value: 1}, {name: 'Polygon', value: 137}], + result(value) { + return this.choices.find(choice => choice.name === value).value; + } + }); - // Conditionally prompt for template if the network is not 'Oasis Sapphire' - const templateAnswer = network !== 'Oasis Sapphire' - ? await prompt>([ - { - type: 'select', - name: 'template', - message: 'Which template would you like to use?\n', - choices: [ - { name: 'Template 1 - user can buy, sell & hold datatokens.', value: 1 }, - { name: 'Template 2 - assets are purchased with basetokens and the effective supply of datatokens is always zero.', value: 2 }, - ], - }, - ]) - : {}; + // Template prompt + let template: number | undefined; + if (chainId !== 23294) { + const templateAnswer = await prompt<{ template: number }>({ + type: 'select', + name: 'template', + message: 'Which template would you like to use?\n', + choices: [ + { name: 'Template 1 - user can buy, sell & hold datatokens.', value: 1 }, + { name: 'Template 2 - assets are purchased with basetokens and the effective supply of datatokens is always zero.', value: 2 }, + ], + result(value) { + return this.choices.find(choice => choice.name === value).value; + } + }); + template = templateAnswer.template; + } // Combine all answers const allAnswers: PublishAssetParams = { ...basicAnswers, + timeout, storageType, assetLocation, isCharged, ...paymentDetails, - network, - ...templateAnswer, - providerUrl // Add provider URL directly here + chainId, + template, + providerUrl }; console.log('\nHere are your responses:'); @@ -167,4 +170,4 @@ export async function interactiveFlow(providerUrl: string): Promise n.chainId), + chainId: params.chainId, nftAddress: '0x0', // Will be updated after creating asset metadata: { created: new Date().toISOString(), From ddc44d32811d58b06e80d280a7513d8be4ef6e58 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Wed, 11 Sep 2024 14:31:51 +0300 Subject: [PATCH 21/31] Updating templateIndex --- src/publishAsset.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/publishAsset.ts b/src/publishAsset.ts index ec005a9..5774dce 100644 --- a/src/publishAsset.ts +++ b/src/publishAsset.ts @@ -1,3 +1,4 @@ +// src/publishAsset.ts import { Signer } from 'ethers'; import { Config, @@ -91,7 +92,7 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c params.providerUrl, config, aquarius, - params.template + 1 ); console.log(`Asset successfully published with DID: ${did}`); From 35184802d16c535cff6be1a6691bd4aeb330cdf2 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Wed, 11 Sep 2024 14:54:41 +0300 Subject: [PATCH 22/31] Creating test for the interactive publish flow --- test/interactivePublishFlow.ts | 93 ++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 test/interactivePublishFlow.ts diff --git a/test/interactivePublishFlow.ts b/test/interactivePublishFlow.ts new file mode 100644 index 0000000..74e0203 --- /dev/null +++ b/test/interactivePublishFlow.ts @@ -0,0 +1,93 @@ +import { expect } from "chai"; +import { exec } from "child_process"; +import path from "path"; + +describe("Ocean CLI Interactive Publishing", function() { + this.timeout(120000); // Set a longer timeout to allow for user input simulation + + const projectRoot = path.resolve(__dirname, ".."); + let publishedDid: string; + + it("should publish an asset using 'npm run cli start' interactive flow", function(done) { + process.env.PRIVATE_KEY = "0x1d751ded5a32226054cd2e71261039b65afb9ee1c746d055dd699b1150a5befc"; + process.env.RPC = "http://127.0.0.1:8545"; + process.env.AQUARIUS_URL = "http://127.0.0.1:8001"; + process.env.PROVIDER_URL = "http://127.0.0.1:8001"; + process.env.ADDRESS_FILE = path.join(process.env.HOME || "", ".ocean/ocean-contracts/artifacts/address.json"); + + const child = exec(`npm run cli start`, { cwd: projectRoot }); + + // Simulate user input + const inputs = [ + "test 123\n", + "description\n", + "me\n", + "test,123\n", + "\n", // Select default (1 day) + "\n", // Select default (IPFS) + "QmdbaSQbGU6Wo9i5LyWWVLuU8g6WrYpWh2K4Li4QuuE8Fr\n", + "\n", // Select default (Paid) + "\n", // Select default (OCEAN) + "100\n", + "\n", // Select default (Polygon) + "\n", // Select default (Template 2) + ]; + + let inputIndex = 0; + let fullOutput = ""; + + if (child.stdin) { + const inputInterval = setInterval(() => { + if (inputIndex < inputs.length) { + child.stdin.write(inputs[inputIndex]); + inputIndex++; + } else { + clearInterval(inputInterval); + if (child.stdin) child.stdin.end(); + } + }, 1000); // Adjust timing as needed + } + + child.stdout?.on('data', (data) => { + fullOutput += data.toString(); + }); + + child.on('close', (code) => { + try { + expect(code).to.equal(0); + expect(fullOutput).to.contain("Asset successfully published with DID:"); + expect(fullOutput).to.contain("Metadata successfully updated for DID:"); + + const match = fullOutput.match(/did:op:[a-f0-9]{64}/); + if (match) { + publishedDid = match[0]; + expect(publishedDid).to.match(/^did:op:[a-f0-9]{64}$/); + } else { + throw new Error("DID not found in output"); + } + + done(); + } catch (assertionError) { + done(assertionError); + } + }); + }); + + it("should get DDO using 'npm run cli getDDO' for the published asset", function(done) { + exec(`npm run cli getDDO ${publishedDid}`, { cwd: projectRoot }, (error, stdout) => { + try { + expect(stdout).to.contain(`${publishedDid}`); + expect(stdout).to.contain("https://w3id.org/did/v1"); + expect(stdout).to.contain("Datatoken"); + expect(stdout).to.contain("test 123"); // Asset title + expect(stdout).to.contain("description"); // Asset description + expect(stdout).to.contain("me"); // Author + expect(stdout).to.contain("test"); // Tag + expect(stdout).to.contain("123"); // Tag + done(); + } catch (assertionError) { + done(assertionError); + } + }); + }); +}); \ No newline at end of file From d3c85a8df7512886c52071d4cbc2c2240131148a Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Wed, 11 Sep 2024 15:17:17 +0300 Subject: [PATCH 23/31] Updating the styling of the interactive cli commands --- package-lock.json | 17 +++++++++++++++++ package.json | 1 + src/interactiveFlow.ts | 40 +++++++++++++++++++++++++--------------- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3c91eab..589b1ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "decimal.js": "^10.4.1", "enquirer": "^2.4.1", "ethers": "^5.7.2", + "figlet": "^1.7.0", "ts-node": "^10.9.1" }, "devDependencies": { @@ -4203,6 +4204,17 @@ "reusify": "^1.0.4" } }, + "node_modules/figlet": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.7.0.tgz", + "integrity": "sha512-gO8l3wvqo0V7wEFLXPbkX83b7MVjRrk1oRLfYlZXol8nEpb/ON9pcKLI4qpBv5YtOTfrINtqb7b40iYY2FTWFg==", + "bin": { + "figlet": "bin/index.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -12252,6 +12264,11 @@ "reusify": "^1.0.4" } }, + "figlet": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.7.0.tgz", + "integrity": "sha512-gO8l3wvqo0V7wEFLXPbkX83b7MVjRrk1oRLfYlZXol8nEpb/ON9pcKLI4qpBv5YtOTfrINtqb7b40iYY2FTWFg==" + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", diff --git a/package.json b/package.json index 0058e62..e96d2f5 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "decimal.js": "^10.4.1", "enquirer": "^2.4.1", "ethers": "^5.7.2", + "figlet": "^1.7.0", "ts-node": "^10.9.1" } } diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index d0334f2..d84d899 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -1,6 +1,8 @@ // interactiveFlow.ts import { prompt } from 'enquirer'; import { PublishAssetParams } from './publishAsset'; +import chalk from 'chalk'; +import figlet from 'figlet'; // Validation functions const validateIPFS = (input: string) => @@ -12,30 +14,38 @@ const validateURL = (input: string) => export async function interactiveFlow(providerUrl: string): Promise { try { + console.clear(); + console.log( + chalk.blue( + figlet.textSync('Ocean CLI', { horizontalLayout: 'full' }) + ) + ); + console.log(chalk.cyan('Welcome to the Ocean Protocol Asset Publisher!\n')); + // Prompting for basic information const basicAnswers = await prompt>([ { type: 'input', name: 'title', - message: 'What is the title of your asset?\n', + message: chalk.green('What is the title of your asset?\n'), required: true }, { type: 'input', name: 'description', - message: 'Please provide a description of your asset:\n', + message: chalk.green('Please provide a description of your asset:\n'), required: true }, { type: 'input', name: 'author', - message: 'Who is the Author of this asset?\n', + message: chalk.green('Who is the Author of this asset?\n'), required: true }, { type: 'list', name: 'tags', - message: 'Please provide tags to make this asset more easily discoverable (comma separated):\n', + message: chalk.green('Please provide tags to make this asset more easily discoverable (comma separated):\n'), required: true }, ]); @@ -44,7 +54,7 @@ export async function interactiveFlow(providerUrl: string): Promise({ type: 'select', name: 'timeout', - message: 'After purchasing your asset, how long should the consumer be allowed to access it for?\n', + message: chalk.green('After purchasing your asset, how long should the consumer be allowed to access it for?\n'), choices: [ {name: 'Forever', value: 0}, {name: '1 day', value: 86400}, @@ -61,7 +71,7 @@ export async function interactiveFlow(providerUrl: string): Promise({ type: 'select', name: 'storageType', - message: 'How is your asset stored?\n', + message: chalk.green('How is your asset stored?\n'), choices: [{name: 'IPFS', value: 'ipfs'}, {name: 'Arweave', value: 'arweave'}, {name: 'URL', value: 'url'}], result(value) { return this.choices.find(choice => choice.name === value).value; @@ -87,7 +97,7 @@ export async function interactiveFlow(providerUrl: string): Promise({ type: 'toggle', name: 'isCharged', - message: 'Will you charge for this asset?\n', + message: chalk.green('Will you charge for this asset?\n'), enabled: 'Paid', disabled: 'Free', }); @@ -109,13 +119,13 @@ export async function interactiveFlow(providerUrl: string): Promise({ type: 'select', name: 'chainId', - message: 'What network will your asset be available for purchase through?\n', + message: chalk.green('What network will your asset be available for purchase through?\n'), choices: [{name: 'Oasis Sapphire', value: 23294}, {name: 'Ethereum', value: 1}, {name: 'Polygon', value: 137}], result(value) { return this.choices.find(choice => choice.name === value).value; @@ -137,7 +147,7 @@ export async function interactiveFlow(providerUrl: string): Promise({ type: 'select', name: 'template', - message: 'Which template would you like to use?\n', + message: chalk.green('Which template would you like to use?\n'), choices: [ { name: 'Template 1 - user can buy, sell & hold datatokens.', value: 1 }, { name: 'Template 2 - assets are purchased with basetokens and the effective supply of datatokens is always zero.', value: 2 }, @@ -162,12 +172,12 @@ export async function interactiveFlow(providerUrl: string): Promise Date: Mon, 7 Oct 2024 15:09:44 +0300 Subject: [PATCH 24/31] Updating to ocean.js 3.4.0 --- package-lock.json | 302 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 288 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 589b1ed..392fe6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@oceanprotocol/contracts": "^2.0.4", - "@oceanprotocol/lib": "^3.3.3", + "@oceanprotocol/lib": "^3.4.0", "cross-fetch": "^3.1.5", "crypto-js": "^4.1.1", "decimal.js": "^10.4.1", @@ -39,6 +39,12 @@ "typescript-eslint": "^7.12.0" } }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==", + "license": "MIT" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1000,17 +1006,153 @@ "node": ">= 8" } }, + "node_modules/@oasisprotocol/deoxysii": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@oasisprotocol/deoxysii/-/deoxysii-0.0.5.tgz", + "integrity": "sha512-a6wYPjk8ALDIiQW/971AKOTSTY1qSdld+Y05F44gVZvlb3FOyHfgbIxXm7CZnUG1A+jK49g5SCWYP+V3/Tc75Q==", + "license": "MIT", + "dependencies": { + "bsaes": "0.0.2", + "uint32": "^0.2.1" + } + }, + "node_modules/@oasisprotocol/sapphire-paratime": { + "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", + "cborg": "1.10.2", + "ethers": "6.10.0", + "tweetnacl": "1.0.3", + "type-fest": "2.19.0" + } + }, + "node_modules/@oasisprotocol/sapphire-paratime/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@oasisprotocol/sapphire-paratime/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@oasisprotocol/sapphire-paratime/node_modules/@types/node": { + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", + "license": "MIT" + }, + "node_modules/@oasisprotocol/sapphire-paratime/node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "license": "MIT" + }, + "node_modules/@oasisprotocol/sapphire-paratime/node_modules/ethers": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.10.0.tgz", + "integrity": "sha512-nMNwYHzs6V1FR3Y4cdfxSQmNgZsRj1RiTU25JwvnJLmyzw9z3SKxNc2XKDuiXXo/v9ds5Mp9m6HBabgYQQ26tA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "1.10.0", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "18.15.13", + "aes-js": "4.0.0-beta.5", + "tslib": "2.4.0", + "ws": "8.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oasisprotocol/sapphire-paratime/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "license": "0BSD" + }, + "node_modules/@oasisprotocol/sapphire-paratime/node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, + "node_modules/@oasisprotocol/sapphire-paratime/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@oasisprotocol/sapphire-paratime/node_modules/ws": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@oceanprotocol/contracts": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-2.0.4.tgz", - "integrity": "sha512-5+SHH0YnpAnOHv9h5xuVLo20tGLLF0utubq3+O25C3NDSaqdm7kvN3sILGxVP1MtUZgJA1yEeUsNYYv0t2jMhw==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-2.2.0.tgz", + "integrity": "sha512-QhewXdtTebycRSZEdkAdvJkSMMnGZyxldlw2eX4VOJto8wymyNdxuhjL/tiaZ5xO7SS5BqURricx9170hfh2kQ==", + "license": "Apache-2.0" }, "node_modules/@oceanprotocol/lib": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-3.3.3.tgz", - "integrity": "sha512-d98X3tBsfbtUJe90wmXI2HcabE3jyY+5628md6vpuItBCnSZgHIQaFvvND2jRThNEmr+4axavylNk/BS3muA5g==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-3.4.0.tgz", + "integrity": "sha512-ekdqW/wfNZ+QMcU66qu8DJLqrq1inB2BXEqRp0tz7N9lZ5S3PN5GMUJ5ifKfBPQOZkjj8zk0wB1yhC5GObpPUQ==", + "license": "Apache-2.0", "dependencies": { - "@oceanprotocol/contracts": "^2.0.3", + "@oasisprotocol/sapphire-paratime": "^1.3.2", + "@oceanprotocol/contracts": "^2.2.0", "cross-fetch": "^4.0.0", "crypto-js": "^4.1.1", "decimal.js": "^10.4.1", @@ -2040,6 +2182,15 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/bsaes": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/bsaes/-/bsaes-0.0.2.tgz", + "integrity": "sha512-iVxJFMOvCUG85sX2UVpZ9IgvH6Jjc5xpd/W8pALvFE7zfCqHkV7hW3M2XZtpg9biPS0K4Eka96bbNNgLohcpgQ==", + "license": "MIT", + "dependencies": { + "uint32": "^0.2.1" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -2203,6 +2354,15 @@ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "peer": true }, + "node_modules/cborg": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-1.10.2.tgz", + "integrity": "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==", + "license": "Apache-2.0", + "bin": { + "cborg": "cli.js" + } + }, "node_modules/chai": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", @@ -8353,6 +8513,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/uint32": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/uint32/-/uint32-0.2.1.tgz", + "integrity": "sha512-d3i8kc/4s1CFW5g3FctmF1Bu2GVXGBMTn82JY2BW0ZtTtI8pRx1YWGPCFBwRF4uYVSJ7ua4y+qYEPqS+x+3w7Q==", + "license": "Do, what You want" + }, "node_modules/ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", @@ -9211,6 +9377,11 @@ } }, "dependencies": { + "@adraffy/ens-normalize": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -9791,17 +9962,100 @@ "fastq": "^1.6.0" } }, + "@oasisprotocol/deoxysii": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@oasisprotocol/deoxysii/-/deoxysii-0.0.5.tgz", + "integrity": "sha512-a6wYPjk8ALDIiQW/971AKOTSTY1qSdld+Y05F44gVZvlb3FOyHfgbIxXm7CZnUG1A+jK49g5SCWYP+V3/Tc75Q==", + "requires": { + "bsaes": "0.0.2", + "uint32": "^0.2.1" + } + }, + "@oasisprotocol/sapphire-paratime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@oasisprotocol/sapphire-paratime/-/sapphire-paratime-1.3.2.tgz", + "integrity": "sha512-98EQ2BrT0942B0VY50PKcJ6xmUAcz71y8OBMizP6oBJIh0+ogw/z3r5z4veJitMXM4zQbh5wOFaS9eOcKWX5FA==", + "requires": { + "@noble/hashes": "1.3.2", + "@oasisprotocol/deoxysii": "0.0.5", + "cborg": "1.10.2", + "ethers": "6.10.0", + "tweetnacl": "1.0.3", + "type-fest": "2.19.0" + }, + "dependencies": { + "@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "requires": { + "@noble/hashes": "1.3.2" + } + }, + "@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==" + }, + "@types/node": { + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" + }, + "aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" + }, + "ethers": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.10.0.tgz", + "integrity": "sha512-nMNwYHzs6V1FR3Y4cdfxSQmNgZsRj1RiTU25JwvnJLmyzw9z3SKxNc2XKDuiXXo/v9ds5Mp9m6HBabgYQQ26tA==", + "requires": { + "@adraffy/ens-normalize": "1.10.0", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "18.15.13", + "aes-js": "4.0.0-beta.5", + "tslib": "2.4.0", + "ws": "8.5.0" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" + }, + "ws": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "requires": {} + } + } + }, "@oceanprotocol/contracts": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-2.0.4.tgz", - "integrity": "sha512-5+SHH0YnpAnOHv9h5xuVLo20tGLLF0utubq3+O25C3NDSaqdm7kvN3sILGxVP1MtUZgJA1yEeUsNYYv0t2jMhw==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-2.2.0.tgz", + "integrity": "sha512-QhewXdtTebycRSZEdkAdvJkSMMnGZyxldlw2eX4VOJto8wymyNdxuhjL/tiaZ5xO7SS5BqURricx9170hfh2kQ==" }, "@oceanprotocol/lib": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-3.3.3.tgz", - "integrity": "sha512-d98X3tBsfbtUJe90wmXI2HcabE3jyY+5628md6vpuItBCnSZgHIQaFvvND2jRThNEmr+4axavylNk/BS3muA5g==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-3.4.0.tgz", + "integrity": "sha512-ekdqW/wfNZ+QMcU66qu8DJLqrq1inB2BXEqRp0tz7N9lZ5S3PN5GMUJ5ifKfBPQOZkjj8zk0wB1yhC5GObpPUQ==", "requires": { - "@oceanprotocol/contracts": "^2.0.3", + "@oasisprotocol/sapphire-paratime": "^1.3.2", + "@oceanprotocol/contracts": "^2.2.0", "cross-fetch": "^4.0.0", "crypto-js": "^4.1.1", "decimal.js": "^10.4.1", @@ -10573,6 +10827,14 @@ "safe-buffer": "^5.1.2" } }, + "bsaes": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/bsaes/-/bsaes-0.0.2.tgz", + "integrity": "sha512-iVxJFMOvCUG85sX2UVpZ9IgvH6Jjc5xpd/W8pALvFE7zfCqHkV7hW3M2XZtpg9biPS0K4Eka96bbNNgLohcpgQ==", + "requires": { + "uint32": "^0.2.1" + } + }, "buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -10687,6 +10949,11 @@ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "peer": true }, + "cborg": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-1.10.2.tgz", + "integrity": "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==" + }, "chai": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", @@ -15238,6 +15505,11 @@ } } }, + "uint32": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/uint32/-/uint32-0.2.1.tgz", + "integrity": "sha512-d3i8kc/4s1CFW5g3FctmF1Bu2GVXGBMTn82JY2BW0ZtTtI8pRx1YWGPCFBwRF4uYVSJ7ua4y+qYEPqS+x+3w7Q==" + }, "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", diff --git a/package.json b/package.json index e96d2f5..8e714d0 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ }, "dependencies": { "@oceanprotocol/contracts": "^2.0.4", - "@oceanprotocol/lib": "^3.3.3", + "@oceanprotocol/lib": "^3.4.0", "cross-fetch": "^3.1.5", "crypto-js": "^4.1.1", "decimal.js": "^10.4.1", From 68fa486a980b43353317f0c34304c4fb5e43844e Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Wed, 9 Oct 2024 08:30:03 +0300 Subject: [PATCH 25/31] Update --- src/helpers.ts | 145 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/src/helpers.ts b/src/helpers.ts index 1328b88..480c159 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -28,6 +28,7 @@ import { ProviderFees, ComputeAlgorithm, LoggerInstance, + Datatoken4 } from "@oceanprotocol/lib"; import { hexlify } from "ethers/lib/utils"; @@ -198,6 +199,150 @@ export async function createAsset( return ddo.id; } + +export async function createSapphireAsset( + name: string, + symbol: string, + owner: Signer, + assetUrl: any, + ddo: any, + providerUrl: string, + config: Config, + aquariusInstance: Aquarius, + datatoken: Datatoken4, + templateIndex: number = 1, + macOsProviderUrl?: string, + encryptDDO: boolean = true, +) { + const { chainId } = await owner.provider.getNetwork(); + const nft = new Nft(owner, chainId); + const nftFactory = new NftFactory(config.nftFactoryAddress, owner); + + 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(), + feeToken: config.oceanTokenAddress, + minter: await owner.getAddress(), + mpFeeAddress: ZERO_ADDRESS, + }; + + 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 { + 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, + 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 = 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 + } + + // Use Datatoken4 to set metadata + await datatoken.setFileObject( + datatokenAddressAsset, + await owner.getAddress(), + true // assuming confidentialEVM is true for Sapphire + ); + + await nft.setMetadata( + nftAddress, + await owner.getAddress(), + 0, + providerUrl, + "", + ethers.utils.hexlify(flags), + metadata, + metadataHash + ); + return ddo.id; +} + + + export async function updateAssetMetadata( owner: Signer, updatedDdo: DDO, From c986e42a97184822f270bb3225761a10a6858590 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Wed, 9 Oct 2024 09:44:34 +0300 Subject: [PATCH 26/31] Update --- src/publishAsset.ts | 79 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 19 deletions(-) diff --git a/src/publishAsset.ts b/src/publishAsset.ts index 5774dce..bc9d031 100644 --- a/src/publishAsset.ts +++ b/src/publishAsset.ts @@ -1,11 +1,14 @@ // src/publishAsset.ts -import { Signer } from 'ethers'; +import { Signer, ethers } from 'ethers'; import { Config, Aquarius, DDO, + Datatoken4 } from '@oceanprotocol/lib'; -import { createAsset, updateAssetMetadata } from './helpers'; // Import helper functions +import { createAsset, createSapphireAsset, updateAssetMetadata } from './helpers'; +import * as sapphire from '@oasisprotocol/sapphire-paratime'; +import ERC20Template4 from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template4.sol/ERC20Template4.json'; export interface PublishAssetParams { title: string; @@ -50,9 +53,9 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c allocated: 0, orders: 0, price: { - value: "0" + value: params.isCharged ? params.price : "0" } - }, + }, services: [ { id: 'access', @@ -61,7 +64,7 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c files: '', datatokenAddress: '0x0', // Will be updated after creating asset serviceEndpoint: params.providerUrl, - timeout: 0, + timeout: params.timeout, }, ], nft: { @@ -79,21 +82,59 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c const assetUrl = { nftAddress: '0x0', // Will be updated after creating asset datatokenAddress: '0x0', // Will be updated after creating asset - files: [{ type: 'url', url: params.assetLocation, method: 'GET' }], + files: [{ type: params.storageType, url: params.assetLocation, method: 'GET' }], }; - // Create the asset using the helper function - const did = await createAsset( - params.title, - 'DATATOKEN', // Assuming a standard symbol for now - signer, - assetUrl, - metadata, - params.providerUrl, - config, - aquarius, - 1 - ); + let did: string; + + if (params.chainId === 23294) { + // Oasis Sapphire + console.log('Publishing to Oasis Sapphire network...'); + + const wrappedSigner = sapphire.wrap(signer); + + const filesObject = [ + { + url: params.assetLocation, + contentType: 'text/plain', // Adjust this based on your asset type + encoding: 'UTF-8' + } + ]; + + const datatoken = new Datatoken4( + wrappedSigner, + ethers.utils.toUtf8Bytes(JSON.stringify(filesObject)), + params.chainId, + config, + ERC20Template4.abi + ); + + did = await createSapphireAsset( + params.title, + 'DATATOKEN', // Assuming a standard symbol for now + wrappedSigner, + assetUrl, + metadata, + params.providerUrl, + config, + aquarius, + datatoken, + params.template || 1 + ); + } else { + // Other networks + did = await createAsset( + params.title, + 'DATATOKEN', // Assuming a standard symbol for now + signer, + assetUrl, + metadata, + params.providerUrl, + config, + aquarius, + params.template || 1 + ); + } console.log(`Asset successfully published with DID: ${did}`); @@ -114,4 +155,4 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c } catch (error) { console.error('Error publishing asset:', error); } -} +} \ No newline at end of file From f129fe6cdae09d763643c76583392e58485aea88 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Wed, 9 Oct 2024 10:07:10 +0300 Subject: [PATCH 27/31] Updating to use Ocean Node url in environmental variables --- README.md | 10 ++++++++-- src/commands.ts | 10 +++++----- src/publishAsset.ts | 4 ++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 148c2c1..a263090 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,19 @@ export MNEMONIC="XXXX" export RPC='XXXX' ``` -- Optional set metadataCache URL if you want to use a custom Aquarius version instead of the default one. +- Set an Ocean Node URL + +``` +export NODE_URL='XXXX' +``` + +- Optional set metadataCache URL if you want to use a custom Aquarius version instead of the default one. This will not be used if you have set an Ocean Node url. ``` export AQUARIUS_URL='XXXX' ``` -- Optional set Provider URL if you want to use a custom Provider version instead of the default one. +- Optional set Provider URL if you want to use a custom Provider version instead of the default one. This will not be used if you have set an Ocean Node url. ``` export PROVIDER_URL='XXXX' diff --git a/src/commands.ts b/src/commands.ts index 3c85936..89abc21 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -36,9 +36,9 @@ export class Commands { constructor(signer: Signer, network: string | number, config?: Config) { this.signer = signer; this.config = config || new ConfigHelper().getConfig(network); - this.providerUrl = process.env.PROVIDER_URL || this.config.providerUri; + this.providerUrl = process.env.NODE_URL || process.env.PROVIDER_URL || this.config.providerUri; if ( - !process.env.PROVIDER_URL && + !process.env.PROVIDER_URL && !process.env.NODE_URL && this.config.chainId === 8996 && os.type() === "Darwin" ) { @@ -48,18 +48,18 @@ export class Commands { this.macOsProviderUrl && console.log(" -> MacOS provider url :", this.macOsProviderUrl); if ( - !process.env.AQUARIUS_URL && + !process.env.AQUARIUS_URL && !process.env.NODE_URL && this.config.chainId === 8996 && os.type() === "Darwin" ) { this.config.metadataCacheUri = "http://127.0.0.1:5000"; } this.aquarius = new Aquarius( - process.env.AQUARIUS_URL || this.config.metadataCacheUri + process.env.NODE_URL || process.env.AQUARIUS_URL || this.config.metadataCacheUri ); console.log( "Using Aquarius :", - process.env.AQUARIUS_URL || this.config.metadataCacheUri + process.env.NODE_URL || process.env.AQUARIUS_URL || this.config.metadataCacheUri ); } diff --git a/src/publishAsset.ts b/src/publishAsset.ts index bc9d031..f8fa4d6 100644 --- a/src/publishAsset.ts +++ b/src/publishAsset.ts @@ -111,7 +111,7 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c did = await createSapphireAsset( params.title, - 'DATATOKEN', // Assuming a standard symbol for now + 'OCEAN-NFT', wrappedSigner, assetUrl, metadata, @@ -125,7 +125,7 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c // Other networks did = await createAsset( params.title, - 'DATATOKEN', // Assuming a standard symbol for now + 'OCEAN-NFT', signer, assetUrl, metadata, From 0734f9630f4fd79c1a354e8793b6bfef3679979b Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Wed, 9 Oct 2024 12:39:10 +0300 Subject: [PATCH 28/31] Updating to ocean.js 3.4.1 + other fixes --- package-lock.json | 14 +- package.json | 2 +- src/helpers.ts | 317 +++++++++++++++++++++++------------------ src/interactiveFlow.ts | 4 +- src/publishAsset.ts | 33 ++--- 5 files changed, 197 insertions(+), 173 deletions(-) diff --git a/package-lock.json b/package-lock.json index 392fe6c..6506ad5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@oceanprotocol/contracts": "^2.0.4", - "@oceanprotocol/lib": "^3.4.0", + "@oceanprotocol/lib": "^3.4.1", "cross-fetch": "^3.1.5", "crypto-js": "^4.1.1", "decimal.js": "^10.4.1", @@ -1146,9 +1146,9 @@ "license": "Apache-2.0" }, "node_modules/@oceanprotocol/lib": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-3.4.0.tgz", - "integrity": "sha512-ekdqW/wfNZ+QMcU66qu8DJLqrq1inB2BXEqRp0tz7N9lZ5S3PN5GMUJ5ifKfBPQOZkjj8zk0wB1yhC5GObpPUQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-3.4.1.tgz", + "integrity": "sha512-DhdNI6CF8O7ylTWNqLEdcqP9iWKFo9FDayfJvhNTL4mgWy2N1E3qzFMbsH2NAHN3fo+PbJ2KTfedpFt24r0LhA==", "license": "Apache-2.0", "dependencies": { "@oasisprotocol/sapphire-paratime": "^1.3.2", @@ -10050,9 +10050,9 @@ "integrity": "sha512-QhewXdtTebycRSZEdkAdvJkSMMnGZyxldlw2eX4VOJto8wymyNdxuhjL/tiaZ5xO7SS5BqURricx9170hfh2kQ==" }, "@oceanprotocol/lib": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-3.4.0.tgz", - "integrity": "sha512-ekdqW/wfNZ+QMcU66qu8DJLqrq1inB2BXEqRp0tz7N9lZ5S3PN5GMUJ5ifKfBPQOZkjj8zk0wB1yhC5GObpPUQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-3.4.1.tgz", + "integrity": "sha512-DhdNI6CF8O7ylTWNqLEdcqP9iWKFo9FDayfJvhNTL4mgWy2N1E3qzFMbsH2NAHN3fo+PbJ2KTfedpFt24r0LhA==", "requires": { "@oasisprotocol/sapphire-paratime": "^1.3.2", "@oceanprotocol/contracts": "^2.2.0", diff --git a/package.json b/package.json index 8e714d0..70163ae 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ }, "dependencies": { "@oceanprotocol/contracts": "^2.0.4", - "@oceanprotocol/lib": "^3.4.0", + "@oceanprotocol/lib": "^3.4.1", "cross-fetch": "^3.1.5", "crypto-js": "^4.1.1", "decimal.js": "^10.4.1", diff --git a/src/helpers.ts b/src/helpers.ts index 480c159..eae49d6 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -4,8 +4,9 @@ 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, @@ -201,148 +202,186 @@ export async function createAsset( export async function createSapphireAsset( - name: string, - symbol: string, - owner: Signer, - assetUrl: any, - ddo: any, - providerUrl: string, - config: Config, - aquariusInstance: Aquarius, - datatoken: Datatoken4, - templateIndex: number = 1, - macOsProviderUrl?: string, - encryptDDO: boolean = true, + name: string, + symbol: string, + owner: Signer, + assetUrl: any, + ddo: any, + providerUrl: string, + config: Config, + aquariusInstance: Aquarius, + templateIndex: number = 1, + macOsProviderUrl?: string, + encryptDDO: boolean = true, ) { - const { chainId } = await owner.provider.getNetwork(); - const nft = new Nft(owner, chainId); - const nftFactory = new NftFactory(config.nftFactoryAddress, owner); - - 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(), - feeToken: config.oceanTokenAddress, - minter: await owner.getAddress(), - mpFeeAddress: ZERO_ADDRESS, - }; - - 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 { - 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, - 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 = 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 - } - - // Use Datatoken4 to set metadata - await datatoken.setFileObject( - datatokenAddressAsset, - await owner.getAddress(), - true // assuming confidentialEVM is true for Sapphire - ); - - await nft.setMetadata( - nftAddress, - await owner.getAddress(), - 0, - providerUrl, - "", - ethers.utils.hexlify(flags), - metadata, - metadataHash - ); - return ddo.id; + const { chainId } = await owner.provider.getNetwork(); + console.log("Chain ID: ", chainId); + const nftFactory = new NftFactory(config.nftFactoryAddress, owner); + + // Wrap the signer for Sapphire + const wrappedSigner = sapphire.wrap(owner); + + // Create Access List Factory + const accessListFactory = new AccesslistFactory(config.accessListFactory, wrappedSigner, chainId); + + // Create Allow List + const allowListAddress = await accessListFactory.deployAccessListContract( + 'AllowList', + 'ALLOW', + ['https://oceanprotocol.com/nft/'], + false, + await owner.getAddress(), + [await owner.getAddress(), ZERO_ADDRESS] + ); + console.log("Allow List Address: ", allowListAddress); + + ddo.chainId = parseInt(chainId.toString(10)); + console.log("DDO Chain ID: ", ddo.chainId); + 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(), + feeToken: config.oceanTokenAddress, + minter: await owner.getAddress(), + mpFeeAddress: ZERO_ADDRESS, + }; + + 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 { + 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, + marketFee: "0", + allowedConsumer: await owner.getAddress(), + withMint: true, + }; + + bundleNFT = await nftFactory.createNftWithDatatokenWithFixedRate( + nftParamsAsset, + datatokenParams, + fixedPriceParams + ); + } + + const trxReceipt = await bundleNFT.wait(); + const nftCreatedEvent = getEventFromTx(trxReceipt, "NFTCreated"); + const tokenCreatedEvent = getEventFromTx(trxReceipt, "TokenCreated"); + + const nftAddress = nftCreatedEvent.args.newTokenAddress; + console.log("NFT Address: ", nftAddress); + const datatokenAddressAsset = tokenCreatedEvent.args.newTokenAddress; + console.log("Datatoken Address: ", datatokenAddressAsset) + + assetUrl.datatokenAddress = datatokenAddressAsset; + assetUrl.nftAddress = nftAddress; + ddo.services[0].files = 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 + } + + // Use Nft class to set metadata on the NFT + const nft = new Nft(wrappedSigner, chainId); +console.log("setting metadata") + // Set metadata using Nft + await nft.setMetadata( + nftAddress, + await owner.getAddress(), + 0, + providerUrl, + "", + ethers.utils.hexlify(flags), + metadata, + metadataHash + ); + console.log('metadata updated') + + // Use Datatoken4 for file object + console.log('Use Datatoken4 for file object') + const datatoken = new Datatoken4( + wrappedSigner, + ethers.utils.toUtf8Bytes(JSON.stringify(assetUrl.files)), + chainId, + config + ); + + // Set file object + console.log('Set file object') + await datatoken.setFileObject(datatokenAddressAsset, await wrappedSigner.getAddress()); + + // Set allow list for the datatoken + console.log('Set allow list for the datatoken') + await datatoken.setAllowListContract( + datatokenAddressAsset, + allowListAddress, + await wrappedSigner.getAddress() + ); + + console.log('returning ddo.id') + return ddo.id; } - export async function updateAssetMetadata( owner: Signer, updatedDdo: DDO, diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index d84d899..4b8a686 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -135,7 +135,7 @@ export async function interactiveFlow(providerUrl: string): Promise choice.name === value).value; } @@ -143,7 +143,7 @@ export async function interactiveFlow(providerUrl: string): Promise({ type: 'select', name: 'template', diff --git a/src/publishAsset.ts b/src/publishAsset.ts index f8fa4d6..91c500f 100644 --- a/src/publishAsset.ts +++ b/src/publishAsset.ts @@ -1,14 +1,12 @@ // src/publishAsset.ts -import { Signer, ethers } from 'ethers'; +import { Signer } from 'ethers'; import { Config, Aquarius, - DDO, - Datatoken4 + DDO } from '@oceanprotocol/lib'; import { createAsset, createSapphireAsset, updateAssetMetadata } from './helpers'; import * as sapphire from '@oasisprotocol/sapphire-paratime'; -import ERC20Template4 from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template4.sol/ERC20Template4.json'; export interface PublishAssetParams { title: string; @@ -32,6 +30,8 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c const aquarius = new Aquarius(config.metadataCacheUri); + console.log('asset timeout: ', params.timeout) + // Prepare initial metadata for the asset const metadata: DDO = { '@context': ['https://w3id.org/did/v1'], @@ -64,7 +64,7 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c files: '', datatokenAddress: '0x0', // Will be updated after creating asset serviceEndpoint: params.providerUrl, - timeout: params.timeout, + timeout: 0, }, ], nft: { @@ -87,27 +87,11 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c let did: string; - if (params.chainId === 23294) { + if (params.chainId === 23295) { // Oasis Sapphire console.log('Publishing to Oasis Sapphire network...'); const wrappedSigner = sapphire.wrap(signer); - - const filesObject = [ - { - url: params.assetLocation, - contentType: 'text/plain', // Adjust this based on your asset type - encoding: 'UTF-8' - } - ]; - - const datatoken = new Datatoken4( - wrappedSigner, - ethers.utils.toUtf8Bytes(JSON.stringify(filesObject)), - params.chainId, - config, - ERC20Template4.abi - ); did = await createSapphireAsset( params.title, @@ -118,8 +102,9 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c params.providerUrl, config, aquarius, - datatoken, - params.template || 1 + params.template || 1, + undefined, // macOsProviderUrl + true // encryptDDO ); } else { // Other networks From 55127c7751825af408d2d40933b135b9718e3e8e Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Wed, 9 Oct 2024 13:17:39 +0300 Subject: [PATCH 29/31] Removing logs --- src/helpers.ts | 13 ++----------- src/interactiveFlow.ts | 39 ++++++++++++++++++--------------------- src/publishAsset.ts | 10 +++------- 3 files changed, 23 insertions(+), 39 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index eae49d6..8e81890 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -215,7 +215,6 @@ export async function createSapphireAsset( encryptDDO: boolean = true, ) { const { chainId } = await owner.provider.getNetwork(); - console.log("Chain ID: ", chainId); const nftFactory = new NftFactory(config.nftFactoryAddress, owner); // Wrap the signer for Sapphire @@ -233,10 +232,8 @@ export async function createSapphireAsset( await owner.getAddress(), [await owner.getAddress(), ZERO_ADDRESS] ); - console.log("Allow List Address: ", allowListAddress); ddo.chainId = parseInt(chainId.toString(10)); - console.log("DDO Chain ID: ", ddo.chainId); const nftParamsAsset: NftCreateData = { name, symbol, @@ -301,9 +298,9 @@ export async function createSapphireAsset( const tokenCreatedEvent = getEventFromTx(trxReceipt, "TokenCreated"); const nftAddress = nftCreatedEvent.args.newTokenAddress; - console.log("NFT Address: ", nftAddress); + console.log("NFT publish with address: ", nftAddress); const datatokenAddressAsset = tokenCreatedEvent.args.newTokenAddress; - console.log("Datatoken Address: ", datatokenAddressAsset) + console.log("Datatoken published with address: ", datatokenAddressAsset) assetUrl.datatokenAddress = datatokenAddressAsset; assetUrl.nftAddress = nftAddress; @@ -342,7 +339,6 @@ export async function createSapphireAsset( // Use Nft class to set metadata on the NFT const nft = new Nft(wrappedSigner, chainId); -console.log("setting metadata") // Set metadata using Nft await nft.setMetadata( nftAddress, @@ -354,10 +350,8 @@ console.log("setting metadata") metadata, metadataHash ); - console.log('metadata updated') // Use Datatoken4 for file object - console.log('Use Datatoken4 for file object') const datatoken = new Datatoken4( wrappedSigner, ethers.utils.toUtf8Bytes(JSON.stringify(assetUrl.files)), @@ -366,18 +360,15 @@ console.log("setting metadata") ); // Set file object - console.log('Set file object') await datatoken.setFileObject(datatokenAddressAsset, await wrappedSigner.getAddress()); // Set allow list for the datatoken - console.log('Set allow list for the datatoken') await datatoken.setAllowListContract( datatokenAddressAsset, allowListAddress, await wrappedSigner.getAddress() ); - console.log('returning ddo.id') return ddo.id; } diff --git a/src/interactiveFlow.ts b/src/interactiveFlow.ts index 4b8a686..7f092a5 100644 --- a/src/interactiveFlow.ts +++ b/src/interactiveFlow.ts @@ -23,7 +23,7 @@ export async function interactiveFlow(providerUrl: string): Promise>([ + const basicAnswers = await prompt>([ { type: 'input', name: 'title', @@ -48,25 +48,23 @@ export async function interactiveFlow(providerUrl: string): Promise choice.name === value).value; + } + }, ]); - // Timeout prompt - const { timeout } = await prompt<{ timeout: number }>({ - type: 'select', - name: 'timeout', - message: chalk.green('After purchasing your asset, how long should the consumer be allowed to access it for?\n'), - choices: [ - {name: 'Forever', value: 0}, - {name: '1 day', value: 86400}, - {name: '1 week', value: 604800}, - {name: '1 month', value: 2592000}, - {name: '1 year', value: 31536000} - ], - result(value) { - return this.choices.find(choice => choice.name === value).value; - } - }); - // Storage type prompt const { storageType } = await prompt<{ storageType: PublishAssetParams['storageType'] }>({ type: 'select', @@ -135,7 +133,7 @@ export async function interactiveFlow(providerUrl: string): Promise choice.name === value).value; } @@ -143,7 +141,7 @@ export async function interactiveFlow(providerUrl: string): Promise({ type: 'select', name: 'template', @@ -162,7 +160,6 @@ export async function interactiveFlow(providerUrl: string): Promise Date: Mon, 14 Oct 2024 14:52:06 +0300 Subject: [PATCH 30/31] Updating to remove seperate publish Sapphire function --- src/helpers.ts | 210 ++++++++------------------------------------ src/publishAsset.ts | 29 +----- 2 files changed, 42 insertions(+), 197 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index 8e81890..72667e2 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -84,6 +84,26 @@ export async function createAsset( const nft = new Nft(owner, chainId); const nftFactory = new NftFactory(config.nftFactoryAddress, owner); + let wrappedSigner + let allowListAddress + if(templateIndex === 4){ + // Wrap the signer for Sapphire + wrappedSigner = sapphire.wrap(owner); + + // Create Access List Factory + const accessListFactory = new AccesslistFactory(config.accessListFactory, wrappedSigner, chainId); + + // Create Allow List + allowListAddress = await accessListFactory.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, @@ -197,182 +217,30 @@ export async function createAsset( metadata, metadataHash ); - return ddo.id; -} - - -export async function createSapphireAsset( - name: string, - symbol: string, - owner: Signer, - assetUrl: any, - ddo: any, - providerUrl: string, - config: Config, - aquariusInstance: Aquarius, - templateIndex: number = 1, - macOsProviderUrl?: string, - encryptDDO: boolean = true, -) { - const { chainId } = await owner.provider.getNetwork(); - const nftFactory = new NftFactory(config.nftFactoryAddress, owner); - - // Wrap the signer for Sapphire - const wrappedSigner = sapphire.wrap(owner); - - // Create Access List Factory - const accessListFactory = new AccesslistFactory(config.accessListFactory, wrappedSigner, chainId); - - // Create Allow List - const allowListAddress = await accessListFactory.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(), - feeToken: config.oceanTokenAddress, - minter: await owner.getAddress(), - mpFeeAddress: ZERO_ADDRESS, - }; - - 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 { - 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, - marketFee: "0", - allowedConsumer: await owner.getAddress(), - withMint: true, - }; - - bundleNFT = await nftFactory.createNftWithDatatokenWithFixedRate( - nftParamsAsset, - datatokenParams, - fixedPriceParams - ); - } - const trxReceipt = await bundleNFT.wait(); - const nftCreatedEvent = getEventFromTx(trxReceipt, "NFTCreated"); - const tokenCreatedEvent = getEventFromTx(trxReceipt, "TokenCreated"); - - const nftAddress = nftCreatedEvent.args.newTokenAddress; - console.log("NFT publish with address: ", nftAddress); - const datatokenAddressAsset = tokenCreatedEvent.args.newTokenAddress; - console.log("Datatoken published with address: ", datatokenAddressAsset) - - assetUrl.datatokenAddress = datatokenAddressAsset; - assetUrl.nftAddress = nftAddress; - ddo.services[0].files = 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 - } - - // Use Nft class to set metadata on the NFT - const nft = new Nft(wrappedSigner, chainId); - // Set metadata using Nft - await nft.setMetadata( - nftAddress, - await owner.getAddress(), - 0, - providerUrl, - "", - ethers.utils.hexlify(flags), - metadata, - metadataHash - ); - - // 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() - ); + 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 ddo.id; } + export async function updateAssetMetadata( owner: Signer, updatedDdo: DDO, diff --git a/src/publishAsset.ts b/src/publishAsset.ts index 7f970f4..57a08ce 100644 --- a/src/publishAsset.ts +++ b/src/publishAsset.ts @@ -5,8 +5,7 @@ import { Aquarius, DDO } from '@oceanprotocol/lib'; -import { createAsset, createSapphireAsset, updateAssetMetadata } from './helpers'; -import * as sapphire from '@oasisprotocol/sapphire-paratime'; +import { createAsset, updateAssetMetadata } from './helpers'; export interface PublishAssetParams { title: string; @@ -81,30 +80,8 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c files: [{ type: params.storageType, url: params.assetLocation, method: 'GET' }], }; - let did: string; - - if (params.chainId === 23295 || params.chainId === 23294) { - // Oasis Sapphire - console.log('Publishing to Oasis Sapphire network...'); - - const wrappedSigner = sapphire.wrap(signer); - - did = await createSapphireAsset( - params.title, - 'OCEAN-NFT', - wrappedSigner, - assetUrl, - metadata, - params.providerUrl, - config, - aquarius, - params.template || 1, - undefined, // macOsProviderUrl - true // encryptDDO - ); - } else { // Other networks - did = await createAsset( + const did = await createAsset( params.title, 'OCEAN-NFT', signer, @@ -115,7 +92,7 @@ export async function publishAsset(params: PublishAssetParams, signer: Signer, c aquarius, params.template || 1 ); - } + console.log(`Asset successfully published with DID: ${did}`); From f4a84111d2731be9a603009d15ac4fd468fe04d9 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Tue, 15 Oct 2024 14:23:29 +0300 Subject: [PATCH 31/31] Setting files as empty string for oasis sapphire assets --- src/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers.ts b/src/helpers.ts index 1620389..4e38a11 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -174,7 +174,7 @@ export async function createAsset( // create the files encrypted string assetUrl.datatokenAddress = datatokenAddressAsset; assetUrl.nftAddress = nftAddress; - ddo.services[0].files = await ProviderInstance.encrypt( + ddo.services[0].files = templateIndex === 4 ? '' : await ProviderInstance.encrypt( assetUrl, chainId, macOsProviderUrl || providerUrl