diff --git a/CHANGELOG.md b/CHANGELOG.md index 135f8b1e9..da6b5a68b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1466,7 +1466,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). > 2 May 2022 -- add aave service agreement to ddo when creating nft721 asset [`#248`](https://github.com/nevermined-io/sdk-js/pull/248) +- add aave service agreement to ddo when creating nft721 asset [`#248`](https://github.com/nevermined-io/sdk-js/pull/248) - Adding v0.19.21 Changelog updates [`c93cdc5`](https://github.com/nevermined-io/sdk-js/commit/c93cdc55f139a43db4130ccb0f80924d2645a931) #### [v0.19.21](https://github.com/nevermined-io/sdk-js/compare/v0.19.20...v0.19.21) @@ -1729,7 +1729,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). > 18 January 2022 -- Removing not used parameter [`#186`](https://github.com/nevermined-io/sdk-js/pull/186) +- Removing not used parameter [`#186`](https://github.com/nevermined-io/sdk-js/pull/186) - [wip] Create agreement+pay in one transaction [`#183`](https://github.com/nevermined-io/sdk-js/pull/183) - Adapting to contracts `v1.3.3` [`#177`](https://github.com/nevermined-io/sdk-js/pull/177) - Adding v0.17.2 Changelog updates [`5eddda4`](https://github.com/nevermined-io/sdk-js/commit/5eddda43954e013e6e6f7344c9a877d801aacb5c) @@ -2235,7 +2235,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - Feature/sync develop [`#268`](https://github.com/nevermined-io/sdk-js/pull/268) - Quick fix for non-eip1559 networks [`#266`](https://github.com/nevermined-io/sdk-js/pull/266) - integrate Permissions and refactor search query [`#264`](https://github.com/nevermined-io/sdk-js/pull/264) -- add aave service agreement to ddo when creating nft721 asset [`#248`](https://github.com/nevermined-io/sdk-js/pull/248) +- add aave service agreement to ddo when creating nft721 asset [`#248`](https://github.com/nevermined-io/sdk-js/pull/248) - Get the keeper version from the artifacts instead of package version [`#244`](https://github.com/nevermined-io/sdk-js/pull/244) - replace `metadata-api` url by `marketplace-api` and sort type [`#243`](https://github.com/nevermined-io/sdk-js/pull/243) - fixing issues with BigNumbers [`#246`](https://github.com/nevermined-io/sdk-js/pull/246) @@ -2274,7 +2274,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - Feature/190 add did to sec markets [`#191`](https://github.com/nevermined-io/sdk-js/pull/191) - Adding utility methods for getting nft token uri [`#189`](https://github.com/nevermined-io/sdk-js/pull/189) - Lint ... [`#187`](https://github.com/nevermined-io/sdk-js/pull/187) -- Removing not used parameter [`#186`](https://github.com/nevermined-io/sdk-js/pull/186) +- Removing not used parameter [`#186`](https://github.com/nevermined-io/sdk-js/pull/186) - [wip] Create agreement+pay in one transaction [`#183`](https://github.com/nevermined-io/sdk-js/pull/183) - Adapting to contracts `v1.3.3` [`#177`](https://github.com/nevermined-io/sdk-js/pull/177) - Correct typo in route [`#184`](https://github.com/nevermined-io/sdk-js/pull/184) diff --git a/integration/external/NVMAppAPI.staging.test.ts b/integration/external/NVMAppAPI.staging.test.ts new file mode 100644 index 000000000..bc2ec4c71 --- /dev/null +++ b/integration/external/NVMAppAPI.staging.test.ts @@ -0,0 +1,284 @@ +import chai, { assert } from 'chai' +import chaiAsPromised from 'chai-as-promised' + +import { + AssetPrice, + ResourceAuthentication, + SubscriptionType, + convertEthersV6SignerToAccountSigner, + makeAccounts, +} from '../../src' +import TestContractHandler from '../../test/keeper/TestContractHandler' +import { NVMAppEnvironments, NvmApp } from '../../src/nevermined/NvmApp' +import { NvmAppMetadata } from '../../src/ddo/NvmAppMetadata' +import { ethers, isAddress } from 'ethers' +import { ZeroDevAccountSigner, ZeroDevEthersProvider } from '@zerodev/sdk' +import { AppDeploymentStaging } from '../../src/nevermined/resources/AppNetworks' + +chai.use(chaiAsPromised) + +// Execute first: +// ./scripts/download-artifacts.sh v3.5.6 arbitrum-sepolia public +// export PROJECT_ID=your_project_id +describe('NVM App API', () => { + let nvmAppPublisher: NvmApp + let nvmAppSubscriber: NvmApp + + let subscriptionNFTAddress: string + let subscriptionDid: string + let agentDid: string + let datasetDid: string + let agreementId + + const projectId = process.env.PROJECT_ID! + let zerodevProvider: ZeroDevEthersProvider<'ECDSA'> + let accountSigner: ZeroDevAccountSigner<'ECDSA'> + let publisherAddress: string + let subscriptionPrice: AssetPrice + let subscriptionPriceWithFees: AssetPrice + + // Agent/Service test configuration + const SERVICE_ENDPOINT = process.env.SERVICE_ENDPOINT || 'http://127.0.0.1:3000' + const OPEN_PATH = process.env.OPEN_PATH || '/openapi.json' + const OPEN_ENDPOINT = process.env.OPEN_ENDPOINT || `${SERVICE_ENDPOINT}${OPEN_PATH}` + const AUTHORIZATION_TYPE = (process.env.AUTHORIZATION_TYPE || + 'oauth') as ResourceAuthentication['type'] + const AUTHORIZATION_TOKEN = process.env.AUTHORIZATION_TOKEN || 'new_authorization_token' + const AUTHORIZATION_USER = process.env.AUTHORIZATION_USER || 'user' + const AUTHORIZATION_PASSWORD = process.env.AUTHORIZATION_PASSWORD || 'password' + + before(async () => { + const owner = ethers.Wallet.createRandom() + zerodevProvider = await ZeroDevEthersProvider.init('ECDSA', { + projectId, + owner: convertEthersV6SignerToAccountSigner(owner), + }) + + const contractABI = await TestContractHandler.getABI( + `NFT1155SubscriptionUpgradeable.arbitrum-sepolia`, + './artifacts/', + ) + + subscriptionNFTAddress = contractABI.address + console.debug(`Using ERC-1155 Subscription NFT on address: ${subscriptionNFTAddress}`) + + accountSigner = zerodevProvider.getAccountSigner() + publisherAddress = await accountSigner.getAddress() + + // Using USDC token address + // WARN: Make sure the subscriber account has balance to pay the gas and the subscription + subscriptionPrice = new AssetPrice(publisherAddress, 1000n).setTokenAddress( + new AppDeploymentStaging().tokenAddress, + ) + }) + + describe('As a PUBLISHER', () => { + it('I want to search content from the app', async () => { + nvmAppPublisher = await NvmApp.getInstance(NVMAppEnvironments.Staging) + const results = await nvmAppPublisher.search.query({}) + console.log(JSON.stringify(results.totalResults)) + + assert.isDefined(results) + }) + + it('Get the default configuration used', async () => { + const appConfig = nvmAppPublisher.config + assert.isDefined(appConfig) + assert.equal(appConfig.marketplaceUri, 'https://marketplace-api.staging.nevermined.app') + }) + + it('Overwrite the default config with some parameters', async () => { + assert.notEqual(nvmAppPublisher.config.artifactsFolder, './artifacts') + nvmAppPublisher = await NvmApp.getInstance(NVMAppEnvironments.Staging, { + artifactsFolder: './artifacts', + }) + assert.equal(nvmAppPublisher.config.artifactsFolder, './artifacts') + }) + + it('I want to connect my account', async () => { + assert.isFalse(nvmAppPublisher.isWeb3Connected()) + + await nvmAppPublisher.connect(accountSigner) + + assert.isTrue(nvmAppPublisher.isWeb3Connected()) + assert.isTrue(nvmAppPublisher.getLoginCredentials().length > 0) + }) + + it('I can get the network fees', async () => { + const networkFees = nvmAppPublisher.networkFees + assert.isDefined(networkFees) + console.log(`Network Fees: ${JSON.stringify(networkFees)}`) + assert.isTrue(isAddress(networkFees.receiver)) + assert.isTrue(networkFees.fee > 0) + }) + + it('I can calculate and include network fees', async () => { + // console.log(`AssetPrice object: ${subscriptionPrice.toString()}`) + + // assert.isFalse(nvmApp.isNetworkFeeIncluded(subscriptionPrice)) + + subscriptionPriceWithFees = nvmAppPublisher.addNetworkFee(subscriptionPrice) + console.log(`Asset Price with fees: ${subscriptionPriceWithFees.toString()}`) + + assert.isTrue(nvmAppPublisher.isNetworkFeeIncluded(subscriptionPriceWithFees)) + }) + + it('I want to create a time subscription', async () => { + const timeSubscriptionMetadata = NvmAppMetadata.getTimeSubscriptionMetadataTemplate( + 'NVM App Time only Subscription test', + 'Nevermined', + 'hours', + ) + timeSubscriptionMetadata.additionalInformation.customData = { + subscriptionLimitType: SubscriptionType.Time, + dateMeasure: 'hours', + } + + const ddo = await nvmAppPublisher.createTimeSubscription( + timeSubscriptionMetadata, + subscriptionPriceWithFees, + 100, // 100 blocks duration + ) + + assert.isDefined(ddo) + const ddoFound = await nvmAppPublisher.search.byDID(ddo.id) + assert.equal(ddo.id, ddoFound.id) + }) + + it('I want to create a credits subscription', async () => { + const creditsSubscriptionMetadata = NvmAppMetadata.getCreditsSubscriptionMetadataTemplate( + 'NVM App Credits Subscription test', + 'Nevermined', + ) + creditsSubscriptionMetadata.additionalInformation.customData = { + subscriptionLimitType: SubscriptionType.Credits, + } + + const ddo = await nvmAppPublisher.createCreditsSubscription( + creditsSubscriptionMetadata, + subscriptionPriceWithFees, + 50n, // number of credits given to the subscribers + ) + + assert.isDefined(ddo) + const ddoFound = await nvmAppPublisher.search.byDID(ddo.id) + assert.equal(ddo.id, ddoFound.id) + subscriptionDid = ddo.id + }) + + it('I want to register an Agent', async () => { + const agentMetadata = NvmAppMetadata.getServiceMetadataTemplate( + 'Nevermined Ageeeent', + 'Nevermined', + [ + { + GET: `${SERVICE_ENDPOINT}/(.*)`, + }, + ], + [OPEN_ENDPOINT], + OPEN_ENDPOINT, + 'RESTful', + AUTHORIZATION_TYPE, + AUTHORIZATION_TOKEN, + AUTHORIZATION_USER, + AUTHORIZATION_PASSWORD, + true, + ) + + const ddo = await nvmAppPublisher.registerServiceAsset( + agentMetadata, + subscriptionDid, + // We are gonna configure the agent usage costs in a dynamic manner: + // The cost in credits for every succesful query to the agent will be between 1 and 5 credits being 2 credits the default cost + 2n, // default cost in credits for every succesful query to the agent + 1n, // min amount of credits to be consumed + 5n, // max amount of credits to be consumed + ) + + assert.isDefined(ddo) + const ddoFound = await nvmAppPublisher.search.byDID(ddo.id) + assert.equal(ddo.id, ddoFound.id) + agentDid = ddo.id + }) + + it('I want to register a Dataset', async () => { + const datasetMetadata = NvmAppMetadata.getFileMetadataTemplate( + 'NVM App Dataset test', + 'Nevermined', + ) + datasetMetadata.main.files = [ + { + index: 0, + contentType: 'application/json', + name: 'ddo-example.json', + url: 'https://storage.googleapis.com/nvm-static-assets/files/ci/ddo-example.json', + }, + { + index: 1, + contentType: 'text/plain', + name: 'README.md', + url: 'https://storage.googleapis.com/nvm-static-assets/files/ci/README.md', + }, + ] + + const ddo = await nvmAppPublisher.registerFileAsset( + datasetMetadata, + subscriptionDid, + 1n, // every file download costs 1 credit to the subscriber + ) + + assert.isDefined(ddo) + const ddoFound = await nvmAppPublisher.search.byDID(ddo.id) + assert.equal(ddo.id, ddoFound.id) + datasetDid = ddo.id + }) + }) + + describe('As a SUBSCRIBER', () => { + it('I want to connect as subscriber', async () => { + nvmAppSubscriber = await NvmApp.getInstance(NVMAppEnvironments.Staging, { + artifactsFolder: './artifacts', + }) + const appConfig = nvmAppSubscriber.config + appConfig.accounts = makeAccounts(process.env.SEED_WORDS) + + const subscriberAddress = await appConfig.accounts[0].getAddress() + await nvmAppSubscriber.connect(subscriberAddress, appConfig) + + assert.isTrue(nvmAppSubscriber.isWeb3Connected()) + }) + + it('I want to order a subscription', async () => { + const orderResult = await nvmAppSubscriber.orderSubscription(subscriptionDid) + assert.isDefined(orderResult) + assert.isTrue(orderResult.success) + assert.isTrue(orderResult.agreementId.length > 0) + agreementId = orderResult.agreementId + }) + + it('I want to get the token giving access to a remote agent', async () => { + const token = await nvmAppSubscriber.getServiceAccessToken(agentDid) + console.log(`Token: ${JSON.stringify(token)}`) + assert.isDefined(token) + assert.isTrue(token.accessToken.length > 0) + assert.isTrue(token.neverminedProxyUri.length > 0) + }) + + it('I want to download a file asset', async () => { + const results = await nvmAppSubscriber.downloadFiles( + datasetDid, + agreementId, + `/tmp/.nevermined/downloads/${datasetDid}/`, + ) + + assert.isDefined(results) + assert.isTrue(results.success) + }) + + it('I can disconnect and still search', async () => { + await nvmAppSubscriber.disconnect() + const results = await nvmAppSubscriber.search.query({}) + assert.isTrue(results.totalResults.value > 0) + }) + }) +}) diff --git a/integration/nevermined/NVMAppAPI.test.ts b/integration/nevermined/NVMAppAPI.test.ts new file mode 100644 index 000000000..5c3239d79 --- /dev/null +++ b/integration/nevermined/NVMAppAPI.test.ts @@ -0,0 +1,255 @@ +import chai, { assert } from 'chai' +import chaiAsPromised from 'chai-as-promised' + +import { + Account, + AssetPrice, + Nevermined, + ResourceAuthentication, + SubscriptionCreditsNFTApi, + ZeroAddress, +} from '../../src' +import { config } from '../config' +import TestContractHandler from '../../test/keeper/TestContractHandler' +import { NVMAppEnvironments, NvmApp } from '../../src/nevermined/NvmApp' +import { Signer } from 'ethers' +import { NvmAppMetadata } from '../../src/ddo/NvmAppMetadata' + +chai.use(chaiAsPromised) + +describe('NVM App API', () => { + describe('LOCAL: As NVM App integrator I want to initialize the api in different ways', () => { + let nvmApp: NvmApp + let signerAddress: string + let defaultSigner: Signer + let publisher: Account + let subscriptionNFTAddress: string + let subscriptionDid: string + let agentDid: string + let datasetDid: string + let agreementId + let subscriptionPrice: AssetPrice + let subscriptionPriceWithFees: AssetPrice + + // Agent/Service test configuration + const SERVICE_ENDPOINT = process.env.SERVICE_ENDPOINT || 'http://127.0.0.1:3000' + const OPEN_PATH = process.env.OPEN_PATH || '/openapi.json' + const OPEN_ENDPOINT = process.env.OPEN_ENDPOINT || `${SERVICE_ENDPOINT}${OPEN_PATH}` + const AUTHORIZATION_TYPE = (process.env.AUTHORIZATION_TYPE || + 'oauth') as ResourceAuthentication['type'] + const AUTHORIZATION_TOKEN = process.env.AUTHORIZATION_TOKEN || 'new_authorization_token' + const AUTHORIZATION_USER = process.env.AUTHORIZATION_USER || 'user' + const AUTHORIZATION_PASSWORD = process.env.AUTHORIZATION_PASSWORD || 'password' + + before(async () => { + TestContractHandler.setConfig(config) + + const nevermined = await Nevermined.getInstance(config) + ;[, publisher] = await nevermined.accounts.list() + + const contractABI = await TestContractHandler.getABI( + `NFT1155SubscriptionUpgradeable.geth-localnet`, + './artifacts/', + ) + const subscriptionNFT = await SubscriptionCreditsNFTApi.deployInstance( + config, + contractABI, + publisher, + [ + publisher.getId(), + nevermined.keeper.didRegistry.address, + 'App Subscription NFT', + 'CRED', + '', + nevermined.keeper.nvmConfig.address, + ], + ) + + subscriptionNFTAddress = subscriptionNFT.address + console.debug(`Deployed ERC-1155 Subscription NFT on address: ${subscriptionNFTAddress}`) + + await nevermined.contracts.loadNft1155Api(subscriptionNFT) + const neverminedNodeAddress = await nevermined.services.node.getProviderAddress() + + await subscriptionNFT.grantOperatorRole( + nevermined.keeper.conditions.transferNftCondition.address, + publisher, + ) + console.debug(`Granting operator role to Nevermined Node Address: ${neverminedNodeAddress}`) + await subscriptionNFT.grantOperatorRole(neverminedNodeAddress, publisher) + + assert.equal(nevermined.nfts1155.getContract.address, subscriptionNFTAddress) + subscriptionPrice = new AssetPrice(publisher.getId(), 1000n).setTokenAddress(ZeroAddress) + }) + + it('I want to search content from the app', async () => { + nvmApp = await NvmApp.getInstance(NVMAppEnvironments.Local) + const results = await nvmApp.search.query({}) + console.log(JSON.stringify(results.totalResults)) + + assert.isDefined(results) + }) + + it('Get the default configuration used', async () => { + const appConfig = nvmApp.config + assert.isDefined(appConfig) + }) + + it('Overwrite the default config with some parameters', async () => { + assert.notEqual(nvmApp.config.artifactsFolder, './artifacts') + nvmApp = await NvmApp.getInstance(NVMAppEnvironments.Local, config) + assert.equal(nvmApp.config.artifactsFolder, './artifacts') + }) + + it('I want to connect my account', async () => { + assert.isFalse(nvmApp.isWeb3Connected()) + + defaultSigner = config.accounts[0] + signerAddress = await defaultSigner.getAddress() + console.log(`Account address: ${signerAddress}`) + await nvmApp.connect(signerAddress) + + assert.isTrue(nvmApp.isWeb3Connected()) + }) + + it('I can calculate and include network fees', async () => { + subscriptionPriceWithFees = nvmApp.addNetworkFee(subscriptionPrice) + console.log(`Asset Price with fees: ${subscriptionPriceWithFees.toString()}`) + + assert.isTrue(nvmApp.isNetworkFeeIncluded(subscriptionPriceWithFees)) + }) + + it('I want to create a time subscription', async () => { + const timeSubscriptionMetadata = NvmAppMetadata.getTimeSubscriptionMetadataTemplate( + 'NVM App Time only Subscription test', + 'Nevermined', + 'hours', + ) + + const ddo = await nvmApp.createTimeSubscription( + timeSubscriptionMetadata, + subscriptionPrice, + 100, // 100 blocks duration + ) + + assert.isDefined(ddo) + const ddoFound = await nvmApp.search.byDID(ddo.id) + assert.equal(ddo.id, ddoFound.id) + }) + + it('I want to create a credits subscription', async () => { + const creditsSubscriptionMetadata = NvmAppMetadata.getCreditsSubscriptionMetadataTemplate( + 'NVM App Credits Subscription test', + 'Nevermined', + ) + + const ddo = await nvmApp.createCreditsSubscription( + creditsSubscriptionMetadata, + subscriptionPrice, + 50n, // blocks duration + ) + + assert.isDefined(ddo) + const ddoFound = await nvmApp.search.byDID(ddo.id) + assert.equal(ddo.id, ddoFound.id) + subscriptionDid = ddo.id + }) + + it('I want to register an Agent', async () => { + const agentMetadata = NvmAppMetadata.getServiceMetadataTemplate( + 'Nevermined Ageeeent', + 'Nevermined', + [ + { + GET: `${SERVICE_ENDPOINT}(.*)`, + }, + ], + [OPEN_ENDPOINT], + OPEN_ENDPOINT, + 'RESTful', + AUTHORIZATION_TYPE, + AUTHORIZATION_TOKEN, + AUTHORIZATION_USER, + AUTHORIZATION_PASSWORD, + true, + ) + + const ddo = await nvmApp.registerServiceAsset( + agentMetadata, + subscriptionDid, + // We are gonna configure the agent usage costs in a dynamic manner: + // The cost in credits for every succesful query to the agent will be between 1 and 5 credits being 2 credits the default cost + 2n, // default cost in credits for every succesful query to the agent + 1n, // min amount of credits to be consumed + 5n, // max amount of credits to be consumed + ) + + assert.isDefined(ddo) + const ddoFound = await nvmApp.search.byDID(ddo.id) + assert.equal(ddo.id, ddoFound.id) + agentDid = ddo.id + }) + + it('I want to register a Dataset', async () => { + const datasetMetadata = NvmAppMetadata.getFileMetadataTemplate( + 'NVM App Dataset test', + 'Nevermined', + ) + datasetMetadata.main.files = [ + { + index: 0, + contentType: 'application/json', + url: 'https://storage.googleapis.com/nvm-static-assets/files/ci/ddo-example.json', + }, + { + index: 1, + contentType: 'text/plain', + url: 'https://storage.googleapis.com/nvm-static-assets/files/ci/README.md', + }, + ] + + const ddo = await nvmApp.registerFileAsset( + datasetMetadata, + subscriptionDid, + 1n, // every file download costs 1 credit to the subscriber + ) + + assert.isDefined(ddo) + const ddoFound = await nvmApp.search.byDID(ddo.id) + assert.equal(ddo.id, ddoFound.id) + datasetDid = ddo.id + }) + + it('I want to order a subscription', async () => { + const orderResult = await nvmApp.orderSubscription(subscriptionDid) + assert.isDefined(orderResult) + assert.isTrue(orderResult.success) + assert.isTrue(orderResult.agreementId.length > 0) + agreementId = orderResult.agreementId + }) + + it('I want to get the token giving access to a remote agent', async () => { + const token = await nvmApp.getServiceAccessToken(agentDid) + assert.isDefined(token) + assert.isTrue(token.accessToken.length > 0) + assert.isTrue(token.neverminedProxyUri.length > 0) + }) + + it('I want to download a file asset', async () => { + const results = await nvmApp.downloadFiles( + datasetDid, + agreementId, + `/tmp/.nevermined/downloads/${datasetDid}/`, + ) + + assert.isDefined(results) + assert.isTrue(results.success) + }) + + it('I can disconnect and still search', async () => { + await nvmApp.disconnect() + const results = await nvmApp.search.query({}) + assert.isTrue(results.totalResults.value > 0) + }) + }) +}) diff --git a/integration/nevermined/NVMAppFlows.ts b/integration/nevermined/NVMAppFlows.test.ts similarity index 100% rename from integration/nevermined/NVMAppFlows.ts rename to integration/nevermined/NVMAppFlows.test.ts diff --git a/package.json b/package.json index 258647840..5cfd5873f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nevermined-io/sdk", - "version": "2.2.0", + "version": "2.2.0-rc1", "description": "Javascript SDK for connecting with Nevermined Data Platform ", "main": "./dist/node/sdk.js", "typings": "./dist/node/sdk.d.ts", diff --git a/src/ddo/NvmAppMetadata.ts b/src/ddo/NvmAppMetadata.ts new file mode 100644 index 000000000..fa98d1ec1 --- /dev/null +++ b/src/ddo/NvmAppMetadata.ts @@ -0,0 +1,177 @@ +import { MetadataValidationResults } from '../nevermined/NvmApp' +import { + ChargeType, + MetaData, + MetaDataMain, + ResourceAuthentication, + SubscriptionType, +} from './types' + +export class NvmAppMetadata { + public static getTimeSubscriptionMetadataTemplate( + name: string, + author: string, + timeMeasure: string, + ): MetaData { + const metadata = NvmAppMetadata.getSubscriptionMetadataTemplate(name, author) + metadata.main.subscription = { + subscriptionType: SubscriptionType.Time, + timeMeasure, + } + metadata.additionalInformation.customData = { + subscriptionLimitType: SubscriptionType.Time, + dateMeasure: timeMeasure, + } + return metadata + } + + public static getCreditsSubscriptionMetadataTemplate(name: string, author: string): MetaData { + const metadata = NvmAppMetadata.getSubscriptionMetadataTemplate(name, author) + metadata.main.subscription = { + subscriptionType: SubscriptionType.Credits, + } + metadata.additionalInformation.customData = { + subscriptionLimitType: SubscriptionType.Credits, + } + return metadata + } + + public static getSubscriptionMetadataTemplate(name: string, author: string): MetaData { + const _metadata = { + main: { + name, + type: 'subscription', + dateCreated: new Date().toISOString().replace(/\.[0-9]{3}/, ''), + datePublished: new Date().toISOString().replace(/\.[0-9]{3}/, ''), + author, + license: '', + files: [], + paymentAttributes: [ + { + paymentType: 'serviceAgreements', + paymentEnabled: true, + }, + ], + } as MetaDataMain, + additionalInformation: {}, + } + + return _metadata + } + + public static getServiceMetadataTemplate( + name: string, + author: string, + endpoints: { [verb: string]: string }[], + openEndpoints: string[], + openApiEndpoint: string | undefined, + serviceType: string = 'RESTful', + authType: ResourceAuthentication['type'], + authToken?: string, + authUser?: string, + authPassword?: string, + isPriceDynamic: boolean = false, + nonce: string | number = Math.random(), + ): MetaData { + const serviceMetadata = { + main: { + name, + type: 'service', + dateCreated: new Date().toISOString().replace(/\.[0-9]{3}/, ''), + datePublished: new Date().toISOString().replace(/\.[0-9]{3}/, ''), + author, + license: '', + files: [], + webService: { + type: serviceType, + endpoints, + openEndpoints, + internalAttributes: {}, + chargeType: isPriceDynamic ? ChargeType.Dynamic : ChargeType.Fixed, + }, + ...({ nonce } as any), + }, + additionalInformation: { + customData: {}, + }, + } + if (openApiEndpoint) { + serviceMetadata.main.webService.openEndpoints.push(openApiEndpoint) + serviceMetadata.main.additionalInformation = { + ...serviceMetadata.main.additionalInformation, + customData: { + openApi: openApiEndpoint, + }, + } + } + + if (authType === 'basic') { + serviceMetadata.main.webService.internalAttributes.authentication = { + type: 'basic', + username: authUser, + password: authPassword, + } + } else if (authType === 'oauth') { + serviceMetadata.main.webService.internalAttributes.authentication = { + type: 'oauth', + token: authToken, + } + serviceMetadata.main.webService.internalAttributes.headers = [ + { Authorization: `Bearer ${authToken}` }, + ] + } else { + serviceMetadata.main.webService.internalAttributes.authentication = { + type: 'none', + } + } + + return serviceMetadata + } + + public static getFileMetadataTemplate(name: string, author: string): MetaData { + const _metadata = { + main: { + name, + type: 'dataset', + dateCreated: new Date().toISOString().replace(/\.[0-9]{3}/, ''), + datePublished: new Date().toISOString().replace(/\.[0-9]{3}/, ''), + author, + license: '', + files: [], + paymentAttributes: [], + } as MetaDataMain, + additionalInformation: { + customData: { + dataSchema: '', + filesFormat: '', + }, + }, + } + + return _metadata + } + + public static validateSubscription( + metadata: MetaData, + subscriptionType: SubscriptionType, + ): MetadataValidationResults { + const errorMessages: string[] = [] + + if (!metadata.additionalInformation?.customData) errorMessages.push('Custom Data not included') + if (!metadata.additionalInformation?.customData?.subscriptionLimitType) + errorMessages.push('customData.subscriptionLimitType not included') + if ( + metadata.additionalInformation?.customData?.subscriptionLimitType !== + subscriptionType.toString() + ) + errorMessages.push('invalid customData.subscriptionLimitType value') + + if (subscriptionType === SubscriptionType.Time) { + if (!metadata.additionalInformation?.customData?.dateMeasure) + errorMessages.push('customData.dateMeasure not included') + } + + if (errorMessages.length > 0) return { isValid: false, messages: errorMessages } + return { isValid: true, messages: [] } + } +} diff --git a/src/ddo/types.ts b/src/ddo/types.ts index 799d16967..944bb46ca 100644 --- a/src/ddo/types.ts +++ b/src/ddo/types.ts @@ -207,6 +207,24 @@ export interface WebService { internalAttributes?: WebServiceInternalAttributes encryptedAttributes?: string + + chargeType?: ChargeType +} + +export enum SubscriptionType { + Time = 'time', + Credits = 'credits', + Both = 'both', +} + +export enum ChargeType { + Fixed = 'fixed', + Dynamic = 'dynamic', +} + +export interface SubscriptionMetadata { + subscriptionType: SubscriptionType + timeMeasure?: string } export interface WebServiceInternalAttributes { @@ -291,6 +309,8 @@ export interface MetaDataMain { */ files?: MetaDataExternalResource[] + subscription?: SubscriptionMetadata + webService?: WebService encryptedService?: any diff --git a/src/models/AssetPrice.ts b/src/models/AssetPrice.ts index 6214f324e..5abddbfe0 100644 --- a/src/models/AssetPrice.ts +++ b/src/models/AssetPrice.ts @@ -13,7 +13,7 @@ export class AssetPrice { public constructor(address: string, amount: bigint, tokenAddress: string) public constructor(..._params: any[]) { this.totalPrice = 0n - this.rewards = new Map() + this.rewards = new Map() if (_params.length === 1) { const [rewards] = _params @@ -52,8 +52,10 @@ export class AssetPrice { } public setReceiver(receiver: string, amount: bigint): AssetPrice { - this.rewards.set(receiver, amount) - this.totalPrice = AssetPrice.sumAmounts(this.getAmounts()) + if (amount > 0) { + this.rewards.set(receiver, amount) + this.totalPrice = AssetPrice.sumAmounts(this.getAmounts()) + } return this } @@ -73,10 +75,8 @@ export class AssetPrice { * @returns the asset rewards object */ public addNetworkFees(feeReceiver: string, networkFeePercent: bigint): AssetPrice { - return this.setReceiver( - feeReceiver, - (this.totalPrice * networkFeePercent) / AssetPrice.NETWORK_FEE_DENOMINATOR / 100n, - ) + const amount = (this.totalPrice * networkFeePercent) / AssetPrice.NETWORK_FEE_DENOMINATOR / 100n + return this.setReceiver(feeReceiver, amount) } /** @@ -89,13 +89,15 @@ export class AssetPrice { const feesToInclude = (this.totalPrice * networkFeePercent) / AssetPrice.NETWORK_FEE_DENOMINATOR / 100n - const newRewards: Map = new Map() - this.rewards.forEach((k, v) => { - newRewards.set(v, k - (k * networkFeePercent) / AssetPrice.NETWORK_FEE_DENOMINATOR / 100n) - }) - newRewards.set(feeReceiver, feesToInclude) - this.rewards = newRewards - this.totalPrice = AssetPrice.sumAmounts(this.getAmounts()) + if (feesToInclude > 0) { + const newRewards: Map = new Map() + this.rewards.forEach((k, v) => { + newRewards.set(v, k - (k * networkFeePercent) / AssetPrice.NETWORK_FEE_DENOMINATOR / 100n) + }) + newRewards.set(feeReceiver, feesToInclude) + this.rewards = newRewards + this.totalPrice = AssetPrice.sumAmounts(this.getAmounts()) + } return this } @@ -112,6 +114,13 @@ export class AssetPrice { } public toString(): string { - return this.getAmountsString() + this.getReceiversString() + return JSON.stringify({ + rewards: { + amounts: this.getAmounts(), + receivers: this.getReceivers(), + }, + totalPrice: this.totalPrice, + token: this.tokenAddress, + }) } } diff --git a/src/nevermined/NvmApp.ts b/src/nevermined/NvmApp.ts new file mode 100644 index 000000000..5ecc1d19c --- /dev/null +++ b/src/nevermined/NvmApp.ts @@ -0,0 +1,490 @@ +import { ZeroDevAccountSigner } from '@zerodev/sdk' +import { + Account, + AssetPrice, + ContractHandler, + DDO, + MetaData, + NFTAttributes, + Nevermined, + NeverminedInitializationOptions, + NeverminedOptions, + PublishMetadataOptions, + PublishOnChainOptions, + SearchApi, + SubscriptionToken, + SubscriptionType, + Web3Error, +} from '../sdk' +import { + AppDeploymentArbitrum, + AppDeploymentGnosis, + AppDeploymentLocal, + AppDeploymentMatic, + AppDeploymentMumbai, + AppDeploymentStaging, + AppDeploymentTesting, + NeverminedAppOptions, +} from './resources/AppNetworks' +import { isAddress } from 'ethers' + +export enum NVMAppEnvironments { + Staging = 'staging', + Testing = 'testing', + Live = 'live', + Matic = 'matic', + Mumbai = 'mumbai', + Gnosis = 'gnosis', + Local = 'local', + Custom = 'custom', +} + +export interface MetadataValidationResults { + isValid: boolean + messages: string[] +} + +export interface OperationResult { + agreementId: string + success: boolean +} + +export class NvmApp { + private configNVM: NeverminedAppOptions + private userAccount: Account | undefined + private searchSDK: Nevermined | undefined + private fullSDK: Nevermined | undefined + private useZeroDevSigner: boolean = false + private zeroDevSignerAccount?: ZeroDevAccountSigner<'ECDSA'> + private assetProviders: string[] = [] + private loginCredentials: string | undefined + private subscriptionNFTContractAddress: string | undefined + private networkFeeReceiver: string | undefined + private networkFee: bigint | undefined + + static readonly defaultAppInitializatioinOptions: NeverminedInitializationOptions = { + loadCore: true, + loadServiceAgreements: true, + loadNFTs1155: true, + loadNFTs721: false, + loadDispenser: true, + loadERC20Token: true, + loadAccessFlow: false, + loadDIDTransferFlow: false, + loadRewards: false, + loadRoyalties: true, + loadCompute: false, + } + + static readonly publicationOptions = { + metadata: PublishMetadataOptions.OnlyMetadataAPI, + did: PublishOnChainOptions.DIDRegistry, + } + + public static async getInstance( + appEnv: NVMAppEnvironments, + config?: Partial, + ): Promise { + const defaultEnvConfig = this.getConfigFromTagName(appEnv) + const mergedConfig = config ? { ...defaultEnvConfig, ...config } : defaultEnvConfig + const nvmApp = new NvmApp(mergedConfig as NeverminedAppOptions) + await nvmApp.initializeSearch() + return nvmApp + } + + private constructor(config: NeverminedAppOptions) { + this.configNVM = config + } + + public async initializeSearch(config?: NeverminedAppOptions) { + this.searchSDK = await Nevermined.getSearchOnlyInstance(config ? config : this.configNVM) + } + + public async connect( + account: string | ZeroDevAccountSigner<'ECDSA'> | Account, + config?: NeverminedOptions, + initOptions?: NeverminedInitializationOptions, + ) { + const ops = initOptions + ? { ...NvmApp.defaultAppInitializatioinOptions, ...initOptions } + : NvmApp.defaultAppInitializatioinOptions + this.fullSDK = await Nevermined.getInstance(config ? config : this.configNVM, ops) + + if (account instanceof ZeroDevAccountSigner) { + this.userAccount = await Account.fromZeroDevSigner(account) + this.zeroDevSignerAccount = account + this.useZeroDevSigner = true + } else if (account instanceof Account) { + this.userAccount = account + } else { + this.userAccount = this.fullSDK.accounts.getAccount(account) + } + + const clientAssertion = await this.fullSDK.utils.jwt.generateClientAssertion(this.userAccount) + + this.loginCredentials = await this.fullSDK.services.marketplace.login(clientAssertion) + + const nodeInfo = await this.fullSDK.services.node.getNeverminedNodeInfo() + this.assetProviders = [nodeInfo['provider-address']] + + if (!isAddress(this.configNVM.nftContractAddress)) { + const contractABI = await ContractHandler.getABI( + 'NFT1155SubscriptionUpgradeable', + this.configNVM.artifactsFolder, + await this.fullSDK.keeper.getNetworkName(), + ) + this.subscriptionNFTContractAddress = contractABI.address + } else { + this.subscriptionNFTContractAddress = this.configNVM.nftContractAddress + } + if (!isAddress(this.subscriptionNFTContractAddress)) { + throw new Web3Error('Invalid Subscription NFT contract address') + } + + this.networkFeeReceiver = await this.fullSDK.keeper.nvmConfig.getFeeReceiver() + this.networkFee = await this.fullSDK.keeper.nvmConfig.getNetworkFee() + } + + public async disconnect() { + if (this.fullSDK && this.isWeb3Connected()) { + this.fullSDK = undefined + this.userAccount = undefined + this.useZeroDevSigner = false + this.zeroDevSignerAccount = undefined + this.loginCredentials = undefined + } + } + + public isWeb3Connected(): boolean { + return this.fullSDK ? this.fullSDK.isKeeperConnected : false + } + + public getLoginCredentials(): string | undefined { + return this.loginCredentials + } + + public get config(): NeverminedOptions { + return this.configNVM + } + + public get search(): SearchApi { + return this.searchSDK.search + } + + public get networkFees(): { receiver: string; fee: bigint } { + return { receiver: this.networkFeeReceiver, fee: this.networkFee } + } + + public async createTimeSubscription( + susbcriptionMetadata: MetaData, + subscriptionPrice: AssetPrice, + duration: number, + ): Promise { + if (!this.isWeb3Connected()) + throw new Web3Error('Web3 not connected, try calling the connect method first') + + const validationResult = this.validateSubscription( + susbcriptionMetadata, + subscriptionPrice, + SubscriptionType.Time, + ) + if (!validationResult.isValid) { + throw new Error(validationResult.messages.join(',')) + } + + this.fullSDK.services.node.getVersionInfo() + const nftAttributes = NFTAttributes.getCreditsSubscriptionInstance({ + metadata: susbcriptionMetadata, + services: [ + { + serviceType: 'nft-sales', + price: subscriptionPrice, + nft: { + duration, + amount: 1n, + nftTransfer: false, + }, + }, + ], + providers: this.assetProviders, + nftContractAddress: this.subscriptionNFTContractAddress, + preMint: false, + }) + + return await this.fullSDK.nfts1155.create( + nftAttributes, + this.userAccount, + NvmApp.publicationOptions, + { ...(this.useZeroDevSigner && { zeroDevSigner: this.zeroDevSignerAccount }) }, + ) + } + + public async createCreditsSubscription( + susbcriptionMetadata: MetaData, + subscriptionPrice: AssetPrice, + numberCredits: bigint, + ): Promise { + if (!this.isWeb3Connected()) + throw new Web3Error('Web3 not connected, try calling the connect method first') + + const validationResult = this.validateSubscription( + susbcriptionMetadata, + subscriptionPrice, + SubscriptionType.Credits, + ) + if (!validationResult.isValid) { + throw new Error(validationResult.messages.join(',')) + } + + const nftAttributes = NFTAttributes.getCreditsSubscriptionInstance({ + metadata: susbcriptionMetadata, + services: [ + { + serviceType: 'nft-sales', + price: subscriptionPrice, + nft: { + amount: numberCredits, + nftTransfer: false, + }, + }, + ], + providers: this.assetProviders, + nftContractAddress: this.subscriptionNFTContractAddress, + preMint: false, + }) + + return await this.fullSDK.nfts1155.create( + nftAttributes, + this.userAccount, + NvmApp.publicationOptions, + { ...(this.useZeroDevSigner && { zeroDevSigner: this.zeroDevSignerAccount }) }, + ) + } + + public async orderSubscription( + subscriptionDid: string, + agreementId?: string, + ): Promise { + if (!this.isWeb3Connected()) + throw new Web3Error('Web3 not connected, try calling the connect method first') + + let numberCredits: bigint + let serviceIndex: number + let transferResult = false + try { + const ddo = await this.fullSDK.assets.resolve(subscriptionDid) + const salesService = ddo.findServiceByReference('nft-sales') + serviceIndex = salesService.index + numberCredits = salesService.attributes.main.nftAttributes.amount + + if (!agreementId) + agreementId = await this.fullSDK.nfts1155.order( + subscriptionDid, + numberCredits, + this.userAccount, + serviceIndex, + { ...(this.useZeroDevSigner && { zeroDevSigner: this.zeroDevSignerAccount }) }, + ) + const subscriptionOwner = await this.fullSDK.assets.owner(subscriptionDid) + transferResult = await this.fullSDK.nfts1155.claim( + agreementId, + subscriptionOwner, + this.userAccount.getId(), + numberCredits, + subscriptionDid, + serviceIndex, + ) + } catch (error) { + throw new Web3Error(`Error ordering subscription: ${error.message}`) + } + + return { agreementId, success: transferResult } + } + + public async getServiceAccessToken(serviceDid: string): Promise { + if (!this.isWeb3Connected()) + throw new Web3Error('Web3 not connected, try calling the connect method first') + + return await this.fullSDK.nfts1155.getSubscriptionToken(serviceDid, this.userAccount) + } + + public async downloadFiles( + fileAssetDid: string, + agreementId: string, + destinationPath: string, + ): Promise { + try { + const result = await this.fullSDK.nfts1155.access( + fileAssetDid, + this.userAccount, + destinationPath, + undefined, + agreementId, + ) + return { agreementId, success: result } + } catch (error) { + throw new Web3Error(`Error downloading files: ${error.message}`) + } + } + + public async registerServiceAsset( + metadata: MetaData, + subscriptionDid: string, + costInCredits = 1n, + minCreditsToCharge = 1n, + maxCreditsToCharge = 1n, + ): Promise { + if (!this.isWeb3Connected()) + throw new Web3Error('Web3 not connected, try calling the connect method first') + + const validationResult = this.validateServiceAssetMetadata(metadata) + if (!validationResult.isValid) { + throw new Error(validationResult.messages.join(',')) + } + + const nftAttributes = NFTAttributes.getCreditsSubscriptionInstance({ + metadata, + services: [ + { + serviceType: 'nft-access', + nft: { + tokenId: subscriptionDid, + amount: costInCredits, + maxCreditsToCharge, + minCreditsToCharge, + nftTransfer: false, + }, + }, + ], + providers: this.assetProviders, + nftContractAddress: this.subscriptionNFTContractAddress, + preMint: false, + }) + + return await this.fullSDK.nfts1155.create( + nftAttributes, + this.userAccount, + NvmApp.publicationOptions, + { ...(this.useZeroDevSigner && { zeroDevSigner: this.zeroDevSignerAccount }) }, + ) + } + + public async registerFileAsset( + metadata: MetaData, + subscriptionDid: string, + costInCredits = 1n, + ): Promise { + if (!this.isWeb3Connected()) + throw new Web3Error('Web3 not connected, try calling the connect method first') + + const validationResult = this.validateFileAssetMetadata(metadata) + if (!validationResult.isValid) { + throw new Error(validationResult.messages.join(',')) + } + + const nftAttributes = NFTAttributes.getCreditsSubscriptionInstance({ + metadata, + services: [ + { + serviceType: 'nft-access', + nft: { + tokenId: subscriptionDid, + amount: costInCredits, + nftTransfer: false, + }, + }, + ], + providers: this.assetProviders, + nftContractAddress: this.subscriptionNFTContractAddress, + preMint: false, + }) + + return await this.fullSDK.nfts1155.create( + nftAttributes, + this.userAccount, + NvmApp.publicationOptions, + { ...(this.useZeroDevSigner && { zeroDevSigner: this.zeroDevSignerAccount }) }, + ) + } + + public addNetworkFee(price: AssetPrice): AssetPrice { + if (!this.isNetworkFeeIncluded(price)) { + return price.adjustToIncludeNetworkFees(this.networkFeeReceiver, this.networkFee) + } + return price + } + + public isNetworkFeeIncluded(price: AssetPrice): boolean { + // If there are no network fees everything is okay + if (this.networkFee === 0n || price.getTotalPrice() === 0n) return true + if (!price.getRewards().has(this.networkFeeReceiver)) return false + + const networkFee = price.getRewards().get(this.networkFeeReceiver) + const expectedFee = + (price.getTotalPrice() * this.networkFee) / AssetPrice.NETWORK_FEE_DENOMINATOR / 100n + if (networkFee !== expectedFee) return false + return true + } + + // TODO: Implement subscription validations + public validateSubscription( + metadata: MetaData, + price: AssetPrice, + subscriptionType: SubscriptionType, + ): MetadataValidationResults { + const errorMessages: string[] = [] + if (!this.isNetworkFeeIncluded(price)) errorMessages.push('Network fee not included') + if (!metadata.additionalInformation?.customData) errorMessages.push('Custom Data not included') + if (!metadata.additionalInformation?.customData?.subscriptionLimitType) + errorMessages.push('customData.subscriptionLimitType not included') + if ( + metadata.additionalInformation?.customData?.subscriptionLimitType !== + subscriptionType.toString() + ) + errorMessages.push('invalid customData.subscriptionLimitType value') + + if (subscriptionType === SubscriptionType.Time) { + if (!metadata.additionalInformation?.customData?.dateMeasure) + errorMessages.push('customData.dateMeasure not included') + } + + if (errorMessages.length > 0) return { isValid: false, messages: errorMessages } + return { isValid: true, messages: [] } + } + + // TODO: Implement subscription validations + public validateServiceAssetMetadata(_susbcriptionMetadata: MetaData): MetadataValidationResults { + return { isValid: true, messages: [] } + } + + // TODO: Implement subscription validations + public validateFileAssetMetadata(_susbcriptionMetadata: MetaData): MetadataValidationResults { + return { isValid: true, messages: [] } + } + + private static getConfigFromTagName(appEnv: NVMAppEnvironments) { + const defaultDeploymentConfig = this.switchConfigBetweenEnvs(appEnv) + return defaultDeploymentConfig + } + + private static switchConfigBetweenEnvs(appEnv: NVMAppEnvironments): NeverminedOptions { + switch (appEnv) { + case NVMAppEnvironments.Staging: + return new AppDeploymentStaging() + case NVMAppEnvironments.Testing: + return new AppDeploymentTesting() + case NVMAppEnvironments.Live: + return new AppDeploymentArbitrum() + case NVMAppEnvironments.Matic: + return new AppDeploymentMatic() + case NVMAppEnvironments.Mumbai: + return new AppDeploymentMumbai() + case NVMAppEnvironments.Gnosis: + return new AppDeploymentGnosis() + case NVMAppEnvironments.Local: + return new AppDeploymentLocal() + default: + throw new Error('Invalid environment') + } + } +} diff --git a/src/nevermined/resources/AppNetworks.ts b/src/nevermined/resources/AppNetworks.ts new file mode 100644 index 000000000..919f731fc --- /dev/null +++ b/src/nevermined/resources/AppNetworks.ts @@ -0,0 +1,104 @@ +import { NeverminedOptions } from '../../models' + +export class NeverminedAppOptions extends NeverminedOptions { + instanceName: string + nftContractAddress?: string + tokenAddress?: string // ERC-20 token address or zero address (0x000..) for native token +} + +export class AppDeploymentLocal extends NeverminedAppOptions { + instanceName = 'localnet' + web3ProviderUri = 'http://contracts.nevermined.localnet' + marketplaceUri = 'http://marketplace.nevermined.localnet' + graphHttpUri = undefined + neverminedNodeUri = 'http://node.nevermined.localnet' + neverminedNodeAddress = '0x068ed00cf0441e4829d9784fcbe7b9e26d4bd8d0' + verbose = true + gasMultiplier = 0 + gasPriceMultiplier = 0 + nftContractAddress = undefined +} + +export class AppDeploymentStaging extends NeverminedAppOptions { + instanceName = 'appStaging' + web3ProviderUri = 'https://sepolia-rollup.arbitrum.io/rpc' + marketplaceUri = 'https://marketplace-api.staging.nevermined.app' + graphHttpUri = 'https://api.thegraph.com/subgraphs/name/nevermined-io/public' + neverminedNodeUri = 'https://node.staging.nevermined.app' + neverminedNodeAddress = '0x046d0698926aFa3ab6D6591f03063488F3Fb4327' + verbose = true + gasMultiplier = 0 + gasPriceMultiplier = 0 + nftContractAddress = undefined + tokenAddress = '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d' +} + +export class AppDeploymentTesting extends NeverminedAppOptions { + instanceName = 'appTesting' + web3ProviderUri = 'https://sepolia-rollup.arbitrum.io/rpc' + marketplaceUri = 'https://marketplace-api.testing.nevermined.app' + graphHttpUri = 'https://api.thegraph.com/subgraphs/name/nevermined-io/public' + neverminedNodeUri = 'https://node.testing.nevermined.app' + neverminedNodeAddress = '0x046d0698926aFa3ab6D6591f03063488F3Fb4327' + verbose = true + gasMultiplier = 0 + gasPriceMultiplier = 0 + nftContractAddress = undefined + tokenAddress = '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d' +} + +export class AppDeploymentArbitrum extends NeverminedAppOptions { + instanceName = 'appArbitrum' + web3ProviderUri = 'https://arb1.arbitrum.io/rpc' + marketplaceUri = 'https://marketplace-api.arbitrum.nevermined.app' + graphHttpUri = 'https://api.thegraph.com/subgraphs/name/nevermined-io/public' + neverminedNodeUri = 'https://node.arbitrum.nevermined.app' + neverminedNodeAddress = '0x0b5297b97655A29dE245700864F5591741e50d2c' + verbose = true + gasMultiplier = 0 + gasPriceMultiplier = 0 + nftContractAddress = undefined + tokenAddress = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' +} + +export class AppDeploymentGnosis extends NeverminedAppOptions { + instanceName = 'appGnosis' + web3ProviderUri = 'https://rpc.gnosischain.com/' + marketplaceUri = 'https://marketplace-api.gnosis.nevermined.app' + graphHttpUri = 'https://api.thegraph.com/subgraphs/name/nevermined-io/public' + neverminedNodeUri = 'https://node.gnosis.nevermined.app' + neverminedNodeAddress = '0x824dbcE5E9C96C5b8ce2A35a25a5ab87eD1D00b1' + verbose = true + gasMultiplier = 0 + gasPriceMultiplier = 0 + nftContractAddress = undefined + tokenAddress = '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83' +} + +export class AppDeploymentMumbai extends NeverminedAppOptions { + instanceName = 'appMumbai' + web3ProviderUri = 'https://matic-mumbai.chainstacklabs.com' + marketplaceUri = 'https://marketplace-api.mumbai.nevermined.app' + graphHttpUri = 'https://api.thegraph.com/subgraphs/name/nevermined-io/public' + neverminedNodeUri = 'https://node.mumbai.nevermined.app' + neverminedNodeAddress = '0x5838B5512cF9f12FE9f2beccB20eb47211F9B0bc' + verbose = true + gasMultiplier = 0 + gasPriceMultiplier = 0 + nftContractAddress = undefined + tokenAddress = '0x2058a9d7613eee744279e3856ef0eada5fcbaa7e' +} + +export class AppDeploymentMatic extends NeverminedAppOptions { + instanceName = 'appMatic' + web3ProviderUri = 'https://polygon-rpc.com' + marketplaceUri = 'https://marketplace-api.matic.nevermined.app' + graphHttpUri = 'https://api.thegraph.com/subgraphs/name/nevermined-io/public' + neverminedNodeUri = 'https://node.matic.nevermined.app' + neverminedNodeAddress = '0x824dbcE5E9C96C5b8ce2A35a25a5ab87eD1D00b1' + verbose = true + gasMultiplier = 1.2 + gasPriceMultiplier = 1.2 + nftContractAddress = undefined + tokenAddress = '0x2791bca1f2de4661ed88a30c99a7a9449aa84174' +}