1
0
mirror of https://github.com/oceanprotocol/ocean.js.git synced 2024-11-26 20:39:05 +01:00

Merge pull request #30 from oceanprotocol/feature/alex_play

Feature: move squid-js to ocean-lib-js
This commit is contained in:
kin 2020-05-28 11:55:04 +02:00 committed by GitHub
commit cee10e72cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 18601 additions and 0 deletions

15
.editorconfig Normal file
View 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
View 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
View 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
View 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

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v12

6
.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 90,
"trailingComma": "none"
}

10
library.json Normal file
View 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

File diff suppressed because it is too large Load Diff

123
package.json Normal file
View File

@ -0,0 +1,123 @@
{
"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:metadata": "./scripts/get-metadata.js > src/metadata.json",
"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",
"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
}
}
}

19
scripts/get-metadata.js Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env node
'use strict'
const packageInfo = require('../package.json')
const execSync = require('child_process').execSync
process.stdout.write(
JSON.stringify(
{
version: require('../package.json').version,
commit: execSync(`git rev-parse HEAD`)
.toString()
.trim()
},
null,
' '
)
)

48
src/.eslintrc Normal file
View 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
View File

@ -0,0 +1 @@
declare module '@ethereum-navigator/navigator'

View 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: config.web3Provider,
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)
}
}

425
src/aquarius/Aquarius.ts Normal file
View File

