import { SearchQuery } from '../metadatastore/MetadataStore' import { DDO } from '../ddo/DDO' import { Metadata } from '../ddo/interfaces/Metadata' import { Service, ServiceAccess, ServiceComputePrivacy, ServiceCommon } from '../ddo/interfaces/Service' import { EditableMetadata } from '../ddo/interfaces/EditableMetadata' import Account from './Account' import DID from './DID' import { SubscribablePromise } from '../utils' import { Instantiable, InstantiableConfig } from '../Instantiable.abstract' import { WebServiceConnector } from './utils/WebServiceConnector' export enum CreateProgressStep { CreatingDataToken, DataTokenCreated, EncryptingFiles, FilesEncrypted, GeneratingProof, ProofGenerated, StoringDdo, DdoStored } export enum OrderProgressStep { CreatingAgreement, AgreementInitialized, LockingPayment, LockedPayment } /** * Assets submodule of Ocean Protocol. */ export class Assets extends Instantiable { /** * Returns the instance of Assets. * @return {Promise} */ public static async getInstance(config: InstantiableConfig): Promise { const instance = new Assets() instance.setInstanceConfig(config) return instance } /** * Creates a simple asset and a datatoken * @param {Account} publisher Publisher account. * @return {Promise} */ public createSimpleAsset(publisher: Account): Promise { const publisherURI = this.ocean.provider.getURI() const jsonBlob = { t: 0, url: publisherURI } const { datatokens } = this.ocean return datatokens.create(JSON.stringify(jsonBlob), publisher) } /** * Creates a new DDO and publishes it * @param {Metadata} metadata DDO metadata. * @param {Account} publisher Publisher account. * @param {list} services list of Service description documents * @return {Promise} */ public create( metadata: Metadata, publisher: Account, services: Service[] = [], dtAddress?: string ): SubscribablePromise { this.logger.log('Creating asset') return new SubscribablePromise(async (observer) => { if (services.length === 0) { this.logger.log('You have no services. Are you sure about this?') } if (!dtAddress) { this.logger.log('Creating datatoken') observer.next(CreateProgressStep.CreatingDataToken) const metadataStoreURI = this.ocean.metadatastore.getURI() const jsonBlob = { t: 1, url: metadataStoreURI } const { datatokens } = this.ocean dtAddress = await datatokens.create(JSON.stringify(jsonBlob), publisher) this.logger.log('DataToken creted') observer.next(CreateProgressStep.DataTokenCreated) } const did: DID = DID.generate() this.logger.log('Encrypting files') observer.next(CreateProgressStep.EncryptingFiles) const encryptedFiles = await this.ocean.provider.encrypt( did.getId(), metadata.main.files, publisher ) this.logger.log('Files encrypted') observer.next(CreateProgressStep.FilesEncrypted) let indexCount = 0 // create ddo itself const ddo: DDO = new DDO({ id: did.getDid(), dataToken: dtAddress, authentication: [ { type: 'RsaSignatureAuthentication2018', publicKey: did.getDid() } ], publicKey: [ { id: did.getDid(), type: 'EthereumECDSAKey', owner: publisher.getId() } ], service: [ { type: 'metadata', attributes: { // Default values curation: { rating: 0, numVotes: 0 }, // Overwrites defaults ...metadata, encryptedFiles, // Cleaning not needed information main: { ...metadata.main, files: metadata.main.files.map((file, index) => ({ ...file, index, url: undefined })) } as any } }, ...services ] // Remove duplications .reverse() .filter( ({ type }, i, list) => list.findIndex(({ type: t }) => t === type) === i ) .reverse() // Adding index .map((_) => ({ ..._, index: indexCount++ })) as Service[] }) this.logger.log('Generating proof') observer.next(CreateProgressStep.GeneratingProof) await ddo.addProof(this.ocean, publisher.getId(), publisher.getPassword()) this.logger.log('Proof generated') observer.next(CreateProgressStep.ProofGenerated) this.logger.log('Storing DDO') observer.next(CreateProgressStep.StoringDdo) const storedDdo = await this.ocean.metadatastore.storeDDO(ddo) this.logger.log('DDO stored') observer.next(CreateProgressStep.DdoStored) return storedDdo }) } /** * Returns the owner of an asset. * @param {string} did Decentralized ID. * @return {Promise} Returns Account ID */ public async owner(did: string): Promise { // TODO: // const owner = await this.ocean.keeper.didRegistry.getDIDOwner(did) // return owner return '' } /** * Returns the assets of a owner. * @param {string} owner Owner address. * @return {Promise} List of DIDs. */ public async ownerAssets(owner: string): Promise { // TODO: // return this.ocean.keeper.didRegistry.getAttributesByOwner(owner) return [''] } /** * Returns a DDO by DID. * @param {string} did Decentralized ID. * @return {Promise} */ public async resolve(did: string): Promise { return this.ocean.metadatastore.retrieveDDO(did) } public async resolveByDTAddress( dtAddress: string, offset?: number, page?: number, sort?: number ): Promise { const searchQuery = { offset: offset || 100, page: page || 1, query: { dtAddress: [dtAddress] }, sort: { value: sort || 1 }, text: dtAddress } as SearchQuery return (await this.ocean.metadatastore.queryMetadata(searchQuery)).results } /** * Edit Metadata for a DDO. * @param {did} string DID. * @param {newMetadata} EditableMetadata Metadata fields & new values. * @param {Account} account Ethereum account of owner to sign and prove the ownership. * @return {Promise} */ public async editMetadata( did: string, newMetadata: EditableMetadata, account: Account ): Promise { const oldDdo = await this.ocean.metadatastore.retrieveDDO(did) // get a signature const signature = await this.ocean.utils.signature.signForAquarius( oldDdo.updated, account ) let result = null if (signature != null) result = await this.ocean.metadatastore.editMetadata( did, newMetadata, oldDdo.updated, signature ) return result } /** * Update Compute Privacy * @param {did} string DID. * @param {number} serviceIndex Index of the compute service in the DDO * @param {ServiceComputePrivacy} computePrivacy ComputePrivacy fields & new values. * @param {Account} account Ethereum account of owner to sign and prove the ownership. * @return {Promise} */ public async updateComputePrivacy( did: string, serviceIndex: number, computePrivacy: ServiceComputePrivacy, account: Account ): Promise { const oldDdo = await this.ocean.metadatastore.retrieveDDO(did) // get a signature const signature = await this.ocean.utils.signature.signForAquarius( oldDdo.updated, account ) let result = null if (signature != null) result = await this.ocean.metadatastore.updateComputePrivacy( did, serviceIndex, computePrivacy.allowRawAlgorithm, computePrivacy.allowNetworkAccess, computePrivacy.trustedAlgorithms, oldDdo.updated, signature ) return result } /** * Retire a DDO (Delete) * @param {did} string DID. * @param {Account} account Ethereum account of owner to sign and prove the ownership. * @return {Promise} */ public async retire(did: string, account: Account): Promise { const oldDdo = await this.ocean.metadatastore.retrieveDDO(did) // get a signature const signature = await this.ocean.utils.signature.signForAquarius( oldDdo.updated, account ) let result = null if (signature != null) result = await this.ocean.metadatastore.retire(did, oldDdo.updated, signature) return result } /** * Returns the creator of a asset. * @param {string} did Decentralized ID. * @return {Promise} Returns eth address */ public async creator(did: string): Promise { const ddo = await this.resolve(did) const checksum = ddo.getChecksum() const { creator, signatureValue } = ddo.proof const signer = await this.ocean.utils.signature.verifyText( checksum, signatureValue ) if (signer.toLowerCase() !== creator.toLowerCase()) { this.logger.warn( `Owner of ${ddo.id} doesn't match. Expected ${creator} instead of ${signer}.` ) } return creator } /** * Search over the assets using a query. * @param {SearchQuery} query Query to filter the assets. * @return {Promise} */ public async query(query: SearchQuery) { return this.ocean.metadatastore.queryMetadata(query) } /** * Search over the assets using a keyword. * @param {SearchQuery} text Text to filter the assets. * @return {Promise} */ public async search(text: string) { return this.ocean.metadatastore.queryMetadataByText({ text, page: 1, offset: 100, query: { value: 1 }, sort: { value: 1 } } as SearchQuery) } public async getService(did: string, serviceType: string): Promise { const services: ServiceCommon[] = (await this.resolve(did)).service let service services.forEach((serv) => { if (serv.type.toString() === serviceType) { service = serv } }) return service } public async createAccessServiceAttributes( creator: Account, dtCost: number, datePublished: string, timeout: number = 0 ): Promise { return { type: 'access', index: 2, serviceEndpoint: this.ocean.provider.getConsumeEndpoint(), attributes: { main: { creator: creator.getId(), datePublished, dtCost, timeout: timeout, name: 'dataAssetAccessServiceAgreement' } } } } public async download( dtAddress: string, serviceEndpoint: string, txId: string, account: string ): Promise { let consumeUrl = serviceEndpoint consumeUrl += `?consumerAddress=${account}` consumeUrl += `&tokenAddress=${dtAddress}` consumeUrl += `&transferTxId=${txId}` const serviceConnector = new WebServiceConnector(this.logger) try { await serviceConnector.downloadFile(consumeUrl) } catch (e) { this.logger.error('Error consuming assets') this.logger.error(e) throw e } return serviceEndpoint } }