mirror of
https://github.com/oceanprotocol/ocean.js.git
synced 2024-07-01 06:11:45 +02:00
first cut, highly WIP
This commit is contained in:
parent
5072178816
commit
67af4d1d59
15
.editorconfig
Normal file
15
.editorconfig
Normal file
|
@ -0,0 +1,15 @@
|
|||
# https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{json,yml,yaml,md}]
|
||||
indent_size = 2
|
||||
|
48
.eslintrc
Normal file
48
.eslintrc
Normal file
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": false
|
||||
},
|
||||
"project": [
|
||||
"./tsconfig.json",
|
||||
"./test/unit/tsconfig.json",
|
||||
"./test/integration/tsconfig.json"
|
||||
]
|
||||
},
|
||||
"extends": [
|
||||
"oceanprotocol",
|
||||
"prettier/standard",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier/@typescript-eslint"
|
||||
],
|
||||
"plugins": ["@typescript-eslint", "prettier"],
|
||||
"rules": {
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"error",
|
||||
{ "multiline": { "delimiter": "none" } }
|
||||
],
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/indent": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-object-literal-type-assertion": "off",
|
||||
"@typescript-eslint/no-parameter-properties": "off",
|
||||
"no-empty": ["error", { "allowEmptyCatch": true }],
|
||||
"prefer-destructuring": ["warn"],
|
||||
"no-dupe-class-members": ["warn"],
|
||||
"no-useless-constructor": ["warn"]
|
||||
},
|
||||
"env": {
|
||||
"es6": true,
|
||||
"browser": true,
|
||||
"mocha": true
|
||||
}
|
||||
}
|
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
node_modules/
|
||||
dist/
|
||||
.nyc_output/
|
||||
coverage/
|
||||
doc/
|
||||
test/**/*.js
|
||||
src/**/*.js
|
||||
|
||||
src/metadata\.json
|
||||
.idea
|
||||
.vscode
|
21
.npmignore
Normal file
21
.npmignore
Normal file
|
@ -0,0 +1,21 @@
|
|||
node_modules/
|
||||
coverage/
|
||||
.github
|
||||
.nyc_output
|
||||
.travis.yml
|
||||
test/
|
||||
src/
|
||||
tsconfig.json
|
||||
tslint.json
|
||||
oceanprotocol-squid-*.tgz
|
||||
squid-js.json
|
||||
barge/
|
||||
integration/
|
||||
plugins/
|
||||
scripts/
|
||||
webpack*
|
||||
ganache*
|
||||
.prettierrc
|
||||
.editorconfig
|
||||
.eslintrc
|
||||
SQUID_INTERFACE.md
|
6
.prettierrc
Normal file
6
.prettierrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 90,
|
||||
"trailingComma": "none"
|
||||
}
|
10
library.json
Normal file
10
library.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "Ocean Library",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/oceanprotocol/ocean-lib-js.git"
|
||||
},
|
||||
"dependencies": [
|
||||
|
||||
]
|
||||
}
|
15592
package-lock.json
generated
Normal file
15592
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
124
package.json
Normal file
124
package.json
Normal file
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
"name": "@oceanprotocol/lib",
|
||||
"version": "0.0.1",
|
||||
"description": "JavaScript client library for Ocean Protocol",
|
||||
"main": "./dist/node/lib.js",
|
||||
"typings": "./dist/node/lib.d.ts",
|
||||
"unpkg": "./dist/browser/lib.cjs2.min.js",
|
||||
"scripts": {
|
||||
"build": "npm run clean && npm run build:tsc && npm run build:dist",
|
||||
"build:tsc": "tsc --sourceMap",
|
||||
"build:dist": "cross-env NODE_ENV=production webpack",
|
||||
"clean": "rm -rf ./dist/ ./doc/ ./.nyc_output",
|
||||
"lint": "eslint --ignore-path .gitignore --ext .ts,.tsx .",
|
||||
"format": "prettier --parser typescript --ignore-path .gitignore --write '**/*.{js,jsx,ts,tsx}'",
|
||||
"run": "ts-node",
|
||||
"release": "release-it --non-interactive",
|
||||
"changelog": "auto-changelog -p",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/oceanprotocol/ocean-lib-js.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Ocean Protocol <devops@oceanprotocol.com>",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/oceanprotocol/ocean-lib-js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/oceanprotocol/ocean-lib-js#readme",
|
||||
"peerDependencies": {
|
||||
"web3": "^1.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethereum-navigator/navigator": "^0.5.0",
|
||||
"@oceanprotocol/keeper-contracts": "^0.13.2",
|
||||
"@oceanprotocol/secret-store-client": "^0.0.15",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"deprecated-decorator": "^0.1.6",
|
||||
"node-fetch": "^2.6.0",
|
||||
"save-file": "^2.3.1",
|
||||
"uuid": "^8.0.0",
|
||||
"web3": "^1.2.6",
|
||||
"whatwg-url": "^8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@release-it/bumper": "^1.1.0",
|
||||
"@truffle/hdwallet-provider": "^1.0.33",
|
||||
"@types/chai": "^4.2.11",
|
||||
"@types/chai-spies": "^1.0.1",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/node": "^14.0.0",
|
||||
"@types/node-fetch": "^2.5.5",
|
||||
"@types/sinon": "^9.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.23.0",
|
||||
"@typescript-eslint/parser": "^2.23.0",
|
||||
"auto-changelog": "^2.0.0",
|
||||
"chai": "^4.2.0",
|
||||
"chai-spies": "^1.0.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-oceanprotocol": "^1.5.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"lcov-result-merger": "^3.1.0",
|
||||
"mocha": "^7.1.0",
|
||||
"mock-local-storage": "^1.1.11",
|
||||
"nyc": "^15.0.0",
|
||||
"ora": "^4.0.2",
|
||||
"prettier": "^1.19.1",
|
||||
"sinon": "^9.0.1",
|
||||
"source-map-support": "^0.5.16",
|
||||
"ts-node": "^8.6.2",
|
||||
"typedoc": "^0.17.1",
|
||||
"typescript": "^3.8.3",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-merge": "^4.2.2"
|
||||
},
|
||||
"nyc": {
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"extension": [
|
||||
".ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register"
|
||||
],
|
||||
"reporter": [
|
||||
"text",
|
||||
"lcov",
|
||||
"html"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"instrument": true
|
||||
},
|
||||
"release-it": {
|
||||
"hooks": {
|
||||
"after:bump": "npm run changelog && npm run doc:json"
|
||||
},
|
||||
"plugins": {
|
||||
"@release-it/bumper": {
|
||||
"out": [
|
||||
"package.json",
|
||||
"package-lock.json"
|
||||
]
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
"tagName": "v${version}"
|
||||
},
|
||||
"github": {
|
||||
"release": true,
|
||||
"assets": [
|
||||
"dist/squid-js.json"
|
||||
]
|
||||
},
|
||||
"npm": {
|
||||
"publish": false
|
||||
}
|
||||
}
|
||||
}
|
31
plugins/add-vendors-plugin.js
Normal file
31
plugins/add-vendors-plugin.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
const { ConcatSource } = require('webpack-sources')
|
||||
|
||||
module.exports = class AddVendorsPlugin {
|
||||
constructor(base) {
|
||||
this.base = base
|
||||
}
|
||||
|
||||
apply(compiler) {
|
||||
compiler.hooks.emit.tapAsync(
|
||||
`AddVendorsPlugin ${this.base}`,
|
||||
(compilation, callback) => {
|
||||
const main = compilation.assets[`main.${this.base}`]
|
||||
const mainMap = compilation.assets[`main.${this.base}.map`]
|
||||
const vendor = compilation.assets[`vendors.${this.base}`]
|
||||
|
||||
if (main && vendor) {
|
||||
const compiledAsset = new ConcatSource(main.children[0])
|
||||
compiledAsset.add(vendor)
|
||||
compiledAsset.add(main.children[1])
|
||||
compilation.assets = {}
|
||||
compilation.assets[this.base] = compiledAsset
|
||||
} else if (main && mainMap) {
|
||||
compilation.assets = {}
|
||||
compilation.assets[this.base] = main
|
||||
compilation.assets[`${this.base}.map`] = mainMap
|
||||
}
|
||||
callback()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
48
src/.eslintrc
Normal file
48
src/.eslintrc
Normal file
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": false
|
||||
},
|
||||
"project": [
|
||||
"./tsconfig.json",
|
||||
"./test/unit/tsconfig.json",
|
||||
"./test/integration/tsconfig.json"
|
||||
]
|
||||
},
|
||||
"extends": [
|
||||
"oceanprotocol",
|
||||
"prettier/standard",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier/@typescript-eslint"
|
||||
],
|
||||
"plugins": ["@typescript-eslint", "prettier"],
|
||||
"rules": {
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"error",
|
||||
{ "multiline": { "delimiter": "none" } }
|
||||
],
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/indent": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-object-literal-type-assertion": "off",
|
||||
"@typescript-eslint/no-parameter-properties": "off",
|
||||
"no-empty": ["error", { "allowEmptyCatch": true }],
|
||||
"prefer-destructuring": ["warn"],
|
||||
"no-dupe-class-members": ["warn"],
|
||||
"no-useless-constructor": ["warn"]
|
||||
},
|
||||
"env": {
|
||||
"es6": true,
|
||||
"browser": true,
|
||||
"mocha": true
|
||||
}
|
||||
}
|
1
src/@types/node_modules.d.ts
vendored
Normal file
1
src/@types/node_modules.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
declare module '@ethereum-navigator/navigator'
|
94
src/Instantiable.abstract.ts
Normal file
94
src/Instantiable.abstract.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import Web3 from 'web3'
|
||||
import Config from './models/Config'
|
||||
import { Logger, LoggerInstance, LogLevel } from './utils'
|
||||
import { Ocean } from './ocean/Ocean'
|
||||
import { OceanFactoryABI } from './datatokens/FactoryABI'
|
||||
import { OceanDataTokenABI } from './datatokens/DatatokensABI'
|
||||
|
||||
export interface InstantiableConfig {
|
||||
ocean: Ocean
|
||||
config?: Config
|
||||
web3?: Web3
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
export function generateIntantiableConfigFromConfig(
|
||||
config: Config
|
||||
): Partial<InstantiableConfig> {
|
||||
const logLevel =
|
||||
typeof config.verbose !== 'number'
|
||||
? config.verbose
|
||||
? LogLevel.Log
|
||||
: LogLevel.None
|
||||
: (config.verbose as LogLevel)
|
||||
return {
|
||||
config,
|
||||
web3: Web3Provider.getWeb3(config),
|
||||
logger: new Logger(logLevel)
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Instantiable {
|
||||
protected get ocean() {
|
||||
if (!this._ocean) {
|
||||
this.logger.error('Ocean instance is not defined.')
|
||||
}
|
||||
return this._ocean
|
||||
}
|
||||
|
||||
protected get web3() {
|
||||
if (!this._web3) {
|
||||
this.logger.error('Web3 instance is not defined.')
|
||||
}
|
||||
return this._web3
|
||||
}
|
||||
|
||||
protected get config() {
|
||||
if (!this._config) {
|
||||
this.logger.error('Config instance is not defined.')
|
||||
}
|
||||
return this._config
|
||||
}
|
||||
|
||||
protected get logger() {
|
||||
if (!this._logger) {
|
||||
LoggerInstance.error('Logger instance is not defined.')
|
||||
LoggerInstance.error('Using default instance.')
|
||||
return LoggerInstance
|
||||
}
|
||||
return this._logger
|
||||
}
|
||||
|
||||
protected get instanceConfig(): InstantiableConfig {
|
||||
const { ocean, web3, config, logger } = this
|
||||
return { ocean, web3, config, logger }
|
||||
}
|
||||
|
||||
public static async getInstance(...args: any[]): Promise<any>
|
||||
|
||||
public static async getInstance(config: InstantiableConfig): Promise<any> {
|
||||
LoggerInstance.warn('getInstance() methods has needs to be added to child class.')
|
||||
}
|
||||
|
||||
protected static setInstanceConfig<T extends Instantiable>(
|
||||
instance: T,
|
||||
{ ocean, config, web3, logger }: InstantiableConfig
|
||||
) {
|
||||
instance._ocean = ocean
|
||||
instance._config = config
|
||||
instance._web3 = web3
|
||||
instance._logger = logger
|
||||
}
|
||||
|
||||
private _ocean: Ocean
|
||||
|
||||
private _web3: Web3
|
||||
|
||||
private _config: Config
|
||||
|
||||
private _logger: Logger
|
||||
|
||||
protected setInstanceConfig(config: InstantiableConfig) {
|
||||
Instantiable.setInstanceConfig(this, config)
|
||||
}
|
||||
}
|
421
src/aquarius/Aquarius.ts
Normal file
421
src/aquarius/Aquarius.ts
Normal file
|
@ -0,0 +1,421 @@
|
|||
import { URL } from 'whatwg-url'
|
||||
import { DDO } from '../ddo/DDO'
|
||||
import DID from '../ocean/DID'
|
||||
import { EditableMetaData } from '../ddo/MetaData'
|
||||
import { Logger } from '../utils'
|
||||
import { WebServiceConnector } from '../ocean/utils/WebServiceConnector'
|
||||
|
||||
const apiPath = '/api/v1/aquarius/assets/ddo'
|
||||
|
||||
export interface QueryResult {
|
||||
results: DDO[]
|
||||
page: number
|
||||
totalPages: number
|
||||
totalResults: number
|
||||
}
|
||||
|
||||
export interface SearchQuery {
|
||||
text?: string
|
||||
offset?: number
|
||||
page?: number
|
||||
query: { [property: string]: string | number | string[] | number[] }
|
||||
sort?: { [jsonPath: string]: number }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an interface with Aquarius.
|
||||
* Aquarius provides an off-chain database store for metadata about data assets.
|
||||
*/
|
||||
export class Aquarius {
|
||||
public fetch: WebServiceConnector
|
||||
private logger: Logger
|
||||
private aquariusUri: string
|
||||
|
||||
private get url() {
|
||||
return this.aquariusUri
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate Aquarius (independently of Ocean) for off-chain interaction.
|
||||
* @param {String} aquariusUri
|
||||
* @param {Logger} logger
|
||||
*/
|
||||
constructor(aquariusUri: string, logger: Logger) {
|
||||
this.fetch = new WebServiceConnector(logger)
|
||||
this.logger = logger
|
||||
this.aquariusUri = aquariusUri
|
||||
}
|
||||
|
||||
public async getVersionInfo() {
|
||||
return (await this.fetch.get(this.url)).json()
|
||||
}
|
||||
|
||||
public async getAccessUrl(accessToken: any, payload: any): Promise<string> {
|
||||
const accessUrl: string = await this.fetch
|
||||
.post(`${accessToken.service_endpoint}/${accessToken.resource_id}`, payload)
|
||||
.then((response: any): string => {
|
||||
if (response.ok) {
|
||||
return response.text()
|
||||
}
|
||||
this.logger.error('Failed: ', response.status, response.statusText)
|
||||
return null
|
||||
})
|
||||
.then((consumptionUrl: string): string => {
|
||||
this.logger.error('Success accessing consume endpoint: ', consumptionUrl)
|
||||
return consumptionUrl
|
||||
})
|
||||
.catch(error => {
|
||||
this.logger.error(
|
||||
'Error fetching the data asset consumption url: ',
|
||||
error
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
return accessUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* Search over the DDOs using a query.
|
||||
* @param {SearchQuery} query Query to filter the DDOs.
|
||||
* @return {Promise<QueryResult>}
|
||||
*/
|
||||
public async queryMetadata(query: SearchQuery): Promise<QueryResult> {
|
||||
const result: QueryResult = await this.fetch
|
||||
.post(`${this.url}${apiPath}/query`, JSON.stringify(query))
|
||||
.then((response: any) => {
|
||||
if (response.ok) {
|
||||
return response.json() as DDO[]
|
||||
}
|
||||
this.logger.error(
|
||||
'queryMetadata failed:',
|
||||
response.status,
|
||||
response.statusText
|
||||
)
|
||||
return this.transformResult()
|
||||
})
|
||||
.then(results => {
|
||||
return this.transformResult(results)
|
||||
})
|
||||
.catch(error => {
|
||||
this.logger.error('Error fetching querying metadata: ', error)
|
||||
return this.transformResult()
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Search over the DDOs using a query.
|
||||
* @param {SearchQuery} query Query to filter the DDOs.
|
||||
* @return {Promise<QueryResult>}
|
||||
*/
|
||||
public async queryMetadataByText(query: SearchQuery): Promise<QueryResult> {
|
||||
const fullUrl = new URL(`${this.url}${apiPath}/query`)
|
||||
fullUrl.searchParams.append('text', query.text)
|
||||
fullUrl.searchParams.append(
|
||||
'sort',
|
||||
decodeURIComponent(JSON.stringify(query.sort))
|
||||
)
|
||||
fullUrl.searchParams.append('offset', query.offset.toString())
|
||||
fullUrl.searchParams.append('page', query.page.toString())
|
||||
|
||||
const result: QueryResult = await this.fetch
|
||||
.get(fullUrl)
|
||||
.then((response: any) => {
|
||||
if (response.ok) {
|
||||
return response.json() as DDO[]
|
||||
}
|
||||
this.logger.log(
|
||||
'queryMetadataByText failed:',
|
||||
response.status,
|
||||
response.statusText
|
||||
)
|
||||
return this.transformResult()
|
||||
})
|
||||
.then(results => {
|
||||
return this.transformResult(results)
|
||||
})
|
||||
.catch(error => {
|
||||
this.logger.error('Error fetching querying metadata by text: ', error)
|
||||
return this.transformResult()
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a DDO in Aquarius.
|
||||
* @param {DDO} ddo DDO to be stored.
|
||||
* @return {Promise<DDO>} Final DDO.
|
||||
*/
|
||||
public async storeDDO(ddo: DDO): Promise<DDO> {
|
||||
const fullUrl = `${this.url}${apiPath}`
|
||||
const result: DDO = await this.fetch
|
||||
.post(fullUrl, DDO.serialize(ddo))
|
||||
.then((response: any) => {
|
||||
if (response.ok) {
|
||||
return response.json()
|
||||
}
|
||||
this.logger.error(
|
||||
'storeDDO failed:',
|
||||
response.status,
|
||||
response.statusText,
|
||||
ddo
|
||||
)
|
||||
return null as DDO
|
||||
})
|
||||
.then((response: DDO) => {
|
||||
return new DDO(response) as DDO
|
||||
})
|
||||
.catch(error => {
|
||||
this.logger.error('Error fetching querying metadata: ', error)
|
||||
return null as DDO
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a DDO by DID.
|
||||
* @param {DID | string} did DID of the asset.
|
||||
* @return {Promise<DDO>} DDO of the asset.
|
||||
*/
|
||||
public async retrieveDDO(
|
||||
did: DID | string,
|
||||
metadataServiceEndpoint?: string
|
||||
): Promise<DDO> {
|
||||
did = did && DID.parse(did)
|
||||
const fullUrl = metadataServiceEndpoint || `${this.url}${apiPath}/${did.getDid()}`
|
||||
const result = await this.fetch
|
||||
.get(fullUrl)
|
||||
.then((response: any) => {
|
||||
if (response.ok) {
|
||||
return response.json()
|
||||
}
|
||||
this.logger.log(
|
||||
'retrieveDDO failed:',
|
||||
response.status,
|
||||
response.statusText,
|
||||
did
|
||||
)
|
||||
return null as DDO
|
||||
})
|
||||
.then((response: DDO) => {
|
||||
return new DDO(response) as DDO
|
||||
})
|
||||
.catch(error => {
|
||||
this.logger.error('Error fetching querying metadata: ', error)
|
||||
return null as DDO
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
public async retrieveDDOByUrl(metadataServiceEndpoint?: string) {
|
||||
return this.retrieveDDO(undefined, metadataServiceEndpoint)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer ownership of a DDO
|
||||
* @param {DID | string} did DID of the asset to update.
|
||||
* @param {String} newOwner New owner of the DDO
|
||||
* @param {String} updated Updated field of the DDO
|
||||
* @param {String} signature Signature using updated field to verify that the consumer has rights
|
||||
* @return {Promise<String>} Result.
|
||||
*/
|
||||
public async transferOwnership(
|
||||
did: DID | string,
|
||||
newOwner: string,
|
||||
updated: string,
|
||||
signature: string
|
||||
): Promise<string> {
|
||||
did = did && DID.parse(did)
|
||||
const fullUrl = `${this.url}${apiPath}/owner/update/${did.getDid()}`
|
||||
const result = await this.fetch
|
||||
.put(
|
||||
fullUrl,
|
||||
JSON.stringify({
|
||||
signature: signature,
|
||||
updated: updated,
|
||||
newOwner: newOwner
|
||||
})
|
||||
)
|
||||
.then((response: any) => {
|
||||
if (response.ok) {
|
||||
return response.text
|
||||
}
|
||||
this.logger.log(
|
||||
'transferownership failed:',
|
||||
response.status,
|
||||
response.statusText
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
.catch(error => {
|
||||
this.logger.error('Error transfering ownership metadata: ', error)
|
||||
return null
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Compute Privacy
|
||||
* @param {DID | string} did DID of the asset to update.
|
||||
* @param {number } serviceIndex Service index
|
||||
* @param {boolean} allowRawAlgorithm Allow Raw Algorithms
|
||||
* @param {boolean} allowNetworkAccess Allow Raw Algorithms
|
||||
* @param {String[]} trustedAlgorithms Allow Raw Algorithms
|
||||
* @param {String} updated Updated field of the DDO
|
||||
* @param {String} signature Signature using updated field to verify that the consumer has rights
|
||||
* @return {Promise<String>} Result.
|
||||
*/
|
||||
public async updateComputePrivacy(
|
||||
did: DID | string,
|
||||
serviceIndex: number,
|
||||
allowRawAlgorithm: boolean,
|
||||
allowNetworkAccess: boolean,
|
||||
trustedAlgorithms: string[],
|
||||
updated: string,
|
||||
signature: string
|
||||
): Promise<string> {
|
||||
did = did && DID.parse(did)
|
||||
const fullUrl = `${this.url}${apiPath}/computePrivacy/update/${did.getDid()}`
|
||||
const result = await this.fetch
|
||||
.put(
|
||||
fullUrl,
|
||||
JSON.stringify({
|
||||
signature: signature,
|
||||
updated: updated,
|
||||
serviceIndex: serviceIndex,
|
||||
allowRawAlgorithm: allowRawAlgorithm,
|
||||
allowNetworkAccess: allowNetworkAccess,
|
||||
trustedAlgorithms: trustedAlgorithms
|
||||
})
|
||||
)
|
||||
.then((response: any) => {
|
||||
if (response.ok) {
|
||||
return response.text
|
||||
}
|
||||
this.logger.log(
|
||||
'update compute privacy failed:',
|
||||
response.status,
|
||||
response.statusText
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
.catch(error => {
|
||||
this.logger.error('Error updating compute privacy: ', error)
|
||||
return null
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Metadata for a DDO.
|
||||
* @param {did} string DID.
|
||||
* @param {newMetadata} EditableMetaData Metadata fields & new values.
|
||||
* @param {String} updated Updated field of the DDO
|
||||
* @param {String} signature Signature using updated field to verify that the consumer has rights
|
||||
* @return {Promise<String>} Result.
|
||||
*/
|
||||
public async editMetadata(
|
||||
did: DID | string,
|
||||
newMetadata: EditableMetaData,
|
||||
updated: string,
|
||||
signature: string
|
||||
): Promise<string> {
|
||||
did = did && DID.parse(did)
|
||||
const fullUrl = `${this.url}${apiPath}/metadata/${did.getDid()}`
|
||||
const data = Object()
|
||||
if (newMetadata.description != null) data.description = newMetadata.description
|
||||
if (newMetadata.title != null) data.title = newMetadata.title
|
||||
if (newMetadata.servicePrices != null)
|
||||
data.servicePrices = newMetadata.servicePrices
|
||||
if (newMetadata.links != null) data.links = newMetadata.links
|
||||
data.updated = updated
|
||||
data.signature = signature
|
||||
const result = await this.fetch
|
||||
.put(fullUrl, JSON.stringify(data))
|
||||
.then((response: any) => {
|
||||
if (response.ok) {
|
||||
return response.text
|
||||
}
|
||||
this.logger.log(
|
||||
'editMetaData failed:',
|
||||
response.status,
|
||||
response.statusText
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
.catch(error => {
|
||||
this.logger.error('Error transfering ownership metadata: ', error)
|
||||
return null
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Retire a DDO (Delete)
|
||||
* @param {DID | string} did DID of the asset to update.
|
||||
* @param {String} updated Updated field of the DDO
|
||||
* @param {String} signature Signature using updated field to verify that the consumer has rights
|
||||
* @return {Promise<String>} Result.
|
||||
*/
|
||||
public async retire(
|
||||
did: DID | string,
|
||||
updated: string,
|
||||
signature: string
|
||||
): Promise<string> {
|
||||
did = did && DID.parse(did)
|
||||
const fullUrl = `${this.url}${apiPath}/${did.getDid()}`
|
||||
const result = await this.fetch
|
||||
.delete(
|
||||
fullUrl,
|
||||
JSON.stringify({
|
||||
signature: signature,
|
||||
updated: updated
|
||||
})
|
||||
)
|
||||
.then((response: any) => {
|
||||
if (response.ok) {
|
||||
return response.text
|
||||
}
|
||||
this.logger.log('retire failed:', response.status, response.statusText)
|
||||
return null
|
||||
})
|
||||
|
||||
.catch(error => {
|
||||
this.logger.error('Error transfering ownership metadata: ', error)
|
||||
return null
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
public getServiceEndpoint(did: DID) {
|
||||
return `${this.url}/api/v1/aquarius/assets/ddo/did:op:${did.getId()}`
|
||||
}
|
||||
|
||||
private transformResult(
|
||||
{ results, page, total_pages: totalPages, total_results: totalResults }: any = {
|
||||
result: [],
|
||||
page: 0,
|
||||
total_pages: 0, // eslint-disable-line @typescript-eslint/camelcase
|
||||
total_results: 0 // eslint-disable-line @typescript-eslint/camelcase
|
||||
}
|
||||
): QueryResult {
|
||||
return {
|
||||
results: (results || []).map(ddo => new DDO(ddo as DDO)),
|
||||
page,
|
||||
totalPages,
|
||||
totalResults
|
||||
}
|
||||
}
|
||||
}
|
242
src/brizo/Brizo.ts
Normal file
242
src/brizo/Brizo.ts
Normal file
|
@ -0,0 +1,242 @@
|
|||
import { File, MetaDataAlgorithm } from '../ddo/MetaData'
|
||||
import Account from '../ocean/Account'
|
||||
import { noZeroX } from '../utils'
|
||||
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
|
||||
import { DDO } from '../ddo/DDO'
|
||||
import { ServiceType } from '../ddo/Service'
|
||||
import { ComputeJob, Output } from '../ocean/OceanCompute'
|
||||
|
||||
const apiPath = '/api/v1/brizo/services'
|
||||
|
||||
/**
|
||||
* Provides a interface with Brizo.
|
||||
* Brizo is the technical component executed by the Publishers allowing to them to provide extended data services.
|
||||
*/
|
||||
export class Brizo extends Instantiable {
|
||||
private get url() {
|
||||
return this.config.brizoUri
|
||||
}
|
||||
|
||||
constructor(config: InstantiableConfig) {
|
||||
super()
|
||||
this.setInstanceConfig(config)
|
||||
}
|
||||
|
||||
public async getVersionInfo() {
|
||||
return (await this.ocean.utils.fetch.get(this.url)).json()
|
||||
}
|
||||
|
||||
public getPurchaseEndpoint() {
|
||||
return `${this.url}${apiPath}/access/initialize`
|
||||
}
|
||||
|
||||
public getConsumeEndpoint() {
|
||||
return `${this.url}${apiPath}/consume`
|
||||
}
|
||||
|
||||
public getEncryptEndpoint() {
|
||||
return `${this.url}${apiPath}/publish`
|
||||
}
|
||||
|
||||
public getComputeEndpoint() {
|
||||
return `${this.url}${apiPath}/compute`
|
||||
}
|
||||
|
||||
public async getEndpointFromAgreement(
|
||||
type: ServiceType,
|
||||
agreementId: string
|
||||
): Promise<string> {
|
||||
const { assets, keeper } = this.ocean
|
||||
const { did } = await keeper.agreementStoreManager.getAgreement(agreementId)
|
||||
const ddo: DDO = await assets.resolve(did)
|
||||
const { serviceEndpoint } = ddo.findServiceByType(type)
|
||||
|
||||
return serviceEndpoint
|
||||
}
|
||||
|
||||
public async initializeServiceAgreement(
|
||||
did: string,
|
||||
serviceAgreementId: string,
|
||||
serviceIndex: number,
|
||||
signature: string,
|
||||
consumerAddress: string
|
||||
): Promise<any> {
|
||||
const args = {
|
||||
did,
|
||||
serviceAgreementId,
|
||||
serviceIndex,
|
||||
signature,
|
||||
consumerAddress
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.ocean.utils.fetch.post(
|
||||
this.getPurchaseEndpoint(),
|
||||
decodeURI(JSON.stringify(args))
|
||||
)
|
||||
} catch (e) {
|
||||
this.logger.error(e)
|
||||
throw new Error('HTTP request failed')
|
||||
}
|
||||
}
|
||||
|
||||
public async consumeService(
|
||||
agreementId: string,
|
||||
serviceEndpoint: string,
|
||||
account: Account,
|
||||
files: File[],
|
||||
destination: string,
|
||||
index: number = -1
|
||||
): Promise<string> {
|
||||
const signature = await this.createSignature(account, agreementId)
|
||||
const filesPromises = files
|
||||
.filter((_, i) => index === -1 || i === index)
|
||||
.map(async ({ index: i }) => {
|
||||
let consumeUrl = serviceEndpoint
|
||||
consumeUrl += `?index=${i}`
|
||||
consumeUrl += `&serviceAgreementId=${noZeroX(agreementId)}`
|
||||
consumeUrl += `&consumerAddress=${account.getId()}`
|
||||
consumeUrl += `&signature=${signature}`
|
||||
|
||||
try {
|
||||
await this.ocean.utils.fetch.downloadFile(consumeUrl, destination, i)
|
||||
} catch (e) {
|
||||
this.logger.error('Error consuming assets')
|
||||
this.logger.error(e)
|
||||
throw e
|
||||
}
|
||||
})
|
||||
await Promise.all(filesPromises)
|
||||
return destination
|
||||
}
|
||||
|
||||
public async compute(
|
||||
method: string,
|
||||
serviceAgreementId: string,
|
||||
consumerAccount: Account,
|
||||
algorithmDid?: string,
|
||||
algorithmMeta?: MetaDataAlgorithm,
|
||||
jobId?: string,
|
||||
output?: Output
|
||||
): Promise<ComputeJob | ComputeJob[]> {
|
||||
const address = consumerAccount.getId()
|
||||
|
||||
let signatureMessage = address
|
||||
signatureMessage += jobId || ''
|
||||
signatureMessage += (serviceAgreementId && `${noZeroX(serviceAgreementId)}`) || ''
|
||||
const signature = await this.createHashSignature(
|
||||
consumerAccount,
|
||||
signatureMessage
|
||||
)
|
||||
|
||||
const serviceEndpoint = serviceAgreementId
|
||||
? await this.getEndpointFromAgreement('compute', serviceAgreementId)
|
||||
: this.getComputeEndpoint()
|
||||
|
||||
if (!serviceEndpoint) {
|
||||
throw new Error(
|
||||
'Computing on asset failed, service definition is missing the `serviceEndpoint`.'
|
||||
)
|
||||
}
|
||||
|
||||
// construct Brizo URL
|
||||
let url = serviceEndpoint
|
||||
url += `?signature=${signature}`
|
||||
url += `&consumerAddress=${address}`
|
||||
url += `&serviceAgreementId=${noZeroX(serviceAgreementId)}`
|
||||
url += (algorithmDid && `&algorithmDid=${algorithmDid}`) || ''
|
||||
url +=
|
||||
(algorithmMeta &&
|
||||
`&algorithmMeta=${encodeURIComponent(JSON.stringify(algorithmMeta))}`) ||
|
||||
''
|
||||
url += (output && `&output=${JSON.stringify(output)}`) || ''
|
||||
url += (jobId && `&jobId=${jobId}`) || ''
|
||||
|
||||
// switch fetch method
|
||||
let fetch
|
||||
|
||||
switch (method) {
|
||||
case 'post':
|
||||
fetch = this.ocean.utils.fetch.post(url, '')
|
||||
break
|
||||
case 'put':
|
||||
fetch = this.ocean.utils.fetch.put(url, '')
|
||||
break
|
||||
case 'delete':
|
||||
fetch = this.ocean.utils.fetch.delete(url)
|
||||
break
|
||||
default:
|
||||
fetch = this.ocean.utils.fetch.get(url)
|
||||
break
|
||||
}
|
||||
|
||||
const result = await fetch
|
||||
.then((response: any) => {
|
||||
if (response.ok) {
|
||||
return response.json()
|
||||
}
|
||||
|
||||
this.logger.error(
|
||||
'Compute job failed:',
|
||||
response.status,
|
||||
response.statusText
|
||||
)
|
||||
|
||||
return null
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
this.logger.error('Error with compute job')
|
||||
this.logger.error(error.message)
|
||||
throw error
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
public async createSignature(account: Account, agreementId: string): Promise<string> {
|
||||
const signature =
|
||||
(await account.getToken()) ||
|
||||
(await this.ocean.utils.signature.signText(
|
||||
noZeroX(agreementId),
|
||||
account.getId()
|
||||
))
|
||||
|
||||
return signature
|
||||
}
|
||||
|
||||
public async createHashSignature(account: Account, message: string): Promise<string> {
|
||||
const signature =
|
||||
(await account.getToken()) ||
|
||||
(await this.ocean.utils.signature.signWithHash(message, account.getId()))
|
||||
|
||||
return signature
|
||||
}
|
||||
|
||||
public async encrypt(
|
||||
did: string,
|
||||
signature: string,
|
||||
document: any,
|
||||
publisher: string
|
||||
): Promise<string> {
|
||||
const args = {
|
||||
documentId: did,
|
||||
signature,
|
||||
document: JSON.stringify(document),
|
||||
publisherAddress: publisher
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.ocean.utils.fetch.post(
|
||||
this.getEncryptEndpoint(),
|
||||
decodeURI(JSON.stringify(args))
|
||||
)
|
||||
if (!response.ok) {
|
||||
throw new Error('HTTP request failed')
|
||||
}
|
||||
return await response.text()
|
||||
} catch (e) {
|
||||
this.logger.error(e)
|
||||
throw new Error('HTTP request failed')
|
||||
}
|
||||
}
|
||||
}
|
120
src/datatokens/Datatokens.ts
Normal file
120
src/datatokens/Datatokens.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
import Account from '../ocean/Account'
|
||||
|
||||
/**
|
||||
* Provides a interface to DataTokens
|
||||
|
||||
*/
|
||||
export class DataTokens {
|
||||
public factoryAddress: string
|
||||
public factoryABI: object
|
||||
public datatokensABI: object
|
||||
public web3: any
|
||||
|
||||
/**
|
||||
* Instantiate DataTokens (independently of Ocean).
|
||||
* @param {String} factoryAddress
|
||||
* @param {Object} factoryABI
|
||||
* @param {Object} datatokensABI
|
||||
* @param {Object} web3
|
||||
|
||||
*/
|
||||
constructor(
|
||||
factoryAddress: string,
|
||||
factoryABI: object,
|
||||
datatokensABI: object,
|
||||
web3: any
|
||||
) {
|
||||
this.factoryAddress = factoryAddress
|
||||
this.factoryABI = factoryABI
|
||||
this.datatokensABI = datatokensABI
|
||||
this.web3 = web3
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new datatoken
|
||||
* @param {String} metaDataStoreURI
|
||||
* @param {Account} account
|
||||
* @return {Promise<string>} datatoken address
|
||||
*/
|
||||
public async create(metaDataStoreURI: string, account: Account): Promise<string> {
|
||||
// TO DO
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve
|
||||
* @param {String} dataTokenAddress
|
||||
* @param {String} toAddress
|
||||
* @param {Number} amount
|
||||
* @param {Account} account
|
||||
* @return {Promise<string>} transactionId
|
||||
*/
|
||||
public async approve(
|
||||
dataTokenAddress: string,
|
||||
toAddress: string,
|
||||
amount: number,
|
||||
account: Account
|
||||
): Promise<string> {
|
||||
// TO DO
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint
|
||||
* @param {String} dataTokenAddress
|
||||
* @param {Account} account
|
||||
* @param {Number} amount
|
||||
* @param {String} toAddress - only if toAddress is different from the minter
|
||||
* @return {Promise<string>} transactionId
|
||||
*/
|
||||
public async mint(
|
||||
dataTokenAddress: string,
|
||||
account: Account,
|
||||
amount: number,
|
||||
toAddress?: string
|
||||
): Promise<string> {
|
||||
// TO DO
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer from Account to Address
|
||||
* @param {String} dataTokenAddress
|
||||
* @param {String} toAddress
|
||||
* @param {Number} amount
|
||||
* @param {Account} account
|
||||
* @return {Promise<string>} transactionId
|
||||
*/
|
||||
public async transfer(
|
||||
dataTokenAddress: string,
|
||||
toAddress: string,
|
||||
amount: number,
|
||||
account: Account
|
||||
): Promise<string> {
|
||||
// TO DO
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer from Address to Account (needs an Approve operation before)
|
||||
* @param {String} dataTokenAddress
|
||||
* @param {String} fromAddress
|
||||
* @param {Number} amount
|
||||
* @param {Account} account
|
||||
* @return {Promise<string>} transactionId
|
||||
*/
|
||||
public async transferFrom(
|
||||
dataTokenAddress: string,
|
||||
fromAddress: string,
|
||||
amount: number,
|
||||
account: Account
|
||||
): Promise<string> {
|
||||
// TO DO
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Account Balance for datatoken
|
||||
* @param {String} dataTokenAddress
|
||||
* @param {Account} account
|
||||
* @return {Promise<string>} transactionId
|
||||
*/
|
||||
public async balance(dataTokenAddress: string, account: Account): Promise<number> {
|
||||
// TO DO
|
||||
}
|
||||
}
|
1
src/datatokens/DatatokensABI.ts
Normal file
1
src/datatokens/DatatokensABI.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const OceanDataTokenABI = { }
|
1
src/datatokens/FactoryABI.ts
Normal file
1
src/datatokens/FactoryABI.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const OceanFactoryABI = {}
|
1
src/datatokens/FactoryAbi.ts
Normal file
1
src/datatokens/FactoryAbi.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const OceanFactoryABi = {}
|
4
src/ddo/Authentication.ts
Normal file
4
src/ddo/Authentication.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export interface Authentication {
|
||||
type: string
|
||||
publicKey: string
|
||||
}
|
160
src/ddo/DDO.ts
Normal file
160
src/ddo/DDO.ts
Normal file
|
@ -0,0 +1,160 @@
|
|||
import Web3Provider from '../keeper/Web3Provider'
|
||||
import { Ocean } from '../ocean/Ocean'
|
||||
import { Authentication } from './Authentication'
|
||||
import { Proof } from './Proof'
|
||||
import { PublicKey } from './PublicKey'
|
||||
import { Service, ServiceType } from './Service'
|
||||
|
||||
/**
|
||||
* DID Descriptor Object.
|
||||
* Contains all the data related to an asset.
|
||||
*/
|
||||
export class DDO {
|
||||
/**
|
||||
* Serializes the DDO object.
|
||||
* @param {DDO} DDO to be serialized.
|
||||
* @return {string} DDO serialized.
|
||||
*/
|
||||
public static serialize(ddo: DDO): string {
|
||||
return JSON.stringify(ddo, null, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the DDO object.
|
||||
* @param {DDO} DDO to be deserialized.
|
||||
* @return {string} DDO deserialized.
|
||||
*/
|
||||
public static deserialize(ddoString: string): DDO {
|
||||
const ddo = JSON.parse(ddoString)
|
||||
|
||||
return new DDO(ddo)
|
||||
}
|
||||
|
||||
public '@context': string = 'https://w3id.org/did/v1'
|
||||
|
||||
/**
|
||||
* DID, descentralized ID.
|
||||
* @type {string}
|
||||
*/
|
||||
public id: string = null
|
||||
|
||||
public created: string
|
||||
|
||||
public updated: string
|
||||
|
||||
public dtAddress: string
|
||||
|
||||
public publicKey: PublicKey[] = []
|
||||
|
||||
public authentication: Authentication[] = []
|
||||
|
||||
public service: Service[] = []
|
||||
|
||||
public proof: Proof
|
||||
|
||||
public constructor(ddo: Partial<DDO> = {}) {
|
||||
Object.assign(this, ddo, {
|
||||
created:
|
||||
(ddo && ddo.created) || new Date().toISOString().replace(/\.[0-9]{3}/, '')
|
||||
})
|
||||
}
|
||||
|
||||
public shortId(): string {
|
||||
return this.id.replace('did:op:', '')
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a service of a DDO by index.
|
||||
* @param {number} Service index.
|
||||
* @return {Service} Service.
|
||||
*/
|
||||
public findServiceById<T extends ServiceType>(index: number): Service<T> {
|
||||
if (isNaN(index)) {
|
||||
throw new Error('index is not set')
|
||||
}
|
||||
|
||||
const service = this.service.find(s => s.index === index)
|
||||
|
||||
return service as Service<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a service of a DDO by type.
|
||||
* @param {string} serviceType Service type.
|
||||
* @return {Service} Service.
|
||||
*/
|
||||
public findServiceByType<T extends ServiceType>(serviceType: T): Service<T> {
|
||||
if (!serviceType) {
|
||||
throw new Error('serviceType not set')
|
||||
}
|
||||
|
||||
return this.service.find(s => s.type === serviceType) as Service<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the checksum using the current content.
|
||||
* @return {string[]} DDO checksum.
|
||||
*/
|
||||
public getChecksum(): string {
|
||||
const { attributes } = this.findServiceByType('metadata')
|
||||
const { files, name, author, license } = attributes.main
|
||||
|
||||
const values = [
|
||||
...(files || []).map(({ checksum }) => checksum).filter(_ => !!_),
|
||||
name,
|
||||
author,
|
||||
license,
|
||||
this.id
|
||||
]
|
||||
|
||||
return Web3Provider.getWeb3()
|
||||
.utils.sha3(values.join(''))
|
||||
.replace(/^0x([a-f0-9]{64})(:!.+)?$/i, '0x$1')
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates proof using personal sing.
|
||||
* @param {Ocean} ocean Ocean instance.
|
||||
* @param {string} publicKey Public key to be used on personal sign.
|
||||
* @param {string} password Password if it's required.
|
||||
* @return {Promise<Proof>} Proof object.
|
||||
*/
|
||||
public async generateProof(
|
||||
ocean: Ocean,
|
||||
publicKey: string,
|
||||
password?: string
|
||||
): Promise<Proof> {
|
||||
const checksum = this.getChecksum()
|
||||
|
||||
const signature = await ocean.utils.signature.signText(
|
||||
checksum,
|
||||
publicKey,
|
||||
password
|
||||
)
|
||||
|
||||
return {
|
||||
created: new Date().toISOString().replace(/\.[0-9]{3}/, ''),
|
||||
creator: publicKey,
|
||||
type: 'DDOIntegritySignature',
|
||||
signatureValue: signature
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and adds a proof using personal sing on the DDO.
|
||||
* @param {Ocean} ocean Ocean instance.
|
||||
* @param {string} publicKey Public key to be used on personal sign.
|
||||
* @param {string} password Password if it's required.
|
||||
* @return {Promise<Proof>} Proof object.
|
||||
*/
|
||||
public async addProof(
|
||||
ocean: Ocean,
|
||||
publicKey: string,
|
||||
password?: string
|
||||
): Promise<void> {
|
||||
if (this.proof) {
|
||||
throw new Error('Proof already exists')
|
||||
}
|
||||
this.proof = await this.generateProof(ocean, publicKey, password)
|
||||
}
|
||||
}
|
303
src/ddo/MetaData.ts
Normal file
303
src/ddo/MetaData.ts
Normal file
|
@ -0,0 +1,303 @@
|
|||
export interface File {
|
||||
/**
|
||||
* File name.
|
||||
* @type {string}
|
||||
*/
|
||||
name?: string
|
||||
|
||||
/**
|
||||
* File URL.
|
||||
* @type {string}
|
||||
*/
|
||||
url: string
|
||||
|
||||
/**
|
||||
* File index.
|
||||
* @type {number}
|
||||
*/
|
||||
index?: number
|
||||
|
||||
/**
|
||||
* File format, if applicable.
|
||||
* @type {string}
|
||||
* @example "text/csv"
|
||||
*/
|
||||
contentType: string
|
||||
|
||||
/**
|
||||
* File checksum.
|
||||
* @type {[type]}
|
||||
*/
|
||||
checksum?: string
|
||||
|
||||
/**
|
||||
* Checksum hash algorithm.
|
||||
* @type {[type]}
|
||||
*/
|
||||
checksumType?: string
|
||||
|
||||
/**
|
||||
* File content length.
|
||||
* @type {[type]}
|
||||
*/
|
||||
contentLength?: string
|
||||
|
||||
/**
|
||||
* Resource ID (depending on the source).
|
||||
* @type {[type]}
|
||||
*/
|
||||
resourceId?: string
|
||||
|
||||
/**
|
||||
* File encoding.
|
||||
* @type {string}
|
||||
* @example "UTF-8"
|
||||
*/
|
||||
encoding?: string
|
||||
|
||||
/**
|
||||
* File compression (e.g. no, gzip, bzip2, etc).
|
||||
* @type {string}
|
||||
* @example "zip"
|
||||
*/
|
||||
compression?: string
|
||||
}
|
||||
|
||||
export interface MetaDataAlgorithm {
|
||||
url?: string
|
||||
rawcode?: string
|
||||
language?: string
|
||||
format?: string
|
||||
version?: string
|
||||
container: {
|
||||
entrypoint: string
|
||||
image: string
|
||||
tag: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main attributes of assets metadata.
|
||||
* @see https://github.com/oceanprotocol/OEPs/tree/master/8
|
||||
*/
|
||||
export interface MetaDataMain {
|
||||
/**
|
||||
* Descriptive name of the Asset.
|
||||
* @type {string}
|
||||
* @example "UK Weather information 2011"
|
||||
*/
|
||||
name: string
|
||||
|
||||
/**
|
||||
* Type of the Asset. Helps to filter by the type of asset ("dataset" or "algorithm").
|
||||
* @type {string}
|
||||
* @example "dataset"
|
||||
*/
|
||||
type: 'dataset' | 'algorithm'
|
||||
|
||||
/**
|
||||
* The date on which the asset was created by the originator in
|
||||
* ISO 8601 format, Coordinated Universal Time.
|
||||
* @type {string}
|
||||
* @example "2019-01-31T08:38:32Z"
|
||||
*/
|
||||
dateCreated: string
|
||||
|
||||
/**
|
||||
* The date on which the asset DDO was registered into the metadata store.
|
||||
* This value is created automatically by Aquarius upon registering,
|
||||
* so this value can't be set.
|
||||
* @type {string}
|
||||
* @example "2019-01-31T08:38:32Z"
|
||||
*/
|
||||
datePublished?: string
|
||||
|
||||
/**
|
||||
* Name of the entity generating this data (e.g. Tfl, Disney Corp, etc.).
|
||||
* @type {string}
|
||||
* @example "Met Office"
|
||||
*/
|
||||
author: string
|
||||
|
||||
/**
|
||||
* Short name referencing the license of the asset (e.g. Public Domain, CC-0, CC-BY, No License Specified, etc. ).
|
||||
* If it's not specified, the following value will be added: "No License Specified".
|
||||
* @type {string}
|
||||
* @example "CC-BY"
|
||||
*/
|
||||
license: string
|
||||
|
||||
/**
|
||||
* Price of the asset in vodka (attoOCEAN). It must be an integer encoded as a string.
|
||||
* @type {string}
|
||||
* @example "1000000000000000000"
|
||||
*/
|
||||
price: string
|
||||
|
||||
/**
|
||||
* Array of File objects including the encrypted file urls and some additional information.
|
||||
* @type {File[]}
|
||||
*/
|
||||
files: File[]
|
||||
|
||||
/**
|
||||
* Metadata used only for assets with type `algorithm`.
|
||||
* @type {MetaDataAlgorithm}
|
||||
*/
|
||||
algorithm?: MetaDataAlgorithm
|
||||
}
|
||||
|
||||
/**
|
||||
* Curation attributes of Assets Metadata.
|
||||
* @see https://github.com/oceanprotocol/OEPs/tree/master/8
|
||||
*/
|
||||
export interface Curation {
|
||||
/**
|
||||
* Decimal value between 0 and 1. 0 is the default value.
|
||||
* @type {number}
|
||||
* @example 0.93
|
||||
*/
|
||||
rating: number
|
||||
|
||||
/**
|
||||
* Number of votes. 0 is the default value.
|
||||
* @type {number}
|
||||
* @example 123
|
||||
*/
|
||||
numVotes: number
|
||||
|
||||
/**
|
||||
* Schema applied to calculate the rating.
|
||||
* @type {string}
|
||||
* @example "Binary Voting"
|
||||
*/
|
||||
schema?: string
|
||||
|
||||
/**
|
||||
* Flag unsuitable content.
|
||||
* @type {boolean}
|
||||
* @example true
|
||||
*/
|
||||
isListed?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional Information of Assets Metadata.
|
||||
* @see https://github.com/oceanprotocol/OEPs/tree/master/8#additional-information
|
||||
*/
|
||||
export interface AdditionalInformation {
|
||||
/**
|
||||
* Details of what the resource is. For a dataset, this attribute
|
||||
* explains what the data represents and what it can be used for.
|
||||
* @type {string}
|
||||
* @example "Weather information of UK including temperature and humidity"
|
||||
*/
|
||||
description?: string
|
||||
|
||||
/**
|
||||
* The party holding the legal copyright. Empty by default.
|
||||
* @type {string}
|
||||
* @example "Met Office"
|
||||
*/
|
||||
copyrightHolder?: string
|
||||
|
||||
/**
|
||||
* Example of the concept of this asset. This example is part
|
||||
* of the metadata, not an external link.
|
||||
* @type {string}
|
||||
* @example "423432fsd,51.509865,-0.118092,2011-01-01T10:55:11+00:00,7.2,68"
|
||||
*/
|
||||
workExample?: string
|
||||
|
||||
/**
|
||||
* Mapping of links for data samples, or links to find out more information.
|
||||
* Links may be to either a URL or another Asset. We expect marketplaces to
|
||||
* converge on agreements of typical formats for linked data: The Ocean Protocol
|
||||
* itself does not mandate any specific formats as these requirements are likely
|
||||
* to be domain-specific.
|
||||
* @type {any[]}
|
||||
* @example
|
||||
* [
|
||||
* {
|
||||
* anotherSample: "http://data.ceda.ac.uk/badc/ukcp09/data/gridded-land-obs/gridded-land-obs-daily/",
|
||||
* },
|
||||
* {
|
||||
* fieldsDescription: "http://data.ceda.ac.uk/badc/ukcp09/",
|
||||
* },
|
||||
* ]
|
||||
*/
|
||||
links?: { [name: string]: string }[]
|
||||
|
||||
/**
|
||||
* The language of the content. Please use one of the language
|
||||
* codes from the {@link https://tools.ietf.org/html/bcp47 IETF BCP 47 standard}.
|
||||
* @type {String}
|
||||
* @example "en"
|
||||
*/
|
||||
inLanguage?: string
|
||||
|
||||
/**
|
||||
* Categories used to describe this content. Empty by default.
|
||||
* @type {string[]}
|
||||
* @example ["Economy", "Data Science"]
|
||||
*/
|
||||
categories?: string[]
|
||||
|
||||
/**
|
||||
* Keywords or tags used to describe this content. Empty by default.
|
||||
* @type {string[]}
|
||||
* @example ["weather", "uk", "2011", "temperature", "humidity"]
|
||||
*/
|
||||
tags?: string[]
|
||||
|
||||
/**
|
||||
* An indication of update latency - i.e. How often are updates expected (seldom,
|
||||
* annually, quarterly, etc.), or is the resource static that is never expected
|
||||
* to get updated.
|
||||
* @type {string}
|
||||
* @example "yearly"
|
||||
*/
|
||||
updateFrequency?: string
|
||||
|
||||
/**
|
||||
* A link to machine-readable structured markup (such as ttl/json-ld/rdf)
|
||||
* describing the dataset.
|
||||
* @type {StructuredMarkup[]}
|
||||
*/
|
||||
structuredMarkup?: {
|
||||
uri: string
|
||||
mediaType: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface MetaData {
|
||||
main: MetaDataMain
|
||||
encryptedFiles?: string
|
||||
additionalInformation?: AdditionalInformation
|
||||
curation?: Curation
|
||||
}
|
||||
/** Warning. serviceIndex is the index of a services in Services array, and not service.index attribute.
|
||||
Let's assume that you have the following services array:
|
||||
[
|
||||
{"index":1,"type":"access","main":{"price":3}},
|
||||
{"index":0,"type":"compute","main":{"price":1}}
|
||||
]
|
||||
then calling update with { serviceIndex:1,price:2} will update the 'compute' service, and not the access one
|
||||
**/
|
||||
export interface ServicePrices {
|
||||
serviceIndex: number
|
||||
price: string
|
||||
}
|
||||
|
||||
export interface EditableMetaDataLinks {
|
||||
name: string
|
||||
url: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface EditableMetaData {
|
||||
description?: string
|
||||
title?: string
|
||||
links?: EditableMetaDataLinks[]
|
||||
servicePrices?: ServicePrices[]
|
||||
}
|
6
src/ddo/Proof.ts
Normal file
6
src/ddo/Proof.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export interface Proof {
|
||||
type: string
|
||||
created: string
|
||||
creator: string
|
||||
signatureValue: string
|
||||
}
|
32
src/ddo/PublicKey.ts
Normal file
32
src/ddo/PublicKey.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Public key data.
|
||||
*/
|
||||
export interface PublicKey {
|
||||
/**
|
||||
* ID of the key.
|
||||
* @type {string}
|
||||
* @example "did:op:123456789abcdefghi#keys-1"
|
||||
*/
|
||||
id: string
|
||||
|
||||
/**
|
||||
* Type of key.
|
||||
* @type {string}
|
||||
*/
|
||||
type:
|
||||
| 'Ed25519VerificationKey2018'
|
||||
| 'RsaVerificationKey2018'
|
||||
| 'EdDsaSAPublicKeySecp256k1'
|
||||
| 'EthereumECDSAKey'
|
||||
|
||||
/**
|
||||
* Key owner.
|
||||
* @type {string}
|
||||
* @example "did:op:123456789abcdefghi"
|
||||
*/
|
||||
owner: string
|
||||
|
||||
publicKeyPem?: string
|
||||
publicKeyBase58?: string
|
||||
publicKeyHex?: string
|
||||
}
|
95
src/ddo/Service.ts
Normal file
95
src/ddo/Service.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
import { MetaData } from './MetaData'
|
||||
|
||||
export type ServiceType = 'authorization' | 'metadata' | 'access' | 'compute'
|
||||
|
||||
export interface ServiceCommon {
|
||||
type: ServiceType
|
||||
index: number
|
||||
serviceEndpoint?: string
|
||||
attributes: ServiceCommonAttributes
|
||||
}
|
||||
|
||||
export interface ServiceCommonAttributes {
|
||||
main: { [key: string]: any }
|
||||
additionalInformation?: { [key: string]: any }
|
||||
}
|
||||
|
||||
export interface ServiceAccessAttributes extends ServiceCommonAttributes {
|
||||
main: {
|
||||
creator: string
|
||||
name: string
|
||||
datePublished: string
|
||||
dtCost: number
|
||||
timeout: number
|
||||
}
|
||||
}
|
||||
export interface ServiceComputePrivacy {
|
||||
allowRawAlgorithm: boolean
|
||||
allowNetworkAccess: boolean
|
||||
trustedAlgorithms: string[]
|
||||
}
|
||||
|
||||
export interface ServiceComputeAttributes extends ServiceCommonAttributes {
|
||||
main: {
|
||||
creator: string
|
||||
datePublished: string
|
||||
price: string
|
||||
timeout: number
|
||||
provider?: ServiceComputeProvider
|
||||
name: string
|
||||
privacy?: ServiceComputePrivacy
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceComputeProvider {
|
||||
type: string
|
||||
description: string
|
||||
environment: {
|
||||
cluster: {
|
||||
type: string
|
||||
url: string
|
||||
}
|
||||
supportedContainers: {
|
||||
image: string
|
||||
tag: string
|
||||
checksum: string
|
||||
}[]
|
||||
supportedServers: {
|
||||
serverId: string
|
||||
serverType: string
|
||||
price: string
|
||||
cpu: string
|
||||
gpu: string
|
||||
memory: string
|
||||
disk: string
|
||||
maxExecutionTime: number
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceMetadata extends ServiceCommon {
|
||||
type: 'metadata'
|
||||
attributes: MetaData
|
||||
}
|
||||
|
||||
export interface ServiceAccess extends ServiceCommon {
|
||||
type: 'access'
|
||||
templateId?: string
|
||||
attributes: ServiceAccessAttributes
|
||||
}
|
||||
|
||||
export interface ServiceCompute extends ServiceCommon {
|
||||
type: 'compute'
|
||||
templateId?: string
|
||||
attributes: ServiceComputeAttributes
|
||||
}
|
||||
|
||||
export type Service<T extends ServiceType | 'default' = 'default'> = T extends 'metadata'
|
||||
? ServiceMetadata
|
||||
: T extends 'access'
|
||||
? ServiceAccess
|
||||
: T extends 'compute'
|
||||
? ServiceCompute
|
||||
: T extends 'default'
|
||||
? ServiceCommon
|
||||
: ServiceCommon
|
1
src/index.ts
Normal file
1
src/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './lib'
|
25
src/lib.ts
Normal file
25
src/lib.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import Config from './models/Config'
|
||||
import Account from './ocean/Account'
|
||||
import DID from './ocean/DID'
|
||||
import { Ocean } from './ocean/Ocean'
|
||||
import { LoggerInstance as Logger } from './utils/Logger'
|
||||
import { Aquarius } from './aquarius/Aquarius'
|
||||
import { DataTokens } from './datatokens/Datatokens'
|
||||
|
||||
import * as utils from './utils'
|
||||
|
||||
// Exports
|
||||
export * from './ddo/DDO'
|
||||
export * from './ddo/MetaData'
|
||||
|
||||
export { CreateProgressStep } from './ocean/OceanAssets'
|
||||
export { ComputeJob, ComputeJobStatus } from './ocean/OceanCompute'
|
||||
export { OrderProgressStep } from './ocean/utils/ServiceUtils'
|
||||
export {
|
||||
OceanPlatformTechStatus,
|
||||
OceanPlatformTech,
|
||||
OceanPlatformKeeperTech,
|
||||
OceanPlatformVersions
|
||||
} from './ocean/OceanVersions'
|
||||
|
||||
export { Ocean, Account, Config, DID, Logger, Aquarius, DataTokens, utils }
|
9
src/models/AccessStatus.ts
Normal file
9
src/models/AccessStatus.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
enum AccessStatus {
|
||||
Requested,
|
||||
Committed,
|
||||
Delivered,
|
||||
Verified,
|
||||
Revoked
|
||||
}
|
||||
|
||||
export default AccessStatus
|
5
src/models/Balance.ts
Normal file
5
src/models/Balance.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default class Balance {
|
||||
public eth: number
|
||||
|
||||
public ocn: number
|
||||
}
|
65
src/models/Config.ts
Normal file
65
src/models/Config.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { LogLevel } from '../utils/Logger'
|
||||
export { LogLevel } from '../utils/Logger'
|
||||
|
||||
export class Config {
|
||||
/**
|
||||
* Aquarius URL.
|
||||
* @type {string}
|
||||
*/
|
||||
public aquariusUri: string
|
||||
|
||||
/**
|
||||
* Brizo URL.
|
||||
* @type {string}
|
||||
*/
|
||||
public brizoUri: string
|
||||
|
||||
/**
|
||||
* Web3 Provider.
|
||||
* @type {any}
|
||||
*/
|
||||
public web3Provider: any
|
||||
|
||||
/**
|
||||
* Factory address
|
||||
* @type {string}
|
||||
*/
|
||||
public factoryAddress: string
|
||||
|
||||
/**
|
||||
* Factory ABI
|
||||
* @type {string}
|
||||
*/
|
||||
public factoryABI: object
|
||||
|
||||
/**
|
||||
* datatokens ABI
|
||||
* @type {string}
|
||||
*/
|
||||
public datatokensABI: object
|
||||
|
||||
/**
|
||||
* Log level.
|
||||
* @type {boolean | LogLevel}
|
||||
*/
|
||||
public verbose?: boolean | LogLevel
|
||||
|
||||
/**
|
||||
* Message shown when the user creates its own token.
|
||||
* @type {string}
|
||||
*/
|
||||
public authMessage?: string
|
||||
|
||||
/**
|
||||
* Token expiration time in ms.
|
||||
* @type {number}
|
||||
*/
|
||||
public authTokenExpiration?: number
|
||||
|
||||
// Parity config
|
||||
public parityUri?: string
|
||||
|
||||
public threshold?: number
|
||||
}
|
||||
|
||||
export default Config
|
5
src/models/InputType.ts
Normal file
5
src/models/InputType.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default class InputType {
|
||||
public name: string
|
||||
|
||||
public type: string
|
||||
}
|
13
src/models/MethodReflection.ts
Normal file
13
src/models/MethodReflection.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import InputType from './InputType'
|
||||
|
||||
export default class MethodReflection {
|
||||
public contractName: string
|
||||
|
||||
public methodName: string
|
||||
|
||||
public address: string
|
||||
|
||||
public signature: string
|
||||
|
||||
public inputs: InputType[]
|
||||
}
|
5
src/models/ValuePair.ts
Normal file
5
src/models/ValuePair.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default class ValuePair {
|
||||
public type: string
|
||||
|
||||
public value: any
|
||||
}
|
8
src/models/ValueType.ts
Normal file
8
src/models/ValueType.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
enum ValueType {
|
||||
DID, // DID string e.g. 'did:op:xxx'
|
||||
DIDRef, // hash of DID same as in parameter (bytes32 _did) in text 0x0123abc.. or 0123abc..
|
||||
URL, // URL string e.g. 'http(s)://xx'
|
||||
DDO // DDO string in JSON e.g. '{ "id": "did:op:xxx"...
|
||||
}
|
||||
|
||||
export default ValueType
|
126
src/ocean/Account.ts
Normal file
126
src/ocean/Account.ts
Normal file
|
@ -0,0 +1,126 @@
|
|||
import BigNumber from 'bignumber.js'
|
||||
import Balance from '../models/Balance'
|
||||
|
||||
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
|
||||
|
||||
/**
|
||||
* Account information.
|
||||
*/
|
||||
export default class Account extends Instantiable {
|
||||
private password?: string
|
||||
|
||||
private token?: string
|
||||
|
||||
constructor(private id: string = '0x0', config?: InstantiableConfig) {
|
||||
super()
|
||||
if (config) {
|
||||
this.setInstanceConfig(config)
|
||||
}
|
||||
}
|
||||
|
||||
public getId() {
|
||||
return this.id
|
||||
}
|
||||
|
||||
public setId(id) {
|
||||
this.id = id
|
||||
}
|
||||
|
||||
/**
|
||||
* Set account password.
|
||||
* @param {string} password Password for account.
|
||||
*/
|
||||
public setPassword(password: string): void {
|
||||
this.password = password
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns account password.
|
||||
* @return {string} Account password.
|
||||
*/
|
||||
public getPassword(): string {
|
||||
return this.password
|
||||
}
|
||||
|
||||
/**
|
||||
* Set account token.
|
||||
* @param {string} token Token for account.
|
||||
*/
|
||||
public setToken(token: string): void {
|
||||
this.token = token
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns account token.
|
||||
* @return {Promise<string>} Account token.
|
||||
*/
|
||||
public async getToken(): Promise<string> {
|
||||
return this.token || this.ocean.auth.restore(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if account token is stored.
|
||||
* @return {Promise<boolean>} Is stored.
|
||||
*/
|
||||
public isTokenStored(): Promise<boolean> {
|
||||
return this.ocean.auth.isStored(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the account.
|
||||
*/
|
||||
public authenticate() {
|
||||
return this.ocean.auth.store(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Balance of Ocean Token.
|
||||
* @return {Promise<number>}
|
||||
*/
|
||||
public async getOceanBalance(): Promise<number> {
|
||||
const { token } = this.ocean.keeper
|
||||
return (await token.balanceOf(this.id)) / 10 ** (await token.decimals())
|
||||
}
|
||||
|
||||
/**
|
||||
* Balance of Ether.
|
||||
* @return {Promise<number>}
|
||||
*/
|
||||
public async getEtherBalance(): Promise<number> {
|
||||
return this.web3.eth
|
||||
.getBalance(this.id, 'latest')
|
||||
.then((balance: string): number => {
|
||||
return new BigNumber(balance).toNumber()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Balances of Ether and Ocean Token.
|
||||
* @return {Promise<Balance>}
|
||||
*/
|
||||
public async getBalance(): Promise<Balance> {
|
||||
return {
|
||||
eth: await this.getEtherBalance(),
|
||||
ocn: await this.getOceanBalance()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request Ocean Tokens.
|
||||
* @param {number} amount Tokens to be requested.
|
||||
* @return {Promise<number>}
|
||||
*/
|
||||
public async requestTokens(amount: number | string): Promise<string> {
|
||||
amount = String(amount)
|
||||
if (!this.ocean.keeper.dispenser) {
|
||||
throw new Error('Dispenser not available on this network.')
|
||||
}
|
||||
try {
|
||||
await this.ocean.keeper.dispenser.requestTokens(amount, this.id)
|
||||
} catch (e) {
|
||||
this.logger.error(e)
|
||||
throw new Error('Error requesting tokens')
|
||||
}
|
||||
return amount
|
||||
}
|
||||
}
|
65
src/ocean/DID.ts
Normal file
65
src/ocean/DID.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { generateId } from '../utils/GeneratorHelpers'
|
||||
|
||||
const prefix = 'did:op:'
|
||||
|
||||
/**
|
||||
* Decentralized ID.
|
||||
*/
|
||||
export default class DID {
|
||||
/**
|
||||
* Parses a DID from a string.
|
||||
* @param {string} didString DID in string.
|
||||
* @return {DID}
|
||||
*/
|
||||
public static parse(didString: string | DID): DID {
|
||||
if (didString instanceof DID) {
|
||||
didString = didString.getDid()
|
||||
}
|
||||
let did: DID
|
||||
const didMatch = didString.match(/^did:op:([a-f0-9]{64})$/i)
|
||||
|
||||
if (didMatch) {
|
||||
did = new DID(didMatch[1])
|
||||
}
|
||||
|
||||
if (!did) {
|
||||
throw new Error(`Parsing DID failed, ${didString}`)
|
||||
}
|
||||
|
||||
return did
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new DID.
|
||||
* @return {DID}
|
||||
*/
|
||||
public static generate(): DID {
|
||||
return new DID(generateId())
|
||||
}
|
||||
|
||||
/**
|
||||
* ID.
|
||||
* @type {string}
|
||||
*/
|
||||
private id: string
|
||||
|
||||
private constructor(id: string) {
|
||||
this.id = id
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DID.
|
||||
* @return {string}
|
||||
*/
|
||||
public getDid(): string {
|
||||
return `${prefix}${this.id}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID.
|
||||
* @return {string}
|
||||
*/
|
||||
public getId(): string {
|
||||
return this.id
|
||||
}
|
||||
}
|
127
src/ocean/Ocean.ts
Normal file
127
src/ocean/Ocean.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
import { OceanAccounts } from './OceanAccounts'
|
||||
|
||||
import { OceanAssets } from './OceanAssets'
|
||||
import { OceanAuth } from './OceanAuth'
|
||||
import { OceanCompute } from './OceanCompute'
|
||||
|
||||
import { OceanTokens } from './OceanTokens'
|
||||
import { OceanVersions } from './OceanVersions'
|
||||
import { OceanUtils } from './utils/OceanUtils'
|
||||
|
||||
import { Aquarius } from '../aquarius/Aquarius'
|
||||
import { Brizo } from '../brizo/Brizo'
|
||||
|
||||
import { Config } from '../models/Config'
|
||||
|
||||
import {
|
||||
Instantiable,
|
||||
generateIntantiableConfigFromConfig
|
||||
} from '../Instantiable.abstract'
|
||||
import { DataTokens } from '../lib'
|
||||
|
||||
/**
|
||||
* Main interface for Ocean Protocol.
|
||||
*/
|
||||
export class Ocean extends Instantiable {
|
||||
/**
|
||||
* Returns the instance of Ocean.
|
||||
* @param {Config} config Ocean instance configuration.
|
||||
* @return {Promise<Ocean>}
|
||||
*/
|
||||
public static async getInstance(config: Config): Promise<Ocean> {
|
||||
const instance = new Ocean()
|
||||
|
||||
const instanceConfig = {
|
||||
...generateIntantiableConfigFromConfig(config),
|
||||
ocean: instance
|
||||
}
|
||||
instance.setInstanceConfig(instanceConfig)
|
||||
|
||||
instance.utils = await OceanUtils.getInstance(instanceConfig)
|
||||
|
||||
instance.brizo = new Brizo(instanceConfig)
|
||||
instance.aquarius = new Aquarius(
|
||||
instanceConfig.config.aquariusUri,
|
||||
instanceConfig.logger
|
||||
)
|
||||
|
||||
instance.accounts = await OceanAccounts.getInstance(instanceConfig)
|
||||
instance.auth = await OceanAuth.getInstance(instanceConfig)
|
||||
instance.assets = await OceanAssets.getInstance(instanceConfig)
|
||||
instance.compute = await OceanCompute.getInstance(instanceConfig)
|
||||
instance.datatokens = new DataTokens(
|
||||
instanceConfig.config.factoryAddress,
|
||||
instanceConfig.config.factoryABI,
|
||||
instanceConfig.config.datatokensABI,
|
||||
instanceConfig.config.web3Provider
|
||||
)
|
||||
instance.tokens = await OceanTokens.getInstance(instanceConfig)
|
||||
instance.versions = await OceanVersions.getInstance(instanceConfig)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Brizo instance.
|
||||
* @type {Brizo}
|
||||
*/
|
||||
public brizo: Brizo
|
||||
|
||||
/**
|
||||
* Aquarius instance.
|
||||
* @type {Aquarius}
|
||||
*/
|
||||
public aquarius: Aquarius
|
||||
|
||||
/**
|
||||
* Ocean account submodule
|
||||
* @type {OceanAccounts}
|
||||
*/
|
||||
public accounts: OceanAccounts
|
||||
|
||||
/**
|
||||
* Ocean auth submodule
|
||||
* @type {OceanAuth}
|
||||
*/
|
||||
public auth: OceanAuth
|
||||
|
||||
/**
|
||||
* Ocean assets submodule
|
||||
* @type {OceanAssets}
|
||||
*/
|
||||
public assets: OceanAssets
|
||||
|
||||
/**
|
||||
* Ocean compute submodule
|
||||
* @type {OceanCompute}
|
||||
*/
|
||||
public compute: OceanCompute
|
||||
|
||||
/**
|
||||
* Ocean secretStore submodule
|
||||
* @type {OceanSecretStore}
|
||||
*/
|
||||
public datatokens: DataTokens
|
||||
|
||||
/**
|
||||
* Ocean tokens submodule
|
||||
* @type {OceanTokens}
|
||||
*/
|
||||
public tokens: OceanTokens
|
||||
|
||||
/**
|
||||
* Ocean versions submodule
|
||||
* @type {OceanVersions}
|
||||
*/
|
||||
public versions: OceanVersions
|
||||
|
||||
/**
|
||||
* Ocean utils submodule
|
||||
* @type {OceanUtils}
|
||||
*/
|
||||
public utils: OceanUtils
|
||||
|
||||
private constructor() {
|
||||
super()
|
||||
}
|
||||
}
|
57
src/ocean/OceanAccounts.ts
Normal file
57
src/ocean/OceanAccounts.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import Balance from '../models/Balance'
|
||||
import Account from './Account'
|
||||
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
|
||||
|
||||
/**
|
||||
* Account submodule of Ocean Protocol.
|
||||
*/
|
||||
export class OceanAccounts extends Instantiable {
|
||||
/**
|
||||
* Returns the instance of OceanAccounts.
|
||||
* @return {Promise<OceanAccounts>}
|
||||
*/
|
||||
public static async getInstance(config: InstantiableConfig): Promise<OceanAccounts> {
|
||||
const instance = new OceanAccounts()
|
||||
instance.setInstanceConfig(config)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of accounts.
|
||||
* @return {Promise<Account[]>}
|
||||
*/
|
||||
public async list(): Promise<Account[]> {
|
||||
// retrieve eth accounts
|
||||
const ethAccounts: string[] = await this.web3.eth.getAccounts()
|
||||
|
||||
const accountPromises = ethAccounts.map(
|
||||
address => new Account(address, this.instanceConfig)
|
||||
)
|
||||
return Promise.all(accountPromises)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return account balance.
|
||||
* @param {Account} account Account instance.
|
||||
* @return {Promise<Balance>} Ether and Ocean Token balance.
|
||||
*/
|
||||
public balance(account: Account): Promise<Balance> {
|
||||
return account.getBalance()
|
||||
}
|
||||
|
||||
/**
|
||||
* Request tokens for an account.
|
||||
* @param {Account} account Account instance.
|
||||
* @param {number} amount Token amount.
|
||||
* @return {Promise<boolean>} Success.
|
||||
*/
|
||||
public async requestTokens(account: Account, amount: number): Promise<boolean> {
|
||||
try {
|
||||
await account.requestTokens(amount)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
600
src/ocean/OceanAssets.ts
Normal file
600
src/ocean/OceanAssets.ts
Normal file
|
@ -0,0 +1,600 @@
|
|||
import { TransactionReceipt } from 'web3-core'
|
||||
import { SearchQuery } from '../aquarius/Aquarius'
|
||||
import { DDO } from '../ddo/DDO'
|
||||
import { MetaData, EditableMetaData } from '../ddo/MetaData'
|
||||
import { Service, ServiceAccess, ServiceComputePrivacy } from '../ddo/Service'
|
||||
import Account from './Account'
|
||||
import DID from './DID'
|
||||
import { fillConditionsWithDDO, SubscribablePromise, didZeroX } from '../utils'
|
||||
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
|
||||
import { OrderProgressStep } from './utils/ServiceUtils'
|
||||
|
||||
export enum CreateProgressStep {
|
||||
EncryptingFiles,
|
||||
FilesEncrypted,
|
||||
GeneratingProof,
|
||||
ProofGenerated,
|
||||
RegisteringDid,
|
||||
DidRegistred,
|
||||
StoringDdo,
|
||||
DdoStored
|
||||
}
|
||||
|
||||
/**
|
||||
* Assets submodule of Ocean Protocol.
|
||||
*/
|
||||
export class OceanAssets extends Instantiable {
|
||||
/**
|
||||
* Returns the instance of OceanAssets.
|
||||
* @return {Promise<OceanAssets>}
|
||||
*/
|
||||
public static async getInstance(config: InstantiableConfig): Promise<OceanAssets> {
|
||||
const instance = new OceanAssets()
|
||||
instance.setInstanceConfig(config)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DDO by DID.
|
||||
* @param {string} did Decentralized ID.
|
||||
* @return {Promise<DDO>}
|
||||
*/
|
||||
public async resolve(did: string): Promise<DDO> {
|
||||
const {
|
||||
serviceEndpoint
|
||||
} = await this.ocean.keeper.didRegistry.getAttributesByDid(did)
|
||||
return this.ocean.aquarius.retrieveDDOByUrl(serviceEndpoint)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DDO.
|
||||
* @param {MetaData} metadata DDO metadata.
|
||||
* @param {Account} publisher Publisher account.
|
||||
* @param {list} services list of Service description documents
|
||||
* @return {Promise<DDO>}
|
||||
*/
|
||||
public create(
|
||||
metadata: MetaData,
|
||||
publisher: Account,
|
||||
services: Service[] = []
|
||||
): SubscribablePromise<CreateProgressStep, DDO> {
|
||||
this.logger.log('Creating asset')
|
||||
return new SubscribablePromise(async observer => {
|
||||
const { secretStoreUri } = this.config
|
||||
const { didRegistry, templates } = this.ocean.keeper
|
||||
|
||||
const did: DID = DID.generate()
|
||||
|
||||
this.logger.log('Encrypting files')
|
||||
observer.next(CreateProgressStep.EncryptingFiles)
|
||||
const encryptedFiles = await this.ocean.secretStore.encrypt(
|
||||
did.getId(),
|
||||
metadata.main.files,
|
||||
publisher
|
||||
)
|
||||
this.logger.log('Files encrypted')
|
||||
observer.next(CreateProgressStep.FilesEncrypted)
|
||||
|
||||
// make sure that access service is defined if services is empty
|
||||
if (services.length === 0) {
|
||||
const accessService = await this.createAccessServiceAttributes(
|
||||
publisher,
|
||||
metadata.main.price,
|
||||
metadata.main.datePublished
|
||||
)
|
||||
services.push(accessService)
|
||||
}
|
||||
|
||||
const serviceAgreementTemplate = await templates.escrowAccessSecretStoreTemplate.getServiceAgreementTemplate()
|
||||
|
||||
const serviceEndpoint = this.ocean.aquarius.getServiceEndpoint(did)
|
||||
|
||||
let indexCount = 0
|
||||
// create ddo itself
|
||||
const ddo: DDO = new DDO({
|
||||
id: did.getDid(),
|
||||
authentication: [
|
||||
{
|
||||
type: 'RsaSignatureAuthentication2018',
|
||||
publicKey: did.getDid()
|
||||
}
|
||||
],
|
||||
publicKey: [
|
||||
{
|
||||
id: did.getDid(),
|
||||
type: 'EthereumECDSAKey',
|
||||
owner: publisher.getId()
|
||||
}
|
||||
],
|
||||
service: [
|
||||
{
|
||||
type: 'authorization',
|
||||
service: 'SecretStore',
|
||||
serviceEndpoint: secretStoreUri,
|
||||
attributes: { main: {} }
|
||||
},
|
||||
{
|
||||
type: 'metadata',
|
||||
serviceEndpoint,
|
||||
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[]
|
||||
})
|
||||
|
||||
// Overwrite initial service agreement conditions
|
||||
serviceAgreementTemplate.conditions = fillConditionsWithDDO(
|
||||
await templates.escrowAccessSecretStoreTemplate.getServiceAgreementTemplateConditions(),
|
||||
ddo
|
||||
)
|
||||
for (const service of services) {
|
||||
if (service.type === 'compute') {
|
||||
service.attributes.serviceAgreementTemplate.conditions = fillConditionsWithDDO(
|
||||
await templates.escrowComputeExecutionTemplate.getServiceAgreementTemplateConditions(),
|
||||
ddo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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('Registering DID')
|
||||
observer.next(CreateProgressStep.RegisteringDid)
|
||||
await didRegistry.registerAttribute(
|
||||
did.getId(),
|
||||
ddo.getChecksum(),
|
||||
[this.config.brizoAddress],
|
||||
serviceEndpoint,
|
||||
publisher.getId()
|
||||
)
|
||||
this.logger.log('DID registred')
|
||||
observer.next(CreateProgressStep.DidRegistred)
|
||||
|
||||
this.logger.log('Storing DDO')
|
||||
observer.next(CreateProgressStep.StoringDdo)
|
||||
const storedDdo = await this.ocean.aquarius.storeDDO(ddo)
|
||||
this.logger.log('DDO stored')
|
||||
observer.next(CreateProgressStep.DdoStored)
|
||||
|
||||
return storedDdo
|
||||
})
|
||||
}
|
||||
|
||||
public async consume(
|
||||
agreementId: string,
|
||||
did: string,
|
||||
consumerAccount: Account,
|
||||
resultPath: string,
|
||||
index?: number,
|
||||
useSecretStore?: boolean
|
||||
): Promise<string>
|
||||
|
||||
/* eslint-disable no-dupe-class-members */
|
||||
public async consume(
|
||||
agreementId: string,
|
||||
did: string,
|
||||
consumerAccount: Account,
|
||||
resultPath?: undefined | null,
|
||||
index?: number,
|
||||
useSecretStore?: boolean
|
||||
): Promise<true>
|
||||
|
||||
public async consume(
|
||||
agreementId: string,
|
||||
did: string,
|
||||
consumerAccount: Account,
|
||||
resultPath?: string,
|
||||
index: number = -1,
|
||||
useSecretStore?: boolean
|
||||
): Promise<string | true> {
|
||||
const ddo = await this.resolve(did)
|
||||
const { attributes } = ddo.findServiceByType('metadata')
|
||||
const accessService = ddo.findServiceByType('access')
|
||||
|
||||
const { files } = attributes.main
|
||||
|
||||
const { serviceEndpoint } = accessService
|
||||
|
||||
if (!serviceEndpoint) {
|
||||
throw new Error(
|
||||
'Consume asset failed, service definition is missing the `serviceEndpoint`.'
|
||||
)
|
||||
}
|
||||
|
||||
this.logger.log('Consuming files')
|
||||
|
||||
resultPath = resultPath
|
||||
? `${resultPath}/datafile.${ddo.shortId()}.${accessService.index}/`
|
||||
: undefined
|
||||
|
||||
if (!useSecretStore) {
|
||||
await this.ocean.brizo.consumeService(
|
||||
agreementId,
|
||||
serviceEndpoint,
|
||||
consumerAccount,
|
||||
files,
|
||||
resultPath,
|
||||
index
|
||||
)
|
||||
} else {
|
||||
const files = await this.ocean.secretStore.decrypt(
|
||||
did,
|
||||
ddo.findServiceByType('metadata').attributes.encryptedFiles,
|
||||
consumerAccount,
|
||||
ddo.findServiceByType('authorization').serviceEndpoint
|
||||
)
|
||||
const downloads = files
|
||||
.filter(({ index: i }) => index === -1 || index === i)
|
||||
.map(({ url, index: i }) =>
|
||||
this.ocean.utils.fetch.downloadFile(url, resultPath, i)
|
||||
)
|
||||
await Promise.all(downloads)
|
||||
}
|
||||
this.logger.log('Files consumed')
|
||||
|
||||
if (resultPath) {
|
||||
return resultPath
|
||||
}
|
||||
return true
|
||||
}
|
||||
/* eslint-enable no-dupe-class-members */
|
||||
|
||||
/**
|
||||
* Start the purchase/order of an asset's service. Starts by signing the service agreement
|
||||
* then sends the request to the publisher via the service endpoint (Brizo http service).
|
||||
* @param {string} did Decentralized ID.
|
||||
* @param {Account} consumerAccount Consumer account.
|
||||
* @param {string} provider ethereum address of service provider (optional)
|
||||
* @return {Promise<string>} Returns Agreement ID
|
||||
*/
|
||||
public order(
|
||||
did: string,
|
||||
consumerAccount: Account,
|
||||
provider?: string
|
||||
): SubscribablePromise<OrderProgressStep, string> {
|
||||
return new SubscribablePromise(async observer => {
|
||||
const { keeper, utils } = this.ocean
|
||||
const ddo: DDO = await this.resolve(did)
|
||||
const condition = keeper.conditions.accessSecretStoreCondition
|
||||
|
||||
const agreementId = await utils.services.order(
|
||||
'access',
|
||||
condition,
|
||||
observer,
|
||||
consumerAccount,
|
||||
ddo,
|
||||
provider
|
||||
)
|
||||
|
||||
return agreementId
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner of an asset.
|
||||
* @param {string} did Decentralized ID.
|
||||
* @return {Promise<string>} Returns Account ID
|
||||
*/
|
||||
public async owner(did: string): Promise<string> {
|
||||
const owner = await this.ocean.keeper.didRegistry.getDIDOwner(did)
|
||||
return owner
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the creator of a asset.
|
||||
* @param {string} did Decentralized ID.
|
||||
* @return {Promise<string>} Returns eth address
|
||||
*/
|
||||
public async creator(did: string): Promise<string> {
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the assets of a owner.
|
||||
* @param {string} owner Owner address.
|
||||
* @return {Promise<string[]>} List of DIDs.
|
||||
*/
|
||||
public async ownerAssets(owner: string): Promise<string[]> {
|
||||
return this.ocean.keeper.didRegistry.getAttributesByOwner(owner)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer ownership of an asset.
|
||||
* @param {string} did Asset DID.
|
||||
* @param {string} newOwner Ethereum address of the new owner of the DID.
|
||||
* @param {Account} account Ethereum account of original/old owner to sign and prove the ownership.
|
||||
* @return {Promise<TransactionReceipt>} Returns Web3 transaction receipt.
|
||||
*/
|
||||
public async transferOwnership(
|
||||
did: string,
|
||||
newOwner: string,
|
||||
account: Account
|
||||
): Promise<TransactionReceipt> {
|
||||
const oldOwner = await this.ocean.assets.owner(did)
|
||||
const oldDdo = await this.ocean.aquarius.retrieveDDO(did)
|
||||
|
||||
// update owner on-chain
|
||||
const txReceipt = this.ocean.keeper.didRegistry.transferDIDOwnership(
|
||||
did,
|
||||
newOwner,
|
||||
oldOwner
|
||||
)
|
||||
// get a signature
|
||||
const signature = await this.ocean.utils.signature.signForAquarius(
|
||||
oldDdo.updated,
|
||||
account
|
||||
)
|
||||
if (signature != null)
|
||||
await this.ocean.aquarius.transferOwnership(
|
||||
did,
|
||||
newOwner,
|
||||
oldDdo.updated,
|
||||
signature
|
||||
)
|
||||
|
||||
return txReceipt
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<string>}
|
||||
*/
|
||||
public async editMetadata(
|
||||
did: string,
|
||||
newMetadata: EditableMetaData,
|
||||
account: Account
|
||||
): Promise<string> {
|
||||
const oldDdo = await this.ocean.aquarius.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.aquarius.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<string>}
|
||||
*/
|
||||
public async updateComputePrivacy(
|
||||
did: string,
|
||||
serviceIndex: number,
|
||||
computePrivacy: ServiceComputePrivacy,
|
||||
account: Account
|
||||
): Promise<string> {
|
||||
const oldDdo = await this.ocean.aquarius.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.aquarius.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<string>}
|
||||
*/
|
||||
public async retire(did: string, account: Account): Promise<string> {
|
||||
const oldDdo = await this.ocean.aquarius.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.aquarius.retire(did, oldDdo.updated, signature)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the assets of a consumer.
|
||||
* @param {string} consumer Consumer address.
|
||||
* @return {Promise<string[]>} List of DIDs.
|
||||
*/
|
||||
public async consumerAssets(consumer: string): Promise<string[]> {
|
||||
return (
|
||||
await this.ocean.keeper.conditions.accessSecretStoreCondition.getGrantedDidByConsumer(
|
||||
consumer
|
||||
)
|
||||
).map(({ did }) => did)
|
||||
}
|
||||
|
||||
/**
|
||||
* Search over the assets using a query.
|
||||
* @param {SearchQuery} query Query to filter the assets.
|
||||
* @return {Promise<DDO[]>}
|
||||
*/
|
||||
public async query(query: SearchQuery) {
|
||||
return this.ocean.aquarius.queryMetadata(query)
|
||||
}
|
||||
|
||||
/**
|
||||
* Search over the assets using a keyword.
|
||||
* @param {SearchQuery} text Text to filter the assets.
|
||||
* @return {Promise<DDO[]>}
|
||||
*/
|
||||
public async search(text: string) {
|
||||
return this.ocean.aquarius.queryMetadataByText({
|
||||
text,
|
||||
page: 1,
|
||||
offset: 100,
|
||||
query: {
|
||||
value: 1
|
||||
},
|
||||
sort: {
|
||||
value: 1
|
||||
}
|
||||
} as SearchQuery)
|
||||
}
|
||||
|
||||
public async createAccessServiceAttributes(
|
||||
consumerAccount: Account,
|
||||
price: string,
|
||||
datePublished: string,
|
||||
timeout: number = 0
|
||||
): Promise<ServiceAccess> {
|
||||
const { templates } = this.ocean.keeper
|
||||
const serviceAgreementTemplate = await templates.escrowAccessSecretStoreTemplate.getServiceAgreementTemplate()
|
||||
return {
|
||||
type: 'access',
|
||||
index: 2,
|
||||
serviceEndpoint: this.ocean.brizo.getConsumeEndpoint(),
|
||||
templateId: templates.escrowAccessSecretStoreTemplate.getId(),
|
||||
attributes: {
|
||||
main: {
|
||||
creator: consumerAccount.getId(),
|
||||
datePublished,
|
||||
price,
|
||||
timeout: timeout,
|
||||
name: 'dataAssetAccessServiceAgreement'
|
||||
},
|
||||
serviceAgreementTemplate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get FreeWhiteList for a DID
|
||||
* @param {string} did Asset DID.
|
||||
* @return {Promise<string[]>} List of addresses.
|
||||
*/
|
||||
public async getFreeWhiteList(did: string): Promise<string[]> {
|
||||
const events = await this.ocean.keeper.didRegistry.getPastEvents(
|
||||
'DIDPermissionGranted',
|
||||
{
|
||||
_did: didZeroX(did)
|
||||
}
|
||||
)
|
||||
const list = events.map(({ returnValues }) => returnValues._grantee)
|
||||
const filteredList = []
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const address = list[index]
|
||||
const hasPermission = await this.ocean.keeper.didRegistry.getPermission(
|
||||
did,
|
||||
address
|
||||
)
|
||||
if (hasPermission) filteredList.push(address)
|
||||
}
|
||||
return filteredList
|
||||
}
|
||||
|
||||
/**
|
||||
* Add consumer to FreeWhiteList for a DID
|
||||
* @param {string} did Asset DID.
|
||||
* @param {string} consumer Ethereum address to add to the list.
|
||||
* @param {Account} account Ethereum account of DID owner
|
||||
* @return None
|
||||
*/
|
||||
public async addConsumerToFreeWhiteList(
|
||||
did: string,
|
||||
consumer: string,
|
||||
account: Account
|
||||
): Promise<TransactionReceipt> {
|
||||
await this.ocean.keeper.didRegistry.grantPermission(
|
||||
did,
|
||||
consumer,
|
||||
account.getId()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove consumer from DID's FreeWhiteList
|
||||
* @param {string} did Asset DID.
|
||||
* @param {string} consumer Ethereum address to add to the list.
|
||||
* @param {Account} account Ethereum account of DID owner
|
||||
* @return None
|
||||
*/
|
||||
public async removeConsumerFromFreeWhiteList(
|
||||
did: string,
|
||||
consumer: string,
|
||||
account: Account
|
||||
): Promise<TransactionReceipt> {
|
||||
await this.ocean.keeper.didRegistry.revokePermission(
|
||||
did,
|
||||
consumer,
|
||||
account.getId()
|
||||
)
|
||||
}
|
||||
}
|
144
src/ocean/OceanAuth.ts
Normal file
144
src/ocean/OceanAuth.ts
Normal file
|
@ -0,0 +1,144 @@
|
|||
import Account from './Account'
|
||||
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
|
||||
|
||||
const defaultAuthMessage = 'Ocean Protocol Authentication'
|
||||
const defaultExpirationTime = 30 * 24 * 60 * 60 * 1000 // 30 days
|
||||
const localStorageKey = 'SquidTokens'
|
||||
|
||||
/**
|
||||
* Tokens submodule of Ocean Protocol.
|
||||
*/
|
||||
export class OceanAuth extends Instantiable {
|
||||
/**
|
||||
* Returns the instance of OceanAuth.
|
||||
* @return {Promise<OceanAuth>}
|
||||
*/
|
||||
public static async getInstance(config: InstantiableConfig): Promise<OceanAuth> {
|
||||
const instance = new OceanAuth()
|
||||
instance.setInstanceConfig(config)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a token for a account.
|
||||
* @param {Account} account Signer account.
|
||||
* @return {Promise<string>} Token
|
||||
*/
|
||||
public async get(account: Account): Promise<string> {
|
||||
const time = Math.floor(Date.now() / 1000)
|
||||
const message = `${this.getMessage()}\n${time}`
|
||||
|
||||
try {
|
||||
const signature = await this.ocean.utils.signature.signText(
|
||||
message,
|
||||
account.getId(),
|
||||
account.getPassword()
|
||||
)
|
||||
|
||||
return `${signature}-${time}`
|
||||
} catch {
|
||||
throw new Error('User denied the signature.')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address of signed token.
|
||||
* @param {string} token Token.
|
||||
* @return {Promise<string>} Signer address.
|
||||
*/
|
||||
public async check(token: string): Promise<string> {
|
||||
const expiration = this.getExpiration()
|
||||
const [signature, timestamp] = token.split('-')
|
||||
|
||||
const message = `${this.getMessage()}\n${timestamp}`
|
||||
|
||||
if (+timestamp * 1000 + expiration < Date.now()) {
|
||||
return `0x${'0'.repeat(40)}`
|
||||
}
|
||||
|
||||
return this.web3.utils.toChecksumAddress(
|
||||
await this.ocean.utils.signature.verifyText(message, signature)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and stores the token for a account.
|
||||
* @param {Account} account Signer account.
|
||||
*/
|
||||
public async store(account: Account) {
|
||||
const token = await this.get(account)
|
||||
this.writeToken(account.getId(), token)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stored token.
|
||||
* @param {Account} account Signer account.
|
||||
*/
|
||||
public async restore(account: Account): Promise<string> {
|
||||
let token
|
||||
try {
|
||||
token = this.readToken(account.getId())
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
if (!token) {
|
||||
return
|
||||
}
|
||||
const signer = await this.check(token)
|
||||
if (signer.toLowerCase() !== account.getId().toLowerCase()) {
|
||||
return
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the token is stored and is valid.
|
||||
* @param {Account} account Signer account.
|
||||
* @return {Promise<boolean>} Is stored and valid.
|
||||
*/
|
||||
public async isStored(account: Account): Promise<boolean> {
|
||||
return !!(await this.restore(account))
|
||||
}
|
||||
|
||||
private writeToken(address: string, token: string) {
|
||||
const localStorage = this.getLocalStorage()
|
||||
const storedTokens = localStorage.getItem(localStorageKey)
|
||||
const tokens = storedTokens ? JSON.parse(storedTokens) : {}
|
||||
|
||||
localStorage.setItem(
|
||||
localStorageKey,
|
||||
JSON.stringify({
|
||||
...tokens,
|
||||
[address]: token
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private readToken(address: string) {
|
||||
const localStorage = this.getLocalStorage()
|
||||
const storedTokens = localStorage.getItem(localStorageKey)
|
||||
const tokens = storedTokens ? JSON.parse(storedTokens) : {}
|
||||
|
||||
return tokens[address]
|
||||
}
|
||||
|
||||
private getLocalStorage() {
|
||||
try {
|
||||
localStorage.getItem('')
|
||||
} catch {
|
||||
throw new Error(
|
||||
'LocalStorage is not supported. This feature is only available on browsers.'
|
||||
)
|
||||
}
|
||||
return localStorage
|
||||
}
|
||||
|
||||
private getMessage() {
|
||||
return this.config.authMessage || defaultAuthMessage
|
||||
}
|
||||
|
||||
private getExpiration() {
|
||||
return this.config.authTokenExpiration || defaultExpirationTime
|
||||
}
|
||||
}
|
325
src/ocean/OceanCompute.ts
Normal file
325
src/ocean/OceanCompute.ts
Normal file
|
@ -0,0 +1,325 @@
|
|||
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
|
||||
import { MetaData, MetaDataAlgorithm } from '../ddo/MetaData'
|
||||
import Account from './Account'
|
||||
import { DDO } from '../ddo/DDO'
|
||||
import { SubscribablePromise } from '../utils'
|
||||
import { OrderProgressStep } from './utils/ServiceUtils'
|
||||
import { DID } from '../squid'
|
||||
import { Service, ServiceCompute, ServiceComputePrivacy } from '../ddo/Service'
|
||||
|
||||
export const ComputeJobStatus = Object.freeze({
|
||||
Started: 10,
|
||||
ConfiguringVolumes: 20,
|
||||
ProvisioningSuccess: 30,
|
||||
DataProvisioningFailed: 31,
|
||||
AlgorithmProvisioningFailed: 32,
|
||||
RunningAlgorithm: 40,
|
||||
FilteringResults: 50,
|
||||
PublishingResult: 60,
|
||||
Completed: 70,
|
||||
Stopped: 80,
|
||||
Deleted: 90
|
||||
})
|
||||
|
||||
export interface Output {
|
||||
publishAlgorithmLog?: boolean
|
||||
publishOutput?: boolean
|
||||
brizoAddress?: string
|
||||
brizoUri?: string
|
||||
metadata?: MetaData
|
||||
metadataUri?: string
|
||||
nodeUri?: string
|
||||
owner?: string
|
||||
secretStoreUri?: string
|
||||
whitelist?: string[]
|
||||
}
|
||||
|
||||
export interface ComputeJob {
|
||||
owner: string
|
||||
agreementId: string
|
||||
jobId: string
|
||||
dateCreated: string
|
||||
dateFinished: string
|
||||
status: number
|
||||
statusText: string
|
||||
algorithmLogUrl: string
|
||||
resultsUrls: string[]
|
||||
resultsDid?: DID
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute submodule of Ocean Protocol.
|
||||
*/
|
||||
export class OceanCompute extends Instantiable {
|
||||
/**
|
||||
* Returns the instance of OceanCompute.
|
||||
* @return {Promise<OceanCompute>}
|
||||
*/
|
||||
public static async getInstance(config: InstantiableConfig): Promise<OceanCompute> {
|
||||
const instance = new OceanCompute()
|
||||
instance.setInstanceConfig(config)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an order of a compute service that is defined in an asset's services.
|
||||
* @param {Account} consumerAccount The account of the consumer ordering the service.
|
||||
* @param {string} datasetDid The DID of the dataset asset (of type `dataset`) to run the algorithm on.
|
||||
* @param {string} algorithmDid The DID of the algorithm asset (of type `algorithm`) to run on the asset.
|
||||
* @param {MetaData} algorithmMeta Metadata about the algorithm being run if `algorithm` is being used. This is ignored when `algorithmDid` is specified.
|
||||
* @return {Promise<string>} Returns the Service Agreement ID, representation of `bytes32` ID.
|
||||
*
|
||||
* Note: algorithmDid and algorithmMeta are optional, but if they are not passed,
|
||||
* you can end up in the situation that you are ordering and paying for your agreement,
|
||||
* but brizo will not allow the compute, due to privacy settings of the ddo
|
||||
*/
|
||||
public order(
|
||||
consumerAccount: Account,
|
||||
datasetDid: string,
|
||||
algorithmDid?: string,
|
||||
algorithmMeta?: MetaDataAlgorithm,
|
||||
provider?: string
|
||||
): SubscribablePromise<OrderProgressStep, string> {
|
||||
return new SubscribablePromise(async observer => {
|
||||
const { assets, keeper, utils } = this.ocean
|
||||
const ddo: DDO = await assets.resolve(datasetDid)
|
||||
const service: Service = ddo.findServiceByType('compute')
|
||||
if (!service) return null
|
||||
if (algorithmMeta) {
|
||||
// check if raw algo is allowed
|
||||
if (service.attributes.main.privacy)
|
||||
if (!service.attributes.main.privacy.allowRawAlgorithm) return null
|
||||
}
|
||||
if (algorithmDid) {
|
||||
// check if did is in trusted list
|
||||
if (service.attributes.main.privacy)
|
||||
if (service.attributes.main.privacy.trustedAlgorithms)
|
||||
if (service.attributes.main.privacy.trustedAlgorithms.length > 0)
|
||||
if (
|
||||
!service.attributes.main.privacy.trustedAlgorithms.includes(
|
||||
algorithmDid
|
||||
)
|
||||
)
|
||||
return null
|
||||
}
|
||||
const condition = keeper.conditions.computeExecutionCondition
|
||||
|
||||
const agreementId = await utils.services.order(
|
||||
'compute',
|
||||
condition,
|
||||
observer,
|
||||
consumerAccount,
|
||||
ddo,
|
||||
provider
|
||||
)
|
||||
|
||||
return agreementId
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the output object and add default properties if needed
|
||||
* @param {Account} consumerAccount The account of the consumer ordering the service.
|
||||
* @param {Output} output Output section used for publishing the result.
|
||||
* @return {Promise<Output>} Returns output object
|
||||
*/
|
||||
public checkOutput(consumerAccount: Account, output?: Output): Output {
|
||||
const isDefault =
|
||||
!output || (!output.publishAlgorithmLog && !output.publishOutput)
|
||||
|
||||
if (isDefault) {
|
||||
return {
|
||||
publishAlgorithmLog: false,
|
||||
publishOutput: false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
publishAlgorithmLog: output.publishAlgorithmLog,
|
||||
publishOutput: output.publishOutput,
|
||||
brizoAddress: output.brizoAddress || this.config.brizoAddress,
|
||||
brizoUri: output.brizoUri || this.config.brizoUri,
|
||||
metadataUri: output.metadataUri || this.config.aquariusUri,
|
||||
nodeUri: output.nodeUri || this.config.nodeUri,
|
||||
owner: output.owner || consumerAccount.getId(),
|
||||
secretStoreUri: output.secretStoreUri || this.config.secretStoreUri
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the execution of a compute job.
|
||||
* @param {Account} consumerAccount The account of the consumer ordering the service.
|
||||
* @param {string} agreementId The service agreement ID.
|
||||
* @param {string} algorithmDid The DID of the algorithm asset (of type `algorithm`) to run on the asset.
|
||||
* @param {MetaData} algorithmMeta Metadata about the algorithm being run if `algorithm` is being used. This is ignored when `algorithmDid` is specified.
|
||||
* @param {Output} output Define algorithm output publishing. Publishing the result of a compute job is turned off by default.
|
||||
* @return {Promise<ComputeJob>} Returns compute job ID under status.jobId
|
||||
*/
|
||||
public async start(
|
||||
consumerAccount: Account,
|
||||
agreementId: string,
|
||||
algorithmDid?: string,
|
||||
algorithmMeta?: MetaDataAlgorithm,
|
||||
output?: Output
|
||||
): Promise<ComputeJob> {
|
||||
output = this.checkOutput(consumerAccount, output)
|
||||
if (agreementId) {
|
||||
const computeJobsList = await this.ocean.brizo.compute(
|
||||
'post',
|
||||
agreementId,
|
||||
consumerAccount,
|
||||
algorithmDid,
|
||||
algorithmMeta,
|
||||
undefined,
|
||||
output
|
||||
)
|
||||
return computeJobsList[0] as ComputeJob
|
||||
} else return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends a running compute job.
|
||||
* @param {Account} consumerAccount The account of the consumer ordering the service.
|
||||
* @param {string} agreementId The service agreement ID.
|
||||
* @param {string} jobId The ID of the compute job to be stopped
|
||||
* @return {Promise<ComputeJob>} Returns the new status of a job
|
||||
*/
|
||||
public async stop(
|
||||
consumerAccount: Account,
|
||||
agreementId: string,
|
||||
jobId: string
|
||||
): Promise<ComputeJob> {
|
||||
const computeJobsList = await this.ocean.brizo.compute(
|
||||
'put',
|
||||
agreementId,
|
||||
consumerAccount,
|
||||
undefined,
|
||||
undefined,
|
||||
jobId
|
||||
)
|
||||
|
||||
return computeJobsList[0] as ComputeJob
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a compute job and all resources associated with the job. If job is running it will be stopped first.
|
||||
* @param {Account} consumerAccount The account of the consumer ordering the service.
|
||||
* @param {string} agreementId The service agreement ID.
|
||||
* @param {string} jobId The ID of the compute job to be stopped
|
||||
* @return {Promise<ComputeJob>} Returns the new status of a job
|
||||
*/
|
||||
public async delete(
|
||||
consumerAccount: Account,
|
||||
agreementId: string,
|
||||
jobId: string
|
||||
): Promise<ComputeJob> {
|
||||
const computeJobsList = await this.ocean.brizo.compute(
|
||||
'delete',
|
||||
agreementId,
|
||||
consumerAccount,
|
||||
undefined,
|
||||
undefined,
|
||||
jobId
|
||||
)
|
||||
|
||||
return computeJobsList[0] as ComputeJob
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends a running compute job and starts it again.
|
||||
* @param {Account} consumerAccount The account of the consumer ordering the service.
|
||||
* @param {string} agreementId The service agreement ID.
|
||||
* @param {string} jobId The ID of the compute job to be stopped
|
||||
* @return {Promise<ComputeJob>} Returns the new status of a job
|
||||
*/
|
||||
public async restart(
|
||||
consumerAccount: Account,
|
||||
agreementId: string,
|
||||
jobId: string
|
||||
): Promise<ComputeJob> {
|
||||
await this.stop(consumerAccount, agreementId, jobId)
|
||||
const result = await this.start(consumerAccount, agreementId, jobId)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the status of all compute jobs, or a single compute job.
|
||||
* @param {Account} consumerAccount The account of the consumer ordering the service.
|
||||
* @param {string} agreementId The service agreement ID.
|
||||
* @param {string} jobId The ID of the compute job to be stopped
|
||||
* @return {Promise<ComputeJob[]>} Returns the status
|
||||
*/
|
||||
public async status(
|
||||
consumerAccount: Account,
|
||||
agreementId?: string,
|
||||
jobId?: string
|
||||
): Promise<ComputeJob[]> {
|
||||
const computeJobsList = await this.ocean.brizo.compute(
|
||||
'get',
|
||||
agreementId,
|
||||
consumerAccount,
|
||||
undefined,
|
||||
undefined,
|
||||
jobId
|
||||
)
|
||||
|
||||
return computeJobsList as ComputeJob[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the final result of a specific compute job published as an asset.
|
||||
* @param {Account} consumerAccount The account of the consumer ordering the service.
|
||||
* @param {string} agreementId The service agreement ID.
|
||||
* @param {string} jobId The ID of the compute job to be stopped.
|
||||
* @return {Promise<ComputeJob>} Returns the DDO of the result asset.
|
||||
*/
|
||||
public async result(
|
||||
consumerAccount: Account,
|
||||
agreementId: string,
|
||||
jobId: string
|
||||
): Promise<ComputeJob> {
|
||||
const computeJobsList = await this.ocean.brizo.compute(
|
||||
'get',
|
||||
agreementId,
|
||||
consumerAccount,
|
||||
undefined,
|
||||
undefined,
|
||||
jobId
|
||||
)
|
||||
|
||||
return computeJobsList[0] as ComputeJob
|
||||
}
|
||||
|
||||
public async createComputeServiceAttributes(
|
||||
consumerAccount: Account,
|
||||
price: string,
|
||||
datePublished: string,
|
||||
computePrivacy?: ServiceComputePrivacy,
|
||||
timeout?: number
|
||||
): Promise<ServiceCompute> {
|
||||
const { templates } = this.ocean.keeper
|
||||
const serviceAgreementTemplate = await templates.escrowComputeExecutionTemplate.getServiceAgreementTemplate()
|
||||
const name = 'dataAssetComputingServiceAgreement'
|
||||
if (!timeout) timeout = 3600
|
||||
const service = {
|
||||
type: 'compute',
|
||||
index: 3,
|
||||
serviceEndpoint: this.ocean.brizo.getComputeEndpoint(),
|
||||
templateId: templates.escrowComputeExecutionTemplate.getId(),
|
||||
attributes: {
|
||||
main: {
|
||||
creator: consumerAccount.getId(),
|
||||
datePublished,
|
||||
price,
|
||||
privacy: {},
|
||||
timeout: timeout,
|
||||
name
|
||||
},
|
||||
serviceAgreementTemplate
|
||||
}
|
||||
}
|
||||
if (computePrivacy) service.attributes.main.privacy = computePrivacy
|
||||
return service as ServiceCompute
|
||||
}
|
||||
}
|
94
src/ocean/OceanSecretStore.ts
Normal file
94
src/ocean/OceanSecretStore.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import SecretStore from '@oceanprotocol/secret-store-client'
|
||||
import SecretStoreConfig from '@oceanprotocol/secret-store-client/dist/models/SecretStoreConfig'
|
||||
|
||||
import Account from './Account'
|
||||
import { noDidPrefixed } from '../utils'
|
||||
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
|
||||
|
||||
/**
|
||||
* SecretStore submodule of Ocean Protocol.
|
||||
*/
|
||||
export class OceanSecretStore extends Instantiable {
|
||||
/**
|
||||
* Returns the instance of OceanSecretStore.
|
||||
* @return {Promise<OceanSecretStore>}
|
||||
*/
|
||||
public static async getInstance(
|
||||
config: InstantiableConfig
|
||||
): Promise<OceanSecretStore> {
|
||||
const instance = new OceanSecretStore()
|
||||
instance.setInstanceConfig(config)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the given text and store the encryption keys using the `did`.
|
||||
* The encrypted text can be decrypted using the same keys identified by the `did`.
|
||||
* @param {string} did Decentralized ID.
|
||||
* @param {string} content Content to be encrypted.
|
||||
* @param {string} publisher Publisher account.
|
||||
* @return {Promise<string>} Encrypted text.
|
||||
*/
|
||||
public async encrypt(
|
||||
did: string,
|
||||
document: any,
|
||||
publisher: Account
|
||||
): Promise<string> {
|
||||
const signature =
|
||||
(await publisher.getToken()) ||
|
||||
(await this.ocean.utils.signature.signText(
|
||||
noDidPrefixed(did),
|
||||
publisher.getId(),
|
||||
publisher.getPassword()
|
||||
))
|
||||
|
||||
return this.ocean.brizo.encrypt(
|
||||
noDidPrefixed(did),
|
||||
signature,
|
||||
document,
|
||||
publisher.getId()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an encrypted text using the stored encryption keys associated with the `did`.
|
||||
* Decryption requires that the account owner has access permissions for this `did`
|
||||
* @param {string} did Decentralized ID.
|
||||
* @param {string} content Content to be encrypted.
|
||||
* @param {string} consumer cONSUMER account.
|
||||
* @return {Promise<string>} Encrypted text.
|
||||
*/
|
||||
public async decrypt(
|
||||
did: string,
|
||||
content: string,
|
||||
consumer?: Account,
|
||||
secretStoreUrl?: string
|
||||
): Promise<any> {
|
||||
return this.getSecretStoreByAccount(consumer, secretStoreUrl).decryptDocument(
|
||||
noDidPrefixed(did),
|
||||
content
|
||||
)
|
||||
}
|
||||
|
||||
private getSecretStoreByAccount(account: Account, secretStoreUrl?: string) {
|
||||
const config: any = { ...this.config }
|
||||
if (account) {
|
||||
config.address = account.getId()
|
||||
}
|
||||
if (account && account.getPassword()) {
|
||||
config.password = account.getPassword()
|
||||
}
|
||||
if (secretStoreUrl) {
|
||||
config.secretStoreUri = secretStoreUrl
|
||||
}
|
||||
return this.getSecretStore(config)
|
||||
}
|
||||
|
||||
private getSecretStore(config: SecretStoreConfig): SecretStore {
|
||||
const { secretStoreUri, parityUri, password, address, threshold } = config
|
||||
config = { secretStoreUri, parityUri, password, address, threshold }
|
||||
|
||||
return new SecretStore(config)
|
||||
}
|
||||
}
|
45
src/ocean/OceanTokens.ts
Normal file
45
src/ocean/OceanTokens.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import Account from './Account'
|
||||
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
|
||||
|
||||
/**
|
||||
* Tokens submodule of Ocean Protocol.
|
||||
*/
|
||||
export class OceanTokens extends Instantiable {
|
||||
/**
|
||||
* Returns the instance of OceanTokens.
|
||||
* @return {Promise<OceanTokens>}
|
||||
*/
|
||||
public static async getInstance(config: InstantiableConfig): Promise<OceanTokens> {
|
||||
const instance = new OceanTokens()
|
||||
instance.setInstanceConfig(config)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer a number of tokens to the mentioned account.
|
||||
* @param {string} to Address that receives the tokens.
|
||||
* @param {number} amount Tokens to transfer.
|
||||
* @param {Account} from Sender account address.
|
||||
* @return {Promise<boolean>} Success,
|
||||
*/
|
||||
public async transfer(to: string, amount: number, from: Account): Promise<boolean> {
|
||||
this.ocean.keeper.token.transfer(to, amount, from.getId())
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Request tokens for an account.
|
||||
* @param {Account} account Account instance.
|
||||
* @param {number} amount Token amount.
|
||||
* @return {Promise<boolean>} Success.
|
||||
*/
|
||||
public async request(account: Account, amount: number): Promise<boolean> {
|
||||
try {
|
||||
await account.requestTokens(amount)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
150
src/ocean/OceanVersions.ts
Normal file
150
src/ocean/OceanVersions.ts
Normal file
|
@ -0,0 +1,150 @@
|
|||
import * as keeperPackageJson from '@oceanprotocol/keeper-contracts/package.json'
|
||||
import * as metadata from '../metadata.json'
|
||||
|
||||
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
|
||||
|
||||
export enum OceanPlatformTechStatus {
|
||||
Loading = 'Loading',
|
||||
Unknown = 'Unknown',
|
||||
Stopped = 'Stopped',
|
||||
Working = 'Working'
|
||||
}
|
||||
|
||||
export interface OceanPlatformTech {
|
||||
name: string
|
||||
version?: string
|
||||
commit?: string
|
||||
status: OceanPlatformTechStatus
|
||||
}
|
||||
|
||||
export interface OceanPlatformKeeperTech extends OceanPlatformTech {
|
||||
network?: string
|
||||
keeperVersion?: string
|
||||
contracts?: { [contractName: string]: string }
|
||||
}
|
||||
|
||||
export interface OceanPlatformVersions {
|
||||
squid: OceanPlatformKeeperTech
|
||||
aquarius: OceanPlatformTech
|
||||
brizo: OceanPlatformKeeperTech
|
||||
status: {
|
||||
ok: boolean
|
||||
contracts: boolean
|
||||
network: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Versions submodule of Ocean Protocol.
|
||||
*/
|
||||
export class OceanVersions extends Instantiable {
|
||||
/**
|
||||
* Returns the instance of OceanVersions.
|
||||
* @return {Promise<OceanVersions>}
|
||||
*/
|
||||
public static async getInstance(config: InstantiableConfig): Promise<OceanVersions> {
|
||||
const instance = new OceanVersions()
|
||||
instance.setInstanceConfig(config)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
public async get(): Promise<OceanPlatformVersions> {
|
||||
const versions = {} as OceanPlatformVersions
|
||||
|
||||
// Squid
|
||||
versions.squid = {
|
||||
name: 'Squid-js',
|
||||
version: metadata.version,
|
||||
commit: metadata.commit,
|
||||
status: OceanPlatformTechStatus.Working,
|
||||
network: (await this.ocean.keeper.getNetworkName()).toLowerCase(),
|
||||
keeperVersion: keeperPackageJson.version,
|
||||
contracts: Object.values(await this.ocean.keeper.getAllInstances())
|
||||
.filter(_ => !!_)
|
||||
.reduce(
|
||||
(acc, { contractName, address }) => ({
|
||||
...acc,
|
||||
[contractName]: address
|
||||
}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
// Brizo
|
||||
try {
|
||||
const {
|
||||
contracts,
|
||||
'keeper-version': keeperVersion,
|
||||
network,
|
||||
software: name,
|
||||
version
|
||||
} = await this.ocean.brizo.getVersionInfo()
|
||||
versions.brizo = {
|
||||
name,
|
||||
status: OceanPlatformTechStatus.Working,
|
||||
version,
|
||||
contracts,
|
||||
network,
|
||||
keeperVersion: keeperVersion.replace(/^v/, '')
|
||||
}
|
||||
} catch {
|
||||
versions.brizo = {
|
||||
name: 'Brizo',
|
||||
status: OceanPlatformTechStatus.Stopped
|
||||
}
|
||||
}
|
||||
|
||||
// Aquarius
|
||||
try {
|
||||
const { software: name, version } = await this.ocean.aquarius.getVersionInfo()
|
||||
versions.aquarius = {
|
||||
name,
|
||||
status: OceanPlatformTechStatus.Working,
|
||||
version
|
||||
}
|
||||
} catch {
|
||||
versions.aquarius = {
|
||||
name: 'Aquarius',
|
||||
status: OceanPlatformTechStatus.Stopped
|
||||
}
|
||||
}
|
||||
|
||||
// Status
|
||||
const techs: OceanPlatformKeeperTech[] = Object.values(versions as any)
|
||||
|
||||
const networks = techs
|
||||
.map(({ network }) => network)
|
||||
.filter(_ => !!_)
|
||||
.reduce((acc, network) => ({ ...acc, [network]: true }), {})
|
||||
|
||||
let contractStatus = true
|
||||
const contractList = techs.map(({ contracts }) => contracts).filter(_ => !!_)
|
||||
Array.from(contractList.map(Object.keys))
|
||||
.reduce((acc, _) => [...acc, ..._], [])
|
||||
.filter((_, i, list) => list.indexOf(_) === i)
|
||||
.forEach(name => {
|
||||
let address
|
||||
contractList
|
||||
.map(_ => _[name])
|
||||
.forEach(_ => {
|
||||
if (!address) {
|
||||
address = _
|
||||
return
|
||||
}
|
||||
if (address !== _) {
|
||||
this.logger.warn(`Error on contract ${name}`)
|
||||
contractStatus = false
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
versions.status = {
|
||||
ok: !techs.find(({ status }) => status !== OceanPlatformTechStatus.Working),
|
||||
network: Object.keys(networks).length === 1,
|
||||
contracts: contractStatus
|
||||
}
|
||||
|
||||
return versions
|
||||
}
|
||||
}
|
55
src/ocean/utils/OceanUtils.ts
Normal file
55
src/ocean/utils/OceanUtils.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { Instantiable, InstantiableConfig } from '../../Instantiable.abstract'
|
||||
|
||||
import { ServiceUtils } from './ServiceUtils'
|
||||
import { ServiceAgreement } from './ServiceAgreement'
|
||||
import { SignatureUtils } from './SignatureUtils'
|
||||
import { WebServiceConnector } from './WebServiceConnector'
|
||||
|
||||
/**
|
||||
* Utils internal submodule of Ocean Protocol.
|
||||
*/
|
||||
export class OceanUtils extends Instantiable {
|
||||
/**
|
||||
* Returns the instance of OceanUtils.
|
||||
* @return {Promise<OceanUtils>}
|
||||
*/
|
||||
public static async getInstance(config: InstantiableConfig): Promise<OceanUtils> {
|
||||
const instance = new OceanUtils()
|
||||
instance.setInstanceConfig(config)
|
||||
|
||||
instance.agreements = new ServiceAgreement(
|
||||
config.ocean,
|
||||
config.logger,
|
||||
config.web3
|
||||
)
|
||||
instance.services = new ServiceUtils(config.ocean, config.logger)
|
||||
instance.signature = new SignatureUtils(config.web3, config.logger)
|
||||
instance.fetch = new WebServiceConnector(config.logger)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Agreement utils.
|
||||
* @type {ServiceAgreement}
|
||||
*/
|
||||
public agreements: ServiceAgreement
|
||||
|
||||
/**
|
||||
* Service utils.
|
||||
* @type {ServiceUtils}
|
||||
*/
|
||||
public services: ServiceUtils
|
||||
|
||||
/**
|
||||
* Signature utils.
|
||||
* @type {SignatureUtils}
|
||||
*/
|
||||
public signature: SignatureUtils
|
||||
|
||||
/**
|
||||
* Fetch utils.
|
||||
* @type {WebServiceConnector}
|
||||
*/
|
||||
public fetch: WebServiceConnector
|
||||
}
|
104
src/ocean/utils/ServiceAgreement.ts
Normal file
104
src/ocean/utils/ServiceAgreement.ts
Normal file
|
@ -0,0 +1,104 @@
|
|||
import { ServiceAgreementTemplateCondition } from '../../ddo/ServiceAgreementTemplate'
|
||||
import { DDO } from '../../ddo/DDO'
|
||||
import { ServiceAccess } from '../../ddo/Service'
|
||||
import Account from '../Account'
|
||||
import { zeroX, Logger } from '../../utils'
|
||||
import { Ocean } from '../../squid'
|
||||
import Web3 from 'web3'
|
||||
|
||||
export class ServiceAgreement {
|
||||
private ocean: Ocean
|
||||
private logger: Logger
|
||||
private web3: Web3
|
||||
constructor(ocean: Ocean, logger: Logger, web3: Web3) {
|
||||
this.ocean = ocean
|
||||
this.logger = logger
|
||||
this.web3 = web3
|
||||
}
|
||||
|
||||
public async signServiceAgreement(
|
||||
ddo: DDO,
|
||||
index: number,
|
||||
serviceAgreementId: string,
|
||||
agreementConditionsIds: string[],
|
||||
consumer: Account
|
||||
): Promise<string> {
|
||||
const service = ddo.findServiceById<'access'>(index)
|
||||
const timelockValues: number[] = this.getTimeValuesFromService(
|
||||
service,
|
||||
'timelock'
|
||||
)
|
||||
const timeoutValues: number[] = this.getTimeValuesFromService(service, 'timeout')
|
||||
|
||||
if (!service.templateId) {
|
||||
throw new Error('TemplateId not found in DDO.')
|
||||
}
|
||||
|
||||
const serviceAgreementHashSignature = await this.createHashSignature(
|
||||
service.templateId,
|
||||
serviceAgreementId,
|
||||
agreementConditionsIds,
|
||||
timelockValues,
|
||||
timeoutValues,
|
||||
consumer
|
||||
)
|
||||
|
||||
this.logger.debug('SA hash signature:', serviceAgreementHashSignature)
|
||||
|
||||
return zeroX(serviceAgreementHashSignature)
|
||||
}
|
||||
|
||||
public async createHashSignature(
|
||||
templateId: string,
|
||||
serviceAgreementId: string,
|
||||
valueHashes: string[],
|
||||
timelockValues: number[],
|
||||
timeoutValues: number[],
|
||||
consumer: Account
|
||||
): Promise<string> {
|
||||
const serviceAgreementHash = this.hashServiceAgreement(
|
||||
templateId,
|
||||
serviceAgreementId,
|
||||
valueHashes,
|
||||
timelockValues,
|
||||
timeoutValues
|
||||
)
|
||||
|
||||
const serviceAgreementHashSignature = await this.ocean.utils.signature.signText(
|
||||
serviceAgreementHash,
|
||||
consumer.getId(),
|
||||
consumer.getPassword()
|
||||
)
|
||||
|
||||
return serviceAgreementHashSignature
|
||||
}
|
||||
|
||||
public hashServiceAgreement(
|
||||
serviceAgreementTemplateId: string,
|
||||
serviceAgreementId: string,
|
||||
valueHashes: string[],
|
||||
timelocks: number[],
|
||||
timeouts: number[]
|
||||
): string {
|
||||
const args: any = [
|
||||
{ type: 'bytes32', value: zeroX(serviceAgreementTemplateId) },
|
||||
{ type: 'bytes32[]', value: valueHashes.map(zeroX) },
|
||||
{ type: 'uint256[]', value: timelocks },
|
||||
{ type: 'uint256[]', value: timeouts },
|
||||
{ type: 'bytes32', value: zeroX(serviceAgreementId) }
|
||||
]
|
||||
|
||||
return this.web3.utils.soliditySha3(...args)
|
||||
}
|
||||
|
||||
private getTimeValuesFromService(
|
||||
service: ServiceAccess,
|
||||
type: 'timeout' | 'timelock'
|
||||
): number[] {
|
||||
const timeoutValues: number[] = service.attributes.serviceAgreementTemplate.conditions.map(
|
||||
(condition: ServiceAgreementTemplateCondition) => condition[type]
|
||||
)
|
||||
|
||||
return timeoutValues
|
||||
}
|
||||
}
|
115
src/ocean/utils/ServiceUtils.ts
Normal file
115
src/ocean/utils/ServiceUtils.ts
Normal file
|
@ -0,0 +1,115 @@
|
|||
import { DDO } from '../../ddo/DDO'
|
||||
import Account from '../Account'
|
||||
import { zeroX, Logger, generateId } from '../../utils'
|
||||
import { Ocean } from '../../squid'
|
||||
import { Condition } from '../../keeper/contracts/conditions'
|
||||
import { ServiceType, Service } from '../../ddo/Service'
|
||||
|
||||
export enum OrderProgressStep {
|
||||
CreatingAgreement,
|
||||
AgreementInitialized,
|
||||
LockingPayment,
|
||||
LockedPayment
|
||||
}
|
||||
|
||||
export class ServiceUtils {
|
||||
private ocean: Ocean
|
||||
private logger: Logger
|
||||
|
||||
constructor(ocean: Ocean, logger: Logger) {
|
||||
this.ocean = ocean
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
public async order(
|
||||
type: ServiceType,
|
||||
condition: Condition,
|
||||
observer: any,
|
||||
consumerAccount: Account,
|
||||
ddo: DDO,
|
||||
provider?: string
|
||||
): Promise<string> {
|
||||
const { keeper, agreements } = this.ocean
|
||||
|
||||
const agreementId = zeroX(generateId())
|
||||
const service: Service = ddo.findServiceByType(type)
|
||||
const metadata = ddo.findServiceByType('metadata')
|
||||
|
||||
const templateName = service.attributes.serviceAgreementTemplate.contractName
|
||||
const template = keeper.getTemplateByName(templateName)
|
||||
|
||||
// use price from compute service,
|
||||
// otherwise always take the price from metadata
|
||||
const price =
|
||||
type === 'compute'
|
||||
? service.attributes.main.price
|
||||
: metadata.attributes.main.price
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
const paymentFlow = new Promise(async (resolve, reject) => {
|
||||
await template.getAgreementCreatedEvent(agreementId).once()
|
||||
|
||||
this.logger.log('Agreement initialized')
|
||||
observer.next(OrderProgressStep.AgreementInitialized)
|
||||
|
||||
this.logger.log('Locking payment')
|
||||
|
||||
const serviceGranted = condition
|
||||
.getConditionFulfilledEvent(agreementId)
|
||||
.once()
|
||||
|
||||
observer.next(OrderProgressStep.LockingPayment)
|
||||
const paid = await agreements.conditions.lockReward(
|
||||
agreementId,
|
||||
price,
|
||||
consumerAccount
|
||||
)
|
||||
observer.next(OrderProgressStep.LockedPayment)
|
||||
|
||||
if (paid) {
|
||||
this.logger.log('Payment was OK')
|
||||
} else {
|
||||
this.logger.error('Payment was KO')
|
||||
this.logger.error('Agreement ID: ', agreementId)
|
||||
this.logger.error('DID: ', ddo.id)
|
||||
reject(new Error('Error on payment'))
|
||||
}
|
||||
|
||||
await serviceGranted
|
||||
|
||||
this.logger.log(`Service ${type} granted`)
|
||||
resolve()
|
||||
})
|
||||
|
||||
observer.next(OrderProgressStep.CreatingAgreement)
|
||||
this.logger.log('Creating agreement')
|
||||
|
||||
// Get provider from didRegistry if not given in arguments
|
||||
let _provider = provider
|
||||
if (!provider) {
|
||||
const providers = await keeper.didRegistry.getDIDProviders(ddo.shortId())
|
||||
if (providers) {
|
||||
_provider = providers[0]
|
||||
}
|
||||
}
|
||||
|
||||
await agreements.create(
|
||||
ddo.id,
|
||||
agreementId,
|
||||
service.index,
|
||||
undefined,
|
||||
consumerAccount,
|
||||
_provider,
|
||||
consumerAccount
|
||||
)
|
||||
this.logger.log('Agreement created')
|
||||
|
||||
try {
|
||||
await paymentFlow
|
||||
} catch (e) {
|
||||
throw new Error(`Error paying the ${type} service.`)
|
||||
}
|
||||
|
||||
return agreementId
|
||||
}
|
||||
}
|
103
src/ocean/utils/SignatureUtils.ts
Normal file
103
src/ocean/utils/SignatureUtils.ts
Normal file
|
@ -0,0 +1,103 @@
|
|||
import Web3 from 'web3'
|
||||
import { Logger } from '../../utils'
|
||||
import { Account } from '../../squid'
|
||||
|
||||
export class SignatureUtils {
|
||||
private web3: Web3
|
||||
private logger: Logger
|
||||
|
||||
constructor(web3: Web3, logger: Logger) {
|
||||
this.web3 = web3
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
public async signText(
|
||||
text: string,
|
||||
publicKey: string,
|
||||
password?: string
|
||||
): Promise<string> {
|
||||
const isMetaMask =
|
||||
this.web3 &&
|
||||
this.web3.currentProvider &&
|
||||
(this.web3.currentProvider as any).isMetaMask
|
||||
try {
|
||||
return await this.web3.eth.personal.sign(text, publicKey, password)
|
||||
} catch (e) {
|
||||
if (isMetaMask) {
|
||||
throw e
|
||||
}
|
||||
this.logger.warn('Error on personal sign.')
|
||||
this.logger.warn(e)
|
||||
try {
|
||||
return await this.web3.eth.sign(text, publicKey)
|
||||
} catch (e2) {
|
||||
this.logger.error('Error on sign.')
|
||||
this.logger.error(e2)
|
||||
throw new Error('Error executing personal sign')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async signWithHash(
|
||||
text: string,
|
||||
publicKey: string,
|
||||
password?: string
|
||||
): Promise<string> {
|
||||
const hash = this.web3.utils.utf8ToHex(text)
|
||||
const isMetaMask =
|
||||
this.web3 &&
|
||||
this.web3.currentProvider &&
|
||||
(this.web3.currentProvider as any).isMetaMask
|
||||
try {
|
||||
return await this.web3.eth.personal.sign(hash, publicKey, password)
|
||||
} catch (e) {
|
||||
if (isMetaMask) {
|
||||
throw e
|
||||
}
|
||||
this.logger.warn('Error on personal sign.')
|
||||
this.logger.warn(e)
|
||||
try {
|
||||
return await this.web3.eth.sign(hash, publicKey)
|
||||
} catch (e2) {
|
||||
this.logger.error('Error on sign.')
|
||||
this.logger.error(e2)
|
||||
throw new Error('Error executing personal sign')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async verifyText(text: string, signature: string): Promise<string> {
|
||||
return this.web3.eth.personal.ecRecover(text, signature)
|
||||
}
|
||||
|
||||
public async getHash(message: string): Promise<string> {
|
||||
let hex = ''
|
||||
for (let i = 0; i < message.length; i++) {
|
||||
hex += '' + message.charCodeAt(i).toString(16)
|
||||
}
|
||||
const hexMessage = '0x' + hex
|
||||
return hexMessage as string
|
||||
}
|
||||
|
||||
public async signForAquarius(message: string, account: Account): Promise<string> {
|
||||
const hash = await this.getHash(message)
|
||||
const isMetaMask =
|
||||
this.web3 &&
|
||||
this.web3.currentProvider &&
|
||||
(this.web3.currentProvider as any).isMetaMask
|
||||
try {
|
||||
return this.web3.eth.personal.sign(
|
||||
hash,
|
||||
account.getId(),
|
||||
account.getPassword()
|
||||
)
|
||||
} catch (e) {
|
||||
if (isMetaMask) {
|
||||
throw e
|
||||
}
|
||||
this.logger.warn('Error on personal sign.')
|
||||
this.logger.warn(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
112
src/ocean/utils/WebServiceConnector.ts
Normal file
112
src/ocean/utils/WebServiceConnector.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { BodyInit, RequestInit, Response } from 'node-fetch'
|
||||
import fs from 'fs'
|
||||
import { Logger } from '../../utils'
|
||||
|
||||
const fetch = require('node-fetch')
|
||||
import save = require('save-file')
|
||||
|
||||
/**
|
||||
* Provides a common interface to web services.
|
||||
*/
|
||||
export class WebServiceConnector {
|
||||
public logger: Logger
|
||||
constructor(logger: Logger) {
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
public post(url: string, payload: BodyInit): Promise<Response> {
|
||||
return this.fetch(url, {
|
||||
method: 'POST',
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-type': 'application/json'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public get(url: string): Promise<Response> {
|
||||
return this.fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public put(url: string, payload: BodyInit): Promise<Response> {
|
||||
return this.fetch(url, {
|
||||
method: 'PUT',
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-type': 'application/json'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public delete(url: string, payload?: BodyInit): Promise<Response> {
|
||||
if (payload != null) {
|
||||
return this.fetch(url, {
|
||||
method: 'DELETE',
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-type': 'application/json'
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return this.fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-type': 'application/json'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public async downloadFile(
|
||||
url: string,
|
||||
destination?: string,
|
||||
index?: number
|
||||
): Promise<string> {
|
||||
const response = await this.get(url)
|
||||
if (!response.ok) {
|
||||
throw new Error('Response error.')
|
||||
}
|
||||
let filename: string
|
||||
try {
|
||||
filename = response.headers
|
||||
.get('content-disposition')
|
||||
.match(/attachment;filename=(.+)/)[1]
|
||||
} catch {
|
||||
try {
|
||||
filename = url.split('/').pop()
|
||||
} catch {
|
||||
filename = `file${index}`
|
||||
}
|
||||
}
|
||||
|
||||
if (destination) {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
await new Promise(async (resolve, reject) => {
|
||||
fs.mkdirSync(destination, { recursive: true })
|
||||
const fileStream = fs.createWriteStream(`${destination}${filename}`)
|
||||
response.body.pipe(fileStream)
|
||||
response.body.on('error', reject)
|
||||
fileStream.on('finish', resolve)
|
||||
})
|
||||
|
||||
return destination
|
||||
} else {
|
||||
save(await response.arrayBuffer(), filename)
|
||||
}
|
||||
}
|
||||
|
||||
private async fetch(url: string, opts: RequestInit): Promise<Response> {
|
||||
const result = await fetch(url, opts)
|
||||
if (!result.ok) {
|
||||
this.logger.error(`Error requesting [${opts.method}] ${url}`)
|
||||
this.logger.error(`Response message: \n${await result.text()}`)
|
||||
throw result
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
47
src/utils/ConversionTypeHelpers.ts
Normal file
47
src/utils/ConversionTypeHelpers.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { LoggerInstance } from './Logger'
|
||||
|
||||
// Ox transformer
|
||||
export const zeroX = (input: string) => zeroXTransformer(input, true)
|
||||
export const noZeroX = (input: string) => zeroXTransformer(input, false)
|
||||
export function zeroXTransformer(input: string = '', zeroOutput: boolean) {
|
||||
const { valid, output } = inputMatch(
|
||||
input,
|
||||
/^(?:0x)*([a-f0-9]+)$/i,
|
||||
'zeroXTransformer'
|
||||
)
|
||||
return (zeroOutput && valid ? '0x' : '') + output
|
||||
}
|
||||
|
||||
// did:op: transformer
|
||||
export const didPrefixed = (input: string) => didTransformer(input, true)
|
||||
export const noDidPrefixed = (input: string) => didTransformer(input, false)
|
||||
export function didTransformer(input: string = '', prefixOutput: boolean) {
|
||||
const { valid, output } = inputMatch(
|
||||
input,
|
||||
/^(?:0x|did:op:)*([a-f0-9]{64})$/i,
|
||||
'didTransformer'
|
||||
)
|
||||
return (prefixOutput && valid ? 'did:op:' : '') + output
|
||||
}
|
||||
|
||||
// 0x + did:op: transformer
|
||||
export const didZeroX = (input: string) => zeroX(didTransformer(input, false))
|
||||
|
||||
// Shared functions
|
||||
function inputMatch(
|
||||
input: string,
|
||||
regexp: RegExp,
|
||||
conversorName: string
|
||||
): { valid: boolean; output: string } {
|
||||
if (typeof input !== 'string') {
|
||||
LoggerInstance.debug('Not input string:')
|
||||
LoggerInstance.debug(input)
|
||||
throw new Error(`[${conversorName}] Expected string, input type: ${typeof input}`)
|
||||
}
|
||||
const match = input.match(regexp)
|
||||
if (!match) {
|
||||
LoggerInstance.warn(`[${conversorName}] Input transformation failed.`)
|
||||
return { valid: false, output: input }
|
||||
}
|
||||
return { valid: true, output: match[1] }
|
||||
}
|
47
src/utils/DDOHelpers.ts
Normal file
47
src/utils/DDOHelpers.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { DDO } from '../ddo/DDO'
|
||||
import {
|
||||
ServiceAgreementTemplateCondition,
|
||||
ServiceAgreementTemplateParameter
|
||||
} from '../ddo/ServiceAgreementTemplate'
|
||||
|
||||
function fillParameterWithDDO(
|
||||
parameter: ServiceAgreementTemplateParameter,
|
||||
ddo: DDO
|
||||
): ServiceAgreementTemplateParameter {
|
||||
const getValue = name => {
|
||||
switch (name) {
|
||||
case 'amount':
|
||||
case 'price':
|
||||
return String(ddo.findServiceByType('metadata').attributes.main.price)
|
||||
case 'assetId':
|
||||
case 'documentId':
|
||||
case 'documentKeyId':
|
||||
return ddo.shortId()
|
||||
case 'rewardAddress':
|
||||
return ddo.publicKey[0].owner
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
const value = getValue(parameter.name.replace(/^_/, ''))
|
||||
|
||||
return { ...parameter, value }
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill some static parameters that depends on the metadata.
|
||||
* @param {ServiceAgreementTemplateCondition[]} conditions Conditions to fill.
|
||||
* @param {DDO} ddo DDO related to this conditions.
|
||||
* @return {ServiceAgreementTemplateCondition[]} Filled conditions.
|
||||
*/
|
||||
export function fillConditionsWithDDO(
|
||||
conditions: ServiceAgreementTemplateCondition[],
|
||||
ddo: DDO
|
||||
): ServiceAgreementTemplateCondition[] {
|
||||
return conditions.map(condition => ({
|
||||
...condition,
|
||||
parameters: condition.parameters.map(parameter => ({
|
||||
...fillParameterWithDDO(parameter, ddo)
|
||||
}))
|
||||
}))
|
||||
}
|
9
src/utils/GeneratorHelpers.ts
Normal file
9
src/utils/GeneratorHelpers.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { v4 } from 'uuid'
|
||||
|
||||
export function generateId(length = 64) {
|
||||
let id = ''
|
||||
while (id.length < length) {
|
||||
id += v4().replace(/-/g, '')
|
||||
}
|
||||
return id.substr(0, length)
|
||||
}
|
44
src/utils/Logger.ts
Normal file
44
src/utils/Logger.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
export enum LogLevel {
|
||||
None = -1,
|
||||
Error = 0,
|
||||
Warn = 1,
|
||||
Log = 2,
|
||||
Verbose = 3
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
constructor(private logLevel: LogLevel = LogLevel.Verbose) {}
|
||||
|
||||
public setLevel(logLevel: LogLevel) {
|
||||
this.logLevel = logLevel
|
||||
}
|
||||
|
||||
public bypass(...args: any[]) {
|
||||
this.dispatch('log', -Infinity as any, ...args)
|
||||
}
|
||||
|
||||
public debug(...args: any[]) {
|
||||
this.dispatch('debug', LogLevel.Verbose, ...args)
|
||||
}
|
||||
|
||||
public log(...args: any[]) {
|
||||
this.dispatch('log', LogLevel.Log, ...args)
|
||||
}
|
||||
|
||||
public warn(...args: any[]) {
|
||||
this.dispatch('warn', LogLevel.Warn, ...args)
|
||||
}
|
||||
|
||||
public error(...args: any[]) {
|
||||
this.dispatch('error', LogLevel.Error, ...args)
|
||||
}
|
||||
|
||||
private dispatch(verb: string, level: LogLevel, ...args: any[]) {
|
||||
if (this.logLevel >= level) {
|
||||
console[verb](...args) // eslint-disable-line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const LoggerInstance = new Logger()
|
||||
export default LoggerInstance
|
15
src/utils/PromiseResolver.ts
Normal file
15
src/utils/PromiseResolver.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
const zipObject = (keys = [], values = []) => {
|
||||
return keys.reduce(
|
||||
(acc, key, index) => ({
|
||||
...acc,
|
||||
[key]: values[index]
|
||||
}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
export const objectPromiseAll = async (obj: { [key: string]: Promise<any> }) => {
|
||||
const keys = Object.keys(obj)
|
||||
const result = await Promise.all(Object.values(obj))
|
||||
return zipObject(keys, result)
|
||||
}
|
51
src/utils/SubscribableObserver.ts
Normal file
51
src/utils/SubscribableObserver.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
export class SubscribableObserver<T, P> {
|
||||
public completed: boolean = false
|
||||
|
||||
private subscriptions = new Set<{
|
||||
onNext?: (next: T) => void
|
||||
onComplete?: (complete: P) => void
|
||||
onError?: (error: any) => void
|
||||
}>()
|
||||
|
||||
public subscribe(
|
||||
onNext?: (next: T) => void,
|
||||
onComplete?: (complete: P) => void,
|
||||
onError?: (error: any) => void
|
||||
) {
|
||||
if (this.completed) {
|
||||
throw new Error('Observer completed.')
|
||||
}
|
||||
const subscription = { onNext, onComplete, onError }
|
||||
this.subscriptions.add(subscription)
|
||||
|
||||
return {
|
||||
unsubscribe: () => this.subscriptions.delete(subscription)
|
||||
}
|
||||
}
|
||||
|
||||
public next(next?: T): void {
|
||||
this.emit('onNext', next)
|
||||
}
|
||||
|
||||
public complete(resolve?: P): void {
|
||||
this.emit('onComplete', resolve)
|
||||
this.unsubscribe()
|
||||
}
|
||||
|
||||
public error(error?: any): void {
|
||||
this.emit('onError', error)
|
||||
this.unsubscribe()
|
||||
}
|
||||
|
||||
private emit(type: 'onNext' | 'onComplete' | 'onError', value: any) {
|
||||
Array.from(this.subscriptions)
|
||||
.map(subscription => subscription[type])
|
||||
.filter((callback: any) => callback && typeof callback === 'function')
|
||||
.forEach((callback: any) => callback(value))
|
||||
}
|
||||
|
||||
private unsubscribe() {
|
||||
this.completed = true
|
||||
this.subscriptions.clear()
|
||||
}
|
||||
}
|
56
src/utils/SubscribablePromise.ts
Normal file
56
src/utils/SubscribablePromise.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { SubscribableObserver } from './SubscribableObserver'
|
||||
|
||||
export class SubscribablePromise<T extends any, P extends any> {
|
||||
private observer = new SubscribableObserver<T, P>()
|
||||
|
||||
private promise = Object.assign(
|
||||
new Promise<P>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
this.observer.subscribe(undefined, resolve, reject)
|
||||
}, 0)
|
||||
}),
|
||||
this
|
||||
)
|
||||
|
||||
constructor(executor: (observer: SubscribableObserver<T, P>) => void | Promise<P>) {
|
||||
// Defear
|
||||
setTimeout(() => this.init(executor), 1)
|
||||
}
|
||||
|
||||
public subscribe(onNext: (next: T) => void) {
|
||||
return this.observer.subscribe(onNext)
|
||||
}
|
||||
|
||||
public next(onNext: (next: T) => void) {
|
||||
this.observer.subscribe(onNext)
|
||||
return this
|
||||
}
|
||||
|
||||
public then(onfulfilled?: (value: P) => any, onrejected?: (error: any) => any) {
|
||||
return Object.assign(this.promise.then(onfulfilled, onrejected), this)
|
||||
}
|
||||
|
||||
public catch(onrejected?: (error: any) => any) {
|
||||
return Object.assign(this.promise.catch(onrejected), this)
|
||||
}
|
||||
|
||||
public finally(onfinally?: () => any) {
|
||||
return Object.assign(this.promise.finally(onfinally), this)
|
||||
}
|
||||
|
||||
private init(executor: (observer: SubscribableObserver<T, P>) => void | Promise<P>) {
|
||||
const execution = executor(this.observer)
|
||||
|
||||
Promise.resolve(execution as any)
|
||||
.then(result => {
|
||||
if (typeof (execution as any).then === 'function') {
|
||||
this.observer.complete(result)
|
||||
}
|
||||
})
|
||||
.catch(result => {
|
||||
if (typeof (execution as any).then === 'function') {
|
||||
this.observer.error(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
7
src/utils/index.ts
Normal file
7
src/utils/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export * from './PromiseResolver'
|
||||
export * from './Logger'
|
||||
export * from './ConversionTypeHelpers'
|
||||
export * from './GeneratorHelpers'
|
||||
export * from './DDOHelpers'
|
||||
export * from './SubscribablePromise'
|
||||
export * from './SubscribableObserver'
|
22
tsconfig.json
Normal file
22
tsconfig.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"lib": ["es2017", "es6", "es7", "dom"],
|
||||
"declaration": true,
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"experimentalDecorators": true,
|
||||
"preserveConstEnums": true,
|
||||
"outDir": "./dist/node/",
|
||||
"rootDir": "./src/",
|
||||
"sourceMap": true,
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user