diff --git a/package.json b/package.json index a329ea0..4e5b43d 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "dist/squid.js", "scripts": { "test": "eslint ./src", - "start": "babel src --watch --out-dir dist", - "build": "babel src --out-dir dist", + "start": "babel ./src/ --watch --out-dir ./dist/", + "build": "babel ./src/ --out-dir ./dist/", "release": "./node_modules/release-it/bin/release-it.js --src.tagName='v%s' --github.release --npm.publish --non-interactive", "release-minor": "./node_modules/release-it/bin/release-it.js minor --src.tagName='v%s' --github.release --npm.publish --non-interactive", "release-major": "./node_modules/release-it/bin/release-it.js major --src.tagName='v%s' --github.release --npm.publish --non-interactive", diff --git a/src/contractLoader.js b/src/contractLoader.js deleted file mode 100644 index 26e9ca6..0000000 --- a/src/contractLoader.js +++ /dev/null @@ -1,19 +0,0 @@ -import TruffleContract from 'truffle-contract' - -const contracts = [] - -export default class ContractLoader { - static async _doLoad(what, where, provider) { - // console.log("Loading", what, "from", where) - /* eslint-disable-next-line */ - const artifact = require(`@oceanprotocol/keeper-contracts/artifacts/${what}.${where}`) - const contract = TruffleContract(artifact) - contract.setProvider(provider) - contracts[what] = await contract.at(artifact.address) - return contracts[what] - } - - static load(what, where, provider) { - return contracts[what] || ContractLoader._doLoad(what, where, provider) - } -} diff --git a/src/keeper/auth.js b/src/keeper/auth.js new file mode 100644 index 0000000..14529c2 --- /dev/null +++ b/src/keeper/auth.js @@ -0,0 +1,29 @@ +import ContractLoader from './contractLoader' +import KeeperBase from './keeper-base' + +export default class OceanAuth extends KeeperBase { + constructor(web3, network) { + super(web3, network) + + const instance = this + + return { + async getInstance() { + instance.contract = ContractLoader.load('OceanAuth', instance._network, instance._web3) + return instance + } + } + } + + cancelAccessRequest(orderId, senderAddress) { + return this.contract.cancelAccessRequest(orderId, { from: senderAddress }) + } + + getOrderStatus(orderId) { + return this.contract.statusOfAccessRequest(orderId) + } + + getEncryptedAccessToken(orderId, senderAddress) { + return this.contract.getEncryptedAccessToken(orderId, { from: senderAddress }) + } +} diff --git a/src/keeper/contractLoader.js b/src/keeper/contractLoader.js new file mode 100644 index 0000000..bede559 --- /dev/null +++ b/src/keeper/contractLoader.js @@ -0,0 +1,23 @@ +import TruffleContract from 'truffle-contract' +import Logger from '../utils/logger' + +const contracts = [] + +export default class ContractLoader { + static async _doLoad(what, where, web3) { + Logger.log('Loading', what, 'from', where) + /* eslint-disable-next-line security/detect-non-literal-require */ + const artifact = require(`@oceanprotocol/keeper-contracts/artifacts/${what}.${where}`) + Logger.log('Loaded artifact', artifact) + + const contract = TruffleContract(artifact) + Logger.log('Getting instance of', what, 'from', where, 'at', artifact.address) + contract.setProvider(web3.currentProvider) + contracts[what] = await contract.at(artifact.address) + return contracts[what] + } + + static async load(what, where, provider) { + return contracts[what] || ContractLoader._doLoad(what, where, provider) + } +} diff --git a/src/keeper/keeper-base.js b/src/keeper/keeper-base.js new file mode 100644 index 0000000..70dee1d --- /dev/null +++ b/src/keeper/keeper-base.js @@ -0,0 +1,7 @@ +export default class KeeperBase { + constructor(web3, network) { + this._web3 = web3 + this._network = network + this.contract = null + } +} diff --git a/src/keeper/market.js b/src/keeper/market.js new file mode 100644 index 0000000..9dbc59b --- /dev/null +++ b/src/keeper/market.js @@ -0,0 +1,58 @@ +import ContractLoader from './contractLoader' +import KeeperBase from './keeper-base' + +import Logger from '../utils/logger' + +export default class OceanMarket extends KeeperBase { + constructor(web3, network) { + super(web3, network) + + const instance = this + + return { + async getInstance() { + instance.contract = await ContractLoader.load('OceanMarket', instance._network, instance._web3) + + return instance + } + } + } + + // call functions (costs no gas) + checkAsset(assetId) { + return this.contract.checkAsset(assetId) + } + + verifyOrderPayment(orderId) { + return this.contract.verifyPaymentReceived(orderId) + } + + getAssetPrice(assetId) { + return this.contract.getAssetPrice(assetId) + .then((price) => price.toNumber()) + } + + // Transactions with gas cost + requestTokens(senderAddress, numTokens) { + return this.contract.requestTokens(numTokens, { from: senderAddress }) + } + + async registerAsset(name, description, price, publisherAddress) { + const assetId = await this.contract.generateId(name + description) + const result = await this.contract.register( + assetId, + price, + { from: publisherAddress, gas: this.defaultGas } + ) + Logger.log('registered: ', result) + return assetId + } + + async payAsset(assetId, order, publisherAddress, senderAddress) { + let assetPrice = await this.contract.getAssetPrice(assetId).then((price) => price.toNumber()) + this.contract.sendPayment(order.id, publisherAddress, assetPrice, order.timeout, { + from: senderAddress, + gas: 2000000 + }) + } +} diff --git a/src/keeper/token.js b/src/keeper/token.js new file mode 100644 index 0000000..7eac890 --- /dev/null +++ b/src/keeper/token.js @@ -0,0 +1,34 @@ +import ContractLoader from './contractLoader' +import KeeperBase from './keeper-base' +import { Logger } from '../utils/logger' + +export default class OceanToken extends KeeperBase { + constructor(web3, network) { + super(web3, network) + + const instance = this + + return { + async getInstance() { + instance.contract = ContractLoader.load('OceanToken', instance._network, instance._web3) + + return instance + } + } + } + + getTokenBalance(accountAddress) { + return this.contract.balanceOf.call(accountAddress) + } + + getEthBalance(account) { + return new Promise((resolve, reject) => { + Logger.log('getting balance for', account) + super._web3.eth.getBalance(account, 'latest', (err, balance) => { + if (err) return reject(err) + Logger.log('balance', balance) + resolve(balance) + }) + }) + } +} diff --git a/src/ocean-agent.js b/src/ocean-agent.js index 902ac9c..19ae2ac 100644 --- a/src/ocean-agent.js +++ b/src/ocean-agent.js @@ -1,5 +1,5 @@ /* global fetch */ -import Logger from 'logger' +import Logger from './utils/logger' export default class OceanAgent { constructor(connectionUrl) { diff --git a/src/ocean-keeper.js b/src/ocean-keeper.js index a00cafb..37cb243 100644 --- a/src/ocean-keeper.js +++ b/src/ocean-keeper.js @@ -1,6 +1,6 @@ import Web3 from 'web3' -import ContractLoader from './contractLoader' -import Logger from './logger' +import ContractLoader from './keeper/contractLoader' +import Logger from './utils/logger' const DEFAULT_GAS = 300000 @@ -10,12 +10,14 @@ export default class OceanKeeper { this.web3 = new Web3(web3Provider) this.defaultGas = DEFAULT_GAS this.network = network || 'development' + + Logger.warn('OceanKeeper is deprecated use the Ocean object from squid instead') } async initContracts() { - this.oceanToken = await ContractLoader.load('OceanToken', this.network, this.web3.currentProvider) - this.oceanMarket = await ContractLoader.load('OceanMarket', this.network, this.web3.currentProvider) - this.oceanAuth = await ContractLoader.load('OceanAuth', this.network, this.web3.currentProvider) + this.oceanToken = await ContractLoader.load('OceanToken', this.network, this.web3) + this.oceanMarket = await ContractLoader.load('OceanMarket', this.network, this.web3) + this.oceanAuth = await ContractLoader.load('OceanAuth', this.network, this.web3) return { oceanToken: this.oceanToken, diff --git a/src/ocean.js b/src/ocean.js new file mode 100644 index 0000000..3d8688c --- /dev/null +++ b/src/ocean.js @@ -0,0 +1,123 @@ +import Web3 from 'web3' +import OceanMarket from './keeper/market' +import OceanAuth from './keeper/auth' +import OceanToken from './keeper/token' +import Logger from './utils/logger' +import Web3Helper from './utils/Web3Helper' + +const DEFAULT_GAS = 300000 + +export default class Ocean { + constructor(config) { + const web3Provider = config.web3Provider || new Web3.providers.HttpProvider(config.uri) + this._web3 = new Web3(web3Provider) + this._defaultGas = config.gas || DEFAULT_GAS + this._network = config.network || 'development' + + this.helper = new Web3Helper(this._web3) + + const instance = this + + return { + async getInstance() { + instance.market = await new OceanMarket(instance._web3, instance._network).getInstance() + instance.auth = await new OceanAuth(instance._web3, instance._network).getInstance() + instance.token = await new OceanToken(instance._web3, instance._network).getInstance() + + return instance + } + } + } + + async getOrdersByConsumer(consumerAddress) { + let accessConsentEvent = this.auth.AccessConsentRequested({ _consumer: consumerAddress }, { + fromBlock: 0, + toBlock: 'latest' + }) + + let _resolve = null + let _reject = null + const promise = new Promise((resolve, reject) => { + _resolve = resolve + _reject = reject + }) + + const getEvents = () => { + accessConsentEvent.get((error, logs) => { + if (error) { + _reject(error) + throw new Error(error) + } else { + _resolve(logs) + } + }) + return promise + } + const events = await getEvents().then((events) => events) + // let orders = await this.buildOrdersFromEvents(events, consumerAddress).then((result) => result) + let orders = events + .filter(obj => (obj.args._consumer === consumerAddress)) + .map(async (event) => ({ + ...event.args, + timeout: event.args._timeout.toNumber(), + status: await this.getOrderStatus(event.args._id).then((status) => status.toNumber()), + paid: await this.verifyOrderPayment(event.args._id).then((received) => received), + key: null + })) + Logger.debug('got orders: ', orders) + return orders + } + + purchaseAsset( + assetId, publisherId, price, privateKey, publicKey, timeout, senderAddress, + initialRequestEventHandler, accessCommittedEventHandler, tokenPublishedEventHandler) { + const { token, market, auth } = this + // Allow market contract to transfer funds on the consumer's behalf + token.approve(market.address, price, { from: senderAddress, gas: 2000000 }) + // Submit the access request + auth.initiateAccessRequest( + assetId, publisherId, publicKey, + timeout, { from: senderAddress, gas: 1000000 } + ) + + const resourceFilter = { _resourceId: assetId, _consumer: senderAddress } + const initRequestEvent = auth.contract.AccessConsentRequested(resourceFilter) + let order = {} + this._listenOnce( + initRequestEvent, + 'AccessConsentRequested', + (result, error) => { + order = initialRequestEventHandler(result, error) + const requestIdFilter = { _id: order.id } + const accessCommittedEvent = auth.contract.AccessRequestCommitted(requestIdFilter) + const tokenPublishedEvent = auth.contract.EncryptedTokenPublished(requestIdFilter) + this._listenOnce( + accessCommittedEvent, + 'AccessRequestCommitted', + (result, error) => { + accessCommittedEventHandler(result, order, error) + } + ) + this._listenOnce( + tokenPublishedEvent, + 'EncryptedTokenPublished', + (result, error) => { + tokenPublishedEventHandler(result, order, error) + } + ) + }) + return order + } + + // Helper functions (private) + _listenOnce(event, eventName, callback) { + // eslint-disable-next-line security/detect-non-literal-fs-filename + event.watch((error, result) => { + event.stopWatching() + if (error) { + Logger.log(`Error in keeper ${eventName} event: `, error) + } + callback(result, error) + }) + } +} diff --git a/src/squid.js b/src/squid.js index 98f8efd..9ae7cc2 100644 --- a/src/squid.js +++ b/src/squid.js @@ -1,7 +1,11 @@ import OceanAgent from './ocean-agent' import OceanKeeper from './ocean-keeper' +import Ocean from './ocean' +import Logger from './utils/logger' export { + Ocean, OceanAgent, - OceanKeeper + OceanKeeper, + Logger } diff --git a/src/utils/Web3Helper.js b/src/utils/Web3Helper.js new file mode 100644 index 0000000..24e6f0c --- /dev/null +++ b/src/utils/Web3Helper.js @@ -0,0 +1,17 @@ +import Logger from './logger' + +export default class Web3Helper { + constructor(web3) { + this._web3 = web3 + } + + getAccounts() { + Logger.log(this._web3) + return this._web3.eth.accounts + } + + // web3 wrappers + sign(accountAddress, message) { + return this._web3.eth.sign(accountAddress, message) + } +} diff --git a/src/logger.js b/src/utils/logger.js similarity index 83% rename from src/logger.js rename to src/utils/logger.js index cd16caf..8a3b5a3 100644 --- a/src/logger.js +++ b/src/utils/logger.js @@ -12,6 +12,10 @@ export default class Logger { Logger.dispatch('debug', ...args) } + static warn(...args) { + Logger.dispatch('warn', ...args) + } + static error(...args) { Logger.dispatch('error', ...args) }