@ -0,0 +1,425 @@
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()}`
}
public getURI() {
return `${this.url}`
}
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
}
}
}

99
src/brizo/Brizo.ts Normal file
View File

@ -0,0 +1,99 @@
import { File, MetaDataAlgorithm } from '../ddo/MetaData'
import Account from '../ocean/Account'
import { noZeroX, noDidPrefixed } from '../utils'
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
import { DDO } from '../ddo/DDO'
import { ServiceType } from '../ddo/Service'
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 getURI() {
return `${this.url}`
}
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 createSignature(account: Account, agreementId: string): Promise<string> {
const signature = await this.ocean.utils.signature.signText(
noZeroX(agreementId),
account.getId()
)
return signature
}
public async createHashSignature(account: Account, message: string): Promise<string> {
const signature = await this.ocean.utils.signature.signWithHash(
message,
account.getId()
)
return signature
}
public async encrypt(
did: string,
document: any,
account: Account,
dtAddress: string
): Promise<string> {
const signature = this.ocean.utils.signature.signWithHash(
did,
account.getId(),
account.getPassword()
)
const args = {
did,
signature,
document: JSON.stringify(document),
publisherAddress: account.getId()
}
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')
}
}
}

View File

@ -0,0 +1,146 @@
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
return ''
}
/**
* 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
return ''
}
/**
* Approve & Lock for a specified number of blocks (reverts after that if not used)
* @param {String} dataTokenAddress
* @param {String} toAddress
* @param {Number} amount
* @param {Number} blocks
* @param {Account} account
* @return {Promise<string>} transactionId
*/
public async approveAndLock(
dataTokenAddress: string,
toAddress: string,
amount: number,
blocks: number,
account: Account
): Promise<string> {
// TO DO
return ''
}
/**
* 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
return ''
}
/**
* 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
return ''
}
/**
* 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
return ''
}
/**
* Get Account Balance for datatoken
* @param {String} dataTokenAddress
* @param {Account} account
* @return {Promise<number>} balance
*/
public async balance(dataTokenAddress: string, account: Account): Promise<number> {
// TO DO
return 0
}
}

View File

@ -0,0 +1 @@
export const OceanDataTokenABI = {}

View File

@ -0,0 +1 @@
export const OceanFactoryABI = {}

View File

@ -0,0 +1,4 @@
export interface Authentication {
type: string
publicKey: string
}

161
src/ddo/DDO.ts Normal file
View File

@ -0,0 +1,161 @@
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.
* @param {Any} web3Provider
* @return {string[]} DDO checksum.
*/
public getChecksum(web3Provider: any): 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(ocean.web3Provider)
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
View 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
View File

@ -0,0 +1,6 @@
export interface Proof {
type: string
created: string
creator: string
signatureValue: string
}

32
src/ddo/PublicKey.ts Normal file
View 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
View 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
View File

@ -0,0 +1 @@
export * from './lib'

23
src/lib.ts Normal file
View File

@ -0,0 +1,23 @@
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, OrderProgressStep } from './ocean/Assets'
export {
OceanPlatformTechStatus,
OceanPlatformTech,
OceanPlatformVersions
} from './ocean/Versions'
export { Ocean, Account, Config, DID, Logger, Aquarius, DataTokens, utils }

65
src/models/Config.ts Normal file
View 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

110
src/ocean/Account.ts Normal file
View File

@ -0,0 +1,110 @@
import BigNumber from 'bignumber.js'
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
/**
* Account information.
*/
export default class Accounts 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
}
// TODO - Check with Samer if authentificate is still needed or we can use sign
/**
* 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 Any Token.
* @return {Promise<number>}
*/
public async getTokenBalance(TokenAdress: string): Promise<number> {
// TO DO
return 0
}
/**
* Symbol of a Token
* @return {Promise<string>}
*/
public async getTokenSymbol(TokenAdress: string): Promise<string> {
// TO DO
return ''
}
/**
* Balance of Ether.
* @return {Promise<number>}
*/
public async getEtherBalance(): Promise<number> {
// TO DO
/* return this.web3.eth
.getBalance(this.id, 'latest')
.then((balance: string): number => {
return new BigNumber(balance).toNumber()
})
*/
return 0
}
}

51
src/ocean/Accounts.ts Normal file
View File

@ -0,0 +1,51 @@
import Account from './Account'
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
/**
* Account submodule of Ocean Protocol.
*/
export class Accounts extends Instantiable {
/**
* Returns the instance of OceanAccounts.
* @return {Promise<OceanAccounts>}
*/
public static async getInstance(config: InstantiableConfig): Promise<Accounts> {
const instance = new Accounts()
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 {String} TokenAddress .
* @param {Account} account Account instance.
* @return {Promise<Balance>} Ether and Ocean Token balance.
*/
public balance(TokenAddress: string, account: Account): Promise<number> {
return account.getTokenBalance(TokenAddress)
}
/**
* Return account balance in ETH
* @param {Account} account Account instance.
* @return {Promise<Balance>} Ether and Ocean Token balance.
*/
public ETHbalance(account: Account): Promise<number> {
return account.getEtherBalance()
}
}

172
src/ocean/Assets.ts Normal file
View File

@ -0,0 +1,172 @@
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 { SubscribablePromise, didZeroX } from '../utils'
import { Instantiable, InstantiableConfig } from '../Instantiable.abstract'
import { DataTokens } from '../lib'
export enum CreateProgressStep {
CreatingDataToken,
DataTokenCreated,
EncryptingFiles,
FilesEncrypted,
GeneratingProof,
ProofGenerated,
StoringDdo,
DdoStored
}
export enum OrderProgressStep {
CreatingAgreement,
AgreementInitialized,
LockingPayment,
LockedPayment
}
/**
* Assets submodule of Ocean Protocol.
*/
export class Assets extends Instantiable {
/**
* Returns the instance of Assets.
* @return {Promise<Assets>}
*/
public static async getInstance(config: InstantiableConfig): Promise<Assets> {
const instance = new Assets()
instance.setInstanceConfig(config)
return instance
}
/**
* Creates a simple asset and a datatoken
* @param {Account} publisher Publisher account.
* @return {Promise<String>}
*/
public createSimpleAsset(publisher: Account): Promise<string> {
const publisherURI = this.ocean.brizo.getURI()
const jsonBlob = { t: 0, url: publisherURI }
const { datatokens } = this.ocean
return datatokens.create(JSON.stringify(jsonBlob), publisher)
}
/**
* Creates a new DDO and publishes it
* @param {MetaData} metadata DDO metadata.
* @param {Account} publisher Publisher account.
* @param {list} services list of Service description documents
* @return {Promise<DDO>}
*/
public create(
metadata: MetaData,
publisher: Account,
services: Service[] = [],
dtAddress?: string
): SubscribablePromise<CreateProgressStep, DDO> {
this.logger.log('Creating asset')
return new SubscribablePromise(async observer => {
if (services.length === 0) {
this.logger.log('You have no services. Are you sure about this?')
}
if (!dtAddress) {
this.logger.log('Creating datatoken')
observer.next(CreateProgressStep.CreatingDataToken)
const metadataStoreURI = this.ocean.aquarius.getURI()
const jsonBlob = { t: 1, url: metadataStoreURI }
const { datatokens } = this.ocean
dtAddress = await datatokens.create(JSON.stringify(jsonBlob), publisher)
this.logger.log('DataToken creted')
observer.next(CreateProgressStep.DataTokenCreated)
}
const did: DID = DID.generate()
this.logger.log('Encrypting files')
observer.next(CreateProgressStep.EncryptingFiles)
const encryptedFiles = await this.ocean.brizo.encrypt(
did.getId(),
metadata.main.files,
publisher,
dtAddress
)
this.logger.log('Files encrypted')
observer.next(CreateProgressStep.FilesEncrypted)
let indexCount = 0
// create ddo itself
const ddo: DDO = new DDO({
id: did.getDid(),
dtAddress: dtAddress,
authentication: [
{
type: 'RsaSignatureAuthentication2018',
publicKey: did.getDid()
}
],
publicKey: [
{
id: did.getDid(),
type: 'EthereumECDSAKey',
owner: publisher.getId()
}
],
service: [
{
type: 'metadata',
attributes: {
// Default values
curation: {
rating: 0,
numVotes: 0
},
// Overwrites defaults
...metadata,
encryptedFiles,
// Cleaning not needed information
main: {
...metadata.main,
files: metadata.main.files.map((file, index) => ({
...file,
index,
url: undefined
}))
} as any
}
},
...services
]
// Remove duplications
.reverse()
.filter(
({ type }, i, list) =>
list.findIndex(({ type: t }) => t === type) === i
)
.reverse()
// Adding index
.map(_ => ({
..._,
index: indexCount++
})) as Service[]
})
this.logger.log('Generating proof')
observer.next(CreateProgressStep.GeneratingProof)
await ddo.addProof(this.ocean, publisher.getId(), publisher.getPassword())
this.logger.log('Proof generated')
observer.next(CreateProgressStep.ProofGenerated)
this.logger.log('Storing DDO')
observer.next(CreateProgressStep.StoringDdo)
const storedDdo = await this.ocean.aquarius.storeDDO(ddo)
this.logger.log('DDO stored')
observer.next(CreateProgressStep.DdoStored)
return storedDdo
})
}
}

65
src/ocean/DID.ts Normal file
View 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
}
}

133
src/ocean/Ocean.ts Normal file
View File

@ -0,0 +1,133 @@
import { Accounts } from './Accounts'
import { Assets } from './Assets'
// import { Compute } from './Compute'
import { Versions } from './Versions'
import { OceanUtils } from './utils/Utils'
import { Aquarius } from '../aquarius/Aquarius'
import { Brizo } from '../brizo/Brizo'
import { DataTokens } from '../datatokens/Datatokens'
import { Config } from '../models/Config'
import {
Instantiable,
generateIntantiableConfigFromConfig
} from '../Instantiable.abstract'
/**
* 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 Accounts.getInstance(instanceConfig)
// instance.auth = await Auth.getInstance(instanceConfig)
instance.assets = await Assets.getInstance(instanceConfig)
// instance.compute = await Compute.getInstance(instanceConfig)
instance.datatokens = new DataTokens(
instanceConfig.config.factoryAddress,
instanceConfig.config.factoryABI,
instanceConfig.config.datatokensABI,
instanceConfig.config.web3Provider
)
instance.versions = await Versions.getInstance(instanceConfig)
return instance
}
/**
* Brizo instance.
* @type {Brizo}
*/
public brizo: Brizo
/**
* Web3 provider.
* @type {any}
*/
public web3Provider: any
/**
* Aquarius instance.
* @type {Aquarius}
*/
public aquarius: Aquarius
/**
* Ocean account submodule
* @type {Accounts}
*/
public accounts: Accounts
/**
* Ocean auth submodule
* @type {OceanAuth}
public auth: OceanAuth
*/
/**
* Ocean assets submodule
* @type {Assets}
*/
public assets: Assets
/**
* Ocean compute submodule
* @type {Compute}
public compute: Compute
*/
/**
* Ocean secretStore submodule
* @type {OceanSecretStore}
*/
public datatokens: DataTokens
/**
* Ocean tokens submodule
* @type {OceanTokens}
public tokens: OceanTokens
*/
/**
* Ocean versions submodule
* @type {Versions}
*/
public versions: Versions
/**
* Ocean utils submodule
* @type {OceanUtils}
*/
public utils: OceanUtils
private constructor() {
super()
}
}

144
src/ocean/OceanAuth.ts Normal file
View 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
}
}

76
src/ocean/Versions.ts Normal file
View File

@ -0,0 +1,76 @@
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 OceanPlatformVersions {
lib: OceanPlatformTech
metadataStore: OceanPlatformTech
provider: OceanPlatformTech
status: {
ok: boolean
}
}
/**
* Versions submodule of Ocean Protocol.
*/
export class Versions extends Instantiable {
/**
* Returns the instance of Ocean Stack Versions.
* @return {Promise<Versions>}
*/
public static async getInstance(config: InstantiableConfig): Promise<Versions> {
const instance = new Versions()
instance.setInstanceConfig(config)
return instance
}
public async get(): Promise<OceanPlatformVersions> {
const versions = {} as OceanPlatformVersions
versions.lib = {
name: 'Lib',
version: metadata.version,
commit: metadata.commit,
status: OceanPlatformTechStatus.Working
}
// MetadataStore
try {
const { software: name, version } = await this.ocean.aquarius.getVersionInfo()
versions.metadataStore = {
name,
status: OceanPlatformTechStatus.Working,
version
}
} catch {
versions.metadataStore = {
name: 'MetadataStore',
status: OceanPlatformTechStatus.Stopped
}
}
// Status
const techs: OceanPlatformTech[] = Object.values(versions as any)
versions.status = {
ok: !techs.find(({ status }) => status !== OceanPlatformTechStatus.Working)
}
return versions
}
}

View File

@ -0,0 +1,103 @@
import Web3 from 'web3'
import { Logger } from '../../utils'
import { Account } from '../../lib'
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
}
}
}

34
src/ocean/utils/Utils.ts Normal file
View File

@ -0,0 +1,34 @@
import { Instantiable, InstantiableConfig } from '../../Instantiable.abstract'
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.signature = new SignatureUtils(config.web3, config.logger)
instance.fetch = new WebServiceConnector(config.logger)
return instance
}
/**
* Signature utils.
* @type {SignatureUtils}
*/
public signature: SignatureUtils
/**
* Fetch utils.
* @type {WebServiceConnector}
*/
public fetch: WebServiceConnector
}

View 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
}
}

View 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] }
}

View 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
View 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

View 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)
}

View 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()
}
}

View 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)
}
})
}
}

6
src/utils/index.ts Normal file
View File

@ -0,0 +1,6 @@
export * from './PromiseResolver'
export * from './Logger'
export * from './ConversionTypeHelpers'
export * from './GeneratorHelpers'
export * from './SubscribablePromise'
export * from './SubscribableObserver'

22
tsconfig.json Normal file
View 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"]
}