diff --git a/src/ConfigProvider.ts b/src/ConfigProvider.ts new file mode 100644 index 0000000..f66ef58 --- /dev/null +++ b/src/ConfigProvider.ts @@ -0,0 +1,15 @@ +import Config from "./models/Config" + +export default class ConfigProvider { + + public static getConfig() { + return ConfigProvider.config + } + + public static configure(config: Config) { + + ConfigProvider.config = config + } + + private static config: Config +} diff --git a/src/keeper/Auth.ts b/src/keeper/Auth.ts index 7e9bb53..266341b 100644 --- a/src/keeper/Auth.ts +++ b/src/keeper/Auth.ts @@ -1,13 +1,11 @@ import {Receipt} from "web3-utils" -import Asset from "../models/Asset" -import Config from "../models/Config" +import Asset from "../ocean/Asset" import ContractBaseWrapper from "./ContractWrapperBase" -import Web3Helper from "./Web3Helper" export default class OceanAuth extends ContractBaseWrapper { - public static async getInstance(config: Config, web3Helper: Web3Helper): Promise { - const auth: OceanAuth = new OceanAuth(config, "OceanAuth", web3Helper) + public static async getInstance(): Promise { + const auth: OceanAuth = new OceanAuth("OceanAuth") await auth.init() return auth } @@ -35,14 +33,11 @@ export default class OceanAuth extends ContractBaseWrapper { public async initiateAccessRequest(asset: Asset, publicKey: string, timeout: number, buyerAddress: string): Promise { - const args = [asset.assetId, asset.publisherId, publicKey, timeout] - const tx = this.contract.methods.initiateAccessRequest(...args) - const gas = await tx.estimateGas(args, { - from: buyerAddress, - }) - return tx.send({ - from: buyerAddress, - gas, - }) + const args = [asset.getId(), asset.publisher.getId(), publicKey, timeout] + return this.sendTransaction("initiateAccessRequest", buyerAddress, args) + } + + public async commitAccessRequest() { + // todo } } diff --git a/src/keeper/ContractHandler.ts b/src/keeper/ContractHandler.ts index 1528173..0b6f20b 100644 --- a/src/keeper/ContractHandler.ts +++ b/src/keeper/ContractHandler.ts @@ -1,19 +1,20 @@ import Contract from "web3-eth-contract" import Logger from "../utils/Logger" -import Web3Helper from "./Web3Helper" +import Web3Provider from "./Web3Provider" +import Keeper from "./Keeper" const contracts: Map = new Map() export default class ContractHandler { - public static async get(what: string, web3Helper: Web3Helper): Contract { - return contracts.get(what) || await ContractHandler.load(what, web3Helper) + public static async get(what: string): Contract { + return contracts.get(what) || await ContractHandler.load(what) } - public static async deployContracts(web3Helper: Web3Helper) { + public static async deployContracts() { Logger.log("Trying to deploy contracts") - const web3 = web3Helper.getWeb3() + const web3 = Web3Provider.getWeb3() const deployerAddress = (await web3.eth.getAccounts())[0] @@ -47,14 +48,14 @@ export default class ContractHandler { }) } - private static async load(what: string, web3Helper: Web3Helper): Promise { - const where = (await web3Helper.getNetworkName()).toLowerCase() + private static async load(what: string): Promise { + const web3 = Web3Provider.getWeb3() + const where = (await (await Keeper.getInstance()).getNetworkName()).toLowerCase() Logger.log("Loading", what, "from", where) try { const artifact = require(`@oceanprotocol/keeper-contracts/artifacts/${what}.${where}`) // Logger.log('Loaded artifact', artifact) // Logger.log("Getting instance of", what, "from", where, "at", artifact.address) - const web3 = web3Helper.getWeb3() const contract = new web3.eth.Contract(artifact.abi, artifact.address) Logger.log("Loaded", what, "from", where) contracts.set(what, contract) diff --git a/src/keeper/ContractWrapperBase.ts b/src/keeper/ContractWrapperBase.ts index 2676090..70980d7 100644 --- a/src/keeper/ContractWrapperBase.ts +++ b/src/keeper/ContractWrapperBase.ts @@ -1,25 +1,16 @@ import Event from "web3" import Contract from "web3-eth-contract" -import Config from "../models/Config" import ContractHandler from "./ContractHandler" -import Web3Helper from "./Web3Helper" -export default class ContractWrapperBase { +export default abstract class ContractWrapperBase { - public static async getInstance(config: Config, web3Helper: Web3Helper): Promise { - // stub - } + protected static instance = null protected contract: Contract = null - protected config: Config - protected web3Helper: Web3Helper - private contractName: string - constructor(config: Config, contractName: string, web3Helper: Web3Helper) { - this.config = config + constructor(contractName) { this.contractName = contractName - this.web3Helper = web3Helper } public async listenToEventOnce(eventName: string, options: any): Promise { @@ -48,7 +39,21 @@ export default class ContractWrapperBase { } protected async init() { - this.contract = await ContractHandler.get(this.contractName, this.web3Helper) + this.contract = await ContractHandler.get(this.contractName) + } + + protected async sendTransaction(name: string, from: string, args: any[]) { + if (!this.contract.methods[name]) { + throw new Error(`Method ${name} is not part of contract ${this.contractName}`) + } + const tx = this.contract.methods[name](...args) + const gas = await tx.estimateGas(args, { + from, + }) + return tx.send({ + from, + gas, + }) } } diff --git a/src/keeper/Keeper.ts b/src/keeper/Keeper.ts index 53193d2..2af79aa 100644 --- a/src/keeper/Keeper.ts +++ b/src/keeper/Keeper.ts @@ -1,28 +1,53 @@ -import Config from "../models/Config" import OceanAuth from "./Auth" import OceanMarket from "./Market" import OceanToken from "./Token" -import Web3Helper from "./Web3Helper" +import Web3Provider from "./Web3Provider" export default class Keeper { - public static async getInstance(config: Config, helper: Web3Helper) { + public static async getInstance() { - const contracts = new Keeper(helper) + if (Keeper.instance === null) { + Keeper.instance = new Keeper() - contracts.market = await OceanMarket.getInstance(config, helper) - contracts.auth = await OceanAuth.getInstance(config, helper) - contracts.token = await OceanToken.getInstance(config, helper) - - return contracts + Keeper.instance.market = await OceanMarket.getInstance() + Keeper.instance.auth = await OceanAuth.getInstance() + Keeper.instance.token = await OceanToken.getInstance() + } + return Keeper.instance } - public web3Helper: Web3Helper + private static instance: Keeper = null + public token: OceanToken public market: OceanMarket public auth: OceanAuth - private constructor(helper: Web3Helper) { - this.web3Helper = helper + public async getNetworkName(): Promise { + return Web3Provider.getWeb3().eth.net.getId() + .then((networkId) => { + let network: string = "unknown" + + switch (networkId) { + case 1: + network = "Main" + break + case 2: + network = "Morden" + break + case 3: + network = "Ropsten" + break + case 4: + network = "Rinkeby" + break + case 42: + network = "Kovan" + break + default: + network = "development" + } + return network + }) } } diff --git a/src/keeper/Market.ts b/src/keeper/Market.ts index 0cd56fe..db12026 100644 --- a/src/keeper/Market.ts +++ b/src/keeper/Market.ts @@ -1,15 +1,13 @@ import BigNumber from "bignumber.js" import {Receipt} from "web3-utils" -import Asset from "../models/Asset" -import Config from "../models/Config" -import Order from "../models/Order" +import ConfigProvider from "../ConfigProvider" +import Order from "../ocean/Order" import ContractWrapperBase from "./ContractWrapperBase" -import Web3Helper from "./Web3Helper" export default class OceanMarket extends ContractWrapperBase { - public static async getInstance(config: Config, web3Helper: Web3Helper): Promise { - const market: OceanMarket = new OceanMarket(config, "OceanMarket", web3Helper) + public static async getInstance(): Promise { + const market: OceanMarket = new OceanMarket("OceanMarket") await market.init() return market } @@ -44,15 +42,22 @@ export default class OceanMarket extends ContractWrapperBase { return await this.contract.methods.register(assetId, price) .send({ from: publisherAddress, - gas: this.config.defaultGas, + gas: ConfigProvider.getConfig().defaultGas, }) } - public async payAsset(asset: Asset, order: Order, buyerAddress: string): Promise { - return this.contract.methods.sendPayment(order.id, asset.publisherId, asset.price, order.timeout) - .send({ - from: buyerAddress, - gas: this.config.defaultGas, - }) + public async payOrder(order: Order, payerAddreess: string): Promise { + + const args = [ + order.getId(), order.getAsset().publisher.getId(), + order.getAsset().price, order.getTimeout(), + ] + + return this.sendTransaction("sendPayment", payerAddreess, args) + } + + public getAssetPublisher(assetId: string): Promise { + return this.contract.methods.getAssetPublisher(assetId) + .call() } } diff --git a/src/keeper/Token.ts b/src/keeper/Token.ts index d9f6b39..467161e 100644 --- a/src/keeper/Token.ts +++ b/src/keeper/Token.ts @@ -1,13 +1,12 @@ import BigNumber from "bignumber.js" import {Receipt} from "web3-utils" -import Config from "../models/Config" +import ConfigProvider from "../ConfigProvider" import ContractBaseWrapper from "./ContractWrapperBase" -import Web3Helper from "./Web3Helper" export default class OceanToken extends ContractBaseWrapper { - public static async getInstance(config: Config, web3Helper: Web3Helper): Promise { - const token: OceanToken = new OceanToken(config, "OceanToken", web3Helper) + public static async getInstance(): Promise { + const token: OceanToken = new OceanToken("OceanToken") await token.init() return token } @@ -16,7 +15,7 @@ export default class OceanToken extends ContractBaseWrapper { return this.contract.methods.approve(marketAddress, price) .send({ from: buyerAddress, - gas: this.config.defaultGas, + gas: ConfigProvider.getConfig().defaultGas, }) } diff --git a/src/keeper/Web3Helper.ts b/src/keeper/Web3Helper.ts deleted file mode 100644 index 578b3fd..0000000 --- a/src/keeper/Web3Helper.ts +++ /dev/null @@ -1,69 +0,0 @@ -import * as Web3 from "web3" -import Config from "../models/Config" -import Logger from "../utils/Logger" - -Logger.log("using web3", Web3.version) - -export default class Web3Helper { - - private web3: Web3 - - public constructor(config: Config) { - const web3Provider = config.web3Provider || new Web3.providers.HttpProvider(config.nodeUri) - this.web3 = new Web3(Web3.givenProvider || web3Provider) - } - - public getWeb3() { - return this.web3 - } - - public getCurrentProvider() { - return this.web3.currentProvider - } - - public async getAccounts(): Promise { - return new Promise((resolve, reject) => { - this.web3.eth.getAccounts((err: any, accounts: string[]) => { - if (err) { - reject(err) - throw err - } - resolve(accounts) - }) - }) - } - - public async getNetworkName(): Promise { - return this.web3.eth.net.getId() - .then((networkId) => { - let network: string = "unknown" - - switch (networkId) { - case 1: - network = "Main" - break - case 2: - network = "Morden" - break - case 3: - network = "Ropsten" - break - case 4: - network = "Rinkeby" - break - case 42: - network = "Kovan" - break - default: - network = "development" - } - return network - }) - } - - // web3 wrappers - public sign(accountAddress: string, message: string) { - return this.web3.eth.sign(accountAddress, message) - } - -} diff --git a/src/keeper/Web3Provider.ts b/src/keeper/Web3Provider.ts new file mode 100644 index 0000000..6bc5901 --- /dev/null +++ b/src/keeper/Web3Provider.ts @@ -0,0 +1,19 @@ +import * as Web3 from "web3" +import ConfigProvider from "../ConfigProvider" +import Logger from "../utils/Logger" + +Logger.log("using web3", Web3.version) + +export default class Web3Provider { + + public static getWeb3() { + if (Web3Provider.web3 === null) { + const config = ConfigProvider.getConfig() + const web3Provider = config.web3Provider || new Web3.providers.HttpProvider(config.nodeUri) + Web3Provider.web3 = new Web3(Web3.givenProvider || web3Provider) + } + return Web3Provider.web3 + } + + private static web3: Web3 = null +} diff --git a/src/models/Account.ts b/src/models/Account.ts deleted file mode 100644 index 52b3255..0000000 --- a/src/models/Account.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Balance from "./Balance" - -export default class Account { - public name: string - public balance: Balance -} diff --git a/src/models/Asset.ts b/src/models/Asset.ts deleted file mode 100644 index 5fbc986..0000000 --- a/src/models/Asset.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default class Asset { - public assetId: string - public publisherId: string - public price: number -} diff --git a/src/models/Order.ts b/src/models/Order.ts deleted file mode 100644 index 812d1cf..0000000 --- a/src/models/Order.ts +++ /dev/null @@ -1,12 +0,0 @@ -import Asset from "./Asset" - -export default class Order { - public id: string - public asset: Asset - public assetId: string - public timeout: number - public pubkey: string - public key: any - public paid: boolean - public status: number -} diff --git a/src/ocean/Account.ts b/src/ocean/Account.ts index 87a4988..3837a9e 100644 --- a/src/ocean/Account.ts +++ b/src/ocean/Account.ts @@ -1,42 +1,40 @@ import BigNumber from "bignumber.js" -import AccountModel from "../models/Account" +import Keeper from "../keeper/Keeper" +import Web3Provider from "../keeper/Web3Provider" +import Balance from "../models/Balance" import OceanBase from "./OceanBase" export default class Account extends OceanBase { + private balance: Balance - public async getTokenBalance(accountAddress: string): Promise { - return this.keeper.token.balanceOf(accountAddress) + public async getOceanBalance(): Promise { + return (await Keeper.getInstance()).token.balanceOf(this.id) } - public async getEthBalance(account: string): Promise { - const {web3Helper} = this.keeper + public async getEthBalance(): Promise { // Logger.log("getting balance for", account); - return web3Helper.getWeb3().eth.getBalance(account, "latest") - .then((balance: string) => { + return Web3Provider.getWeb3().eth + .getBalance(this.id, "latest") + .then((balance: string): number => { // Logger.log("balance", balance); return new BigNumber(balance).toNumber() }) } - public async list() { - const {web3Helper} = this.keeper + public async getBalance(): Promise { - const ethAccounts = await web3Helper.getAccounts() - return Promise.all(ethAccounts - .map(async (account: string) => { - // await ocean.market.requestTokens(account, 1000) - return { - name: account, - balance: { - eth: await this.getEthBalance(account), - ocn: await this.getTokenBalance(account), - }, - } as AccountModel - })) + if (!this.balance) { + this.balance = { + eth: await this.getEthBalance(), + ocn: await this.getOceanBalance(), + } as Balance + } + + return this.balance } // Transactions with gas cost - public async requestTokens(amount: number, receiver: string): Promise { - return this.keeper.market.requestTokens(amount, receiver) + public async requestTokens(amount: number): Promise { + return (await Keeper.getInstance()).market.requestTokens(amount, this.id) } } diff --git a/src/ocean/Asset.ts b/src/ocean/Asset.ts index 7826f78..182b523 100644 --- a/src/ocean/Asset.ts +++ b/src/ocean/Asset.ts @@ -1,31 +1,163 @@ -import AssetModel from "../models/Asset" +import * as EthCrypto from "eth-crypto" +import EthEcies from "eth-ecies" +import * as EthjsUtil from "ethereumjs-util" +import JWT from "jsonwebtoken" +import Keeper from "../keeper/Keeper" +import Web3Provider from "../keeper/Web3Provider" import Logger from "../utils/Logger" +import Account from "./Account" import OceanBase from "./OceanBase" +import Order from "./Order" + +declare var fetch export default class Asset extends OceanBase { - public async isAssetActive(assetId: string): Promise { - const {market} = this.keeper - return market.isAssetActive(assetId) + public static async load(assetId): Promise { + const {market} = await Keeper.getInstance() + + const asset = new Asset("unknown", "unknown", + await market.getAssetPrice(assetId), + new Account(await market.getAssetPublisher(assetId))) + + asset.setId(assetId) + + return asset } - public async registerAsset(name: string, description: string, - price: number, publisherAddress: string): Promise { - const {market} = this.keeper - - // generate an id - const assetId = await market.generateId(name + description) - Logger.log(`Registering: ${assetId} with price ${price}`) - - // register asset in the market - const result = await market.register(assetId, price, publisherAddress) - Logger.log("Registered:", assetId, "in block", result.blockNumber) - - return { - assetId, - publisherId: publisherAddress, - price, - } as AssetModel + constructor(public name: string, + public description: string, + public price: number, + public publisher: Account) { + super() } + public async isActive(): Promise { + const {market} = await Keeper.getInstance() + return market.isAssetActive(this.getId()) + } + + public async purchase(account: Account, timeout: number): Promise { + const {token, market, auth} = await Keeper.getInstance() + + const key = EthCrypto.createIdentity() + const publicKey = EthjsUtil.privateToPublic(key.privateKey).toString("hex") + const price = await market.getAssetPrice(this.getId()) + const isValid = await market.isAssetActive(this.getId()) + + Logger.log("The asset:", this.getId(), "is it valid?", isValid, "it's price is:", price) + + if (!isValid) { + throw new Error("The Asset is not valid!") + } + try { + const marketAddr = market.getAddress() + // Allow market contract to transfer funds on the consumer"s behalf + await token.approve(marketAddr, price, account.getId()) + Logger.log(`${price} tokens approved on market with id: ${marketAddr}`) + } catch (err) { + Logger.error("token.approve failed", err) + } + let order: Order + try { + // Submit the access request + const initiateAccessRequestReceipt = await auth.initiateAccessRequest(this, + publicKey, timeout, account.getId()) + + const {returnValues} = initiateAccessRequestReceipt.events.AccessConsentRequested + Logger.log(`Keeper AccessConsentRequested event received on asset: ${this.getId()}`, returnValues) + order = new Order(this, returnValues._timeout, returnValues._pubKey, key) + order.setId(returnValues._id) + } catch (err) { + Logger.error("auth.initiateAccessRequest failed", err) + } + + return order + if (false) { + // todo: AccessRequestCommitted event is not emitted in this flow + await auth.listenToEventOnce( + "AccessRequestCommitted", { + filter: { + _id: order.getId(), + }, + }) + .then((accessRequestCommittedResult) => { + Logger.log("Got AccessRequestCommitted Event") + + return order.pay(account) + }) + .then((payAssetReceipt) => { + return auth.listenToEventOnce( + "EncryptedTokenPublished", { + filter: { + _id: order.getId(), + }, + }) + }) + .then((encryptedTokenPublishedResult) => { + Logger.log("Got EncryptedTokenPublished Event") + + const {returnValues} = encryptedTokenPublishedResult + + return this.finalizePurchaseAsset( + returnValues._id, order, key, account, + ) + }) + } + } + + public async finalizePurchaseAsset(accessId: string, order: Order, key: any, account: Account): Promise { + const {auth} = await Keeper.getInstance() + + const encryptedAccessToken = await auth.getEncryptedAccessToken(accessId, this.getId()) + + // grab the access token from acl contract + const tokenNo0x = encryptedAccessToken.slice(2) + const encryptedTokenBuffer = Buffer.from(tokenNo0x, "hex") + + const privateKey = key.privateKey.slice(2) + const accessTokenEncoded = EthEcies.Decrypt(Buffer.from(privateKey, "hex"), encryptedTokenBuffer) + const accessToken = JWT.decode(accessTokenEncoded) // Returns a json object + + // sign it + const hexEncrToken = `0x${encryptedTokenBuffer.toString("hex")}` + + const signature = Web3Provider.getWeb3().eth.sign(account.getId(), hexEncrToken) + const fixedMsgSha = Web3Provider.getWeb3().utils.sha3(encryptedAccessToken) + + // Download the data set from the provider using the url in the access token + // decode the access token, grab the service_endpoint, request_id, + + // payload keys: ['consumerId', 'fixed_msg', 'sigEncJWT', 'jwt'] + const payload = JSON.stringify({ + consumerId: account.getId(), + fixed_msg: fixedMsgSha, + sigEncJWT: signature, + jwt: accessTokenEncoded, + }) + const accessUrl = await fetch(`${accessToken.service_endpoint}/${accessToken.resource_id}`, { + method: "POST", + body: payload, + headers: { + "Content-type": "application/json", + }, + }) + .then((response: any) => { + if (response.ok) { + return response.text() + } + Logger.log("Failed: ", response.status, response.statusText) + }) + .then((consumptionUrl: string) => { + Logger.log("Success accessing consume endpoint: ", consumptionUrl) + return consumptionUrl + }) + .catch((error) => { + Logger.error("Error fetching the data asset consumption url: ", error) + }) + Logger.log("consume url: ", accessUrl) + order.setAccessUrl(accessUrl) + + return order + } } diff --git a/src/ocean/MetaData.ts b/src/ocean/MetaData.ts deleted file mode 100644 index 2794ac3..0000000 --- a/src/ocean/MetaData.ts +++ /dev/null @@ -1,45 +0,0 @@ -import Config from "../models/Config" -import Logger from "../utils/Logger" - -declare var fetch - -export default class MetaData { - - private assetsUrl: string - - constructor(config: Config) { - const providerUri = config.providerUri || null - - this.assetsUrl = providerUri + "/assets" - } - - public getAssetsMetadata() { - return fetch(this.assetsUrl + "/metadata", {method: "GET"}) - .then((res) => res.json()) - .then((data) => JSON.parse(data)) - } - - public publishDataAsset(asset: object) { - return fetch(this.assetsUrl + "/metadata", - { - method: "POST", - body: JSON.stringify(asset), - headers: {"Content-type": "application/json"}, - }) - .then((response: any) => { - Logger.log("Success:", response) - if (response.ok) { - Logger.log("Success:", response) - return true - } - Logger.log("Failed: ", response.status, response.statusText) - return false - // throw new Error(response.statusText ? response.statusText : - // `publish asset failed with status ${response.status}`) - }) - .catch((error: Error) => { - Logger.log(`Publish asset to ocean database could not be completed: ${error.message}`) - return false - }) - } -} diff --git a/src/ocean/Ocean.ts b/src/ocean/Ocean.ts index 72e2e2e..a3bebd7 100644 --- a/src/ocean/Ocean.ts +++ b/src/ocean/Ocean.ts @@ -1,36 +1,86 @@ +import ConfigProvider from "../ConfigProvider" import Keeper from "../keeper/Keeper" -import Web3Helper from "../keeper/Web3Helper" -import Config from "../models/Config" +import Web3Provider from "../keeper/Web3Provider" +import Logger from "../utils/Logger" import Account from "./Account" import Asset from "./Asset" -import MetaData from "./MetaData" import Order from "./Order" -import Tribe from "./Tribe" export default class Ocean { public static async getInstance(config) { - const ocean = new Ocean(config) - ocean.keeper = await Keeper.getInstance(config, ocean.helper) - ocean.tribe = await Tribe.getInstance(ocean.helper) - ocean.order = new Order(ocean.keeper) - ocean.account = new Account(ocean.keeper) - ocean.asset = new Asset(ocean.keeper) - return ocean + + if (!Ocean.instance) { + ConfigProvider.configure(config) + Ocean.instance = new Ocean() + } + + return Ocean.instance } - public account: Account - public order: Order - public tribe: Tribe - public asset: Asset - public helper: Web3Helper - public metadata: MetaData + private static instance = null - private keeper: Keeper + public async getAccounts(): Promise { - private constructor(config: Config) { + // retrieve eth accounts + const ethAccounts = await Web3Provider.getWeb3().eth.getAccounts() - this.helper = new Web3Helper(config) - this.metadata = new MetaData(config) + return ethAccounts + .map((address: string) => { + return new Account(address) + }) + } + + public async register(asset: Asset): Promise { + const {market} = await Keeper.getInstance() + + // generate an id + const assetId = await market.generateId(asset.name + asset.description) + Logger.log(`Registering: ${assetId} with price ${asset.price}`) + asset.setId(assetId) + // register asset in the market + const result = await market.register(asset.getId(), asset.price, asset.publisher.getId()) + Logger.log("Registered:", assetId, "in block", result.blockNumber) + + return asset + } + + public async getOrdersByConsumer(consumer: Account): Promise { + const {auth, market} = await Keeper.getInstance() + + Logger.log("Getting orders") + + const accessConsentRequestedData = await auth.getEventData( + "AccessConsentRequested", { + filter: { + _consumer: consumer.getId(), + }, + fromBlock: 0, + toBlock: "latest", + }) + + const orders = await Promise.all( + accessConsentRequestedData + .map(async (event: any) => { + + const {returnValues} = event + + const order: Order = new Order( + await Asset.load(returnValues._resourceId), + parseInt(returnValues._timeout, 10), + null, null) + + order.setId(returnValues._id) + order.setStatus(await auth.getOrderStatus(returnValues._id)) + order.setPaid(await market.verifyOrderPayment(returnValues._id)) + + return order + }), + ) + + // Logger.log("Got orders:", JSON.stringify(orders, null, 2)) + Logger.log(`Got ${Object.keys(orders).length} orders`) + + return orders } } diff --git a/src/ocean/OceanBase.ts b/src/ocean/OceanBase.ts index 1a654dd..99f23e7 100644 --- a/src/ocean/OceanBase.ts +++ b/src/ocean/OceanBase.ts @@ -1,10 +1,18 @@ -import Keeper from "../keeper/Keeper" +export default abstract class OceanBase { -export default class OceanBase { + protected id = "0x00" - protected keeper: Keeper + constructor(id?) { + if (id) { + this.id = id + } + } - constructor(keeper: Keeper) { - this.keeper = keeper + public getId() { + return this.id + } + + public setId(id) { + this.id = id } } diff --git a/src/ocean/Order.ts b/src/ocean/Order.ts index 6bb190b..8351aa5 100644 --- a/src/ocean/Order.ts +++ b/src/ocean/Order.ts @@ -1,198 +1,71 @@ -import * as EthCrypto from "eth-crypto" -import EthEcies from "eth-ecies" -import * as EthjsUtil from "ethereumjs-util" -import JWT from "jsonwebtoken" -import Asset from "../models/Asset" -import OrderModel from "../models/Order" +import Keeper from "../keeper/Keeper" import Logger from "../utils/Logger" +import Asset from "./Asset" import OceanBase from "./OceanBase" - -declare var fetch +import Account from "./Account" export default class Order extends OceanBase { - private static create(asset: Asset, args, key): OrderModel { - const accessId = args._id - Logger.log(`got new access request id: ${accessId}`) - const order: OrderModel = { - id: accessId, - assetId: asset.assetId, - asset, - timeout: parseInt(args._timeout, 10), - pubkey: args._pubKey, - key, - } as OrderModel - // Logger.log("Created order", order) + private paid: boolean + private status: number + private accessUrl: string + private accessId: string - return order + constructor(private asset: Asset, private timeout: number, + private pubkey: string, private key: any) { + super() } - public async getOrdersByConsumer(consumerAddress: string): Promise { - const {auth, market} = this.keeper - - Logger.log("Getting orders") - - const accessConsentRequestedData = await auth.getEventData( - "AccessConsentRequested", { - filter: { - _consumer: consumerAddress, - }, - fromBlock: 0, - toBlock: "latest", - }) - - const orders = await Promise.all( - accessConsentRequestedData - .filter((event: any) => { - return event.returnValues._consumer === consumerAddress - }) - // todo: this is not orders model maybe? lacking proper typing here - .map(async (event: any) => ({ - id: event.returnValues._id, - asset: null, - assetId: event.returnValues._resourceId, - timeout: parseInt(event.returnValues._timeout, 10), - pubkey: null, - key: null, - status: await auth.getOrderStatus(event.returnValues._id), - paid: await market.verifyOrderPayment(event.returnValues._id), - } as OrderModel - ), - ), - ) - - // Logger.log("Got orders:", JSON.stringify(orders, null, 2)) - Logger.log(`Got ${Object.keys(orders).length} orders`) - - return orders + public setAccessUrl(url: string) { + this.accessUrl = url } - public async purchaseAsset(asset: Asset, timeout: number, buyerAddress: string): Promise { - const {token, market, auth} = this.keeper - - const key = EthCrypto.createIdentity() - const publicKey = EthjsUtil.privateToPublic(key.privateKey).toString("hex") - const price = await market.getAssetPrice(asset.assetId) - const isValid = await market.isAssetActive(asset.assetId) - - Logger.log("The asset:", asset.assetId, "is it valid?", isValid, "it's price is:", price) - - if (!isValid) { - throw new Error("The Asset is not valid!") - } - try { - const marketAddr = market.getAddress() - // Allow market contract to transfer funds on the consumer"s behalf - await token.approve(marketAddr, price, buyerAddress) - Logger.log(`${price} tokens approved on market with id: ${marketAddr}`) - } catch (err) { - Logger.error("token.approve failed", err) - } - let order: OrderModel - try { - // Submit the access request - const initiateAccessRequestReceipt = await auth.initiateAccessRequest(asset, - publicKey, timeout, buyerAddress) - - const args = initiateAccessRequestReceipt.events.AccessConsentRequested.returnValues - Logger.log(`keeper AccessConsentRequested event received on asset: ${asset.assetId}`) - order = Order.create(asset, args, key) - } catch (err) { - Logger.error("auth.initiateAccessRequest failed", err) - } - - return order - if (false) { - // todo: AccessRequestCommitted event is not emitted in this flow - await auth.listenToEventOnce( - "AccessRequestCommitted", { - filter: { - _id: order.id, - }, - }) - .then((accessRequestCommittedResult) => { - Logger.log("Got AccessRequestCommitted Event") - - return this.payAsset(asset, accessRequestCommittedResult.returnValues, order, buyerAddress) - }) - .then((payAssetReceipt) => { - return auth.listenToEventOnce( - "EncryptedTokenPublished", { - filter: { - _id: order.id, - }, - }) - }) - .then((result) => { - Logger.log("Got EncryptedTokenPublished Event") - - return this.finalizePurchaseAsset( - result, order, key, buyerAddress, - ) - }) - } + public getAccessUrl() { + return this.accessUrl } - private async payAsset(asset: Asset, args, order, buyerAddress) { - const {market} = this.keeper + public setStatus(status: number) { + this.status = status + } + public setAccessId(accessId: string) { + Logger.log("accessId", accessId) + this.accessId = accessId + } + + public getStatus() { + return this.status + } + + public setPaid(paid: boolean) { + this.paid = paid + } + + public getPaid() { + return this.paid + } + + public getAsset() { + return this.asset + } + + public getPubkey() { + return this.pubkey + } + + public getTimeout() { + return this.timeout + } + + public getKey() { + return this.key + } + + public async pay(account: Account) { + const {market} = await Keeper.getInstance() // send payment - Logger.log("Sending payment: ", order.id, args._id, asset.publisherId, asset.price, order.timeout) - return market.payAsset(asset, order, buyerAddress) - } - - private async finalizePurchaseAsset(args, order, key, buyerAddress): Promise { - const {auth, web3Helper} = this.keeper - - const encryptedAccessToken = await auth.getEncryptedAccessToken(args._id, buyerAddress) - - // grab the access token from acl contract - const tokenNo0x = encryptedAccessToken.slice(2) - const encryptedTokenBuffer = Buffer.from(tokenNo0x, "hex") - - const privateKey = key.privateKey.slice(2) - const accessTokenEncoded = EthEcies.Decrypt(Buffer.from(privateKey, "hex"), encryptedTokenBuffer) - const accessToken = JWT.decode(accessTokenEncoded) // Returns a json object - - // sign it - const hexEncrToken = `0x${encryptedTokenBuffer.toString("hex")}` - - const signature = web3Helper.sign(buyerAddress, hexEncrToken) - const fixedMsgSha = web3Helper.getWeb3().utils.sha3(encryptedAccessToken) - - // Download the data set from the provider using the url in the access token - // decode the access token, grab the service_endpoint, request_id, - - // payload keys: ['consumerId', 'fixed_msg', 'sigEncJWT', 'jwt'] - const payload = JSON.stringify({ - consumerId: buyerAddress, - fixed_msg: fixedMsgSha, - sigEncJWT: signature, - jwt: accessTokenEncoded, - }) - const accessUrl = await fetch(`${accessToken.service_endpoint}/${accessToken.resource_id}`, { - method: "POST", - body: payload, - headers: { - "Content-type": "application/json", - }, - }) - .then((response: any) => { - if (response.ok) { - return response.text() - } - Logger.log("Failed: ", response.status, response.statusText) - }) - .then((consumptionUrl: string) => { - Logger.log("Success accessing consume endpoint: ", consumptionUrl) - return consumptionUrl - }) - .catch((error) => { - Logger.error("Error fetching the data asset consumption url: ", error) - }) - Logger.log("consume url: ", accessUrl) - order.accessUrl = accessUrl - - return order + Logger.log("Sending payment: ", this.getId(), this.accessId, + this.asset.publisher.getId(), this.asset.price, this.timeout) + return market.payOrder(this, account.getId()) } } diff --git a/src/ocean/Tribe.ts b/src/ocean/Tribe.ts deleted file mode 100644 index 6550ca4..0000000 --- a/src/ocean/Tribe.ts +++ /dev/null @@ -1,27 +0,0 @@ -import Web3Helper from "../keeper/Web3Helper" - -export default class Tribe { - - public static getInstance(web3Helper: Web3Helper) { - - return new Tribe(web3Helper) - } - - private constructor(web3Helper: Web3Helper) { - - } - - // did ddo for tribes/marketplaces - public registerTribe() { - return "" - } - - public tribessList() { - return "" - } - - public resolveTribeDID() { - // verify DDO - return "DDO" - } -} diff --git a/test/config.ts b/test/config.ts new file mode 100644 index 0000000..6c85b26 --- /dev/null +++ b/test/config.ts @@ -0,0 +1,5 @@ +import Config from "../src/models/Config" + +export default { + nodeUri: "http://localhost:8545", +} as Config diff --git a/test/keeper/ContractHandler.test.ts b/test/keeper/ContractHandler.test.ts new file mode 100644 index 0000000..fcc6bae --- /dev/null +++ b/test/keeper/ContractHandler.test.ts @@ -0,0 +1,20 @@ +import * as assert from "assert" +import ConfigProvider from "../../src/ConfigProvider" +import ContractHandler from "../../src/keeper/ContractHandler" +import config from "../config" + +before(async () => { + ConfigProvider.configure(config) + await ContractHandler.deployContracts() +}) + +describe("ContractHandler", () => { + + describe("#get()", () => { + + it("should load and get OceanToken correctly", async () => { + assert(await ContractHandler.get("OceanTokewn") !== null) + }) + + }) +}) diff --git a/test/keeper/Keeper.test.ts b/test/keeper/Keeper.test.ts index 06f8209..0e8cbc0 100644 --- a/test/keeper/Keeper.test.ts +++ b/test/keeper/Keeper.test.ts @@ -1,18 +1,15 @@ import * as assert from "assert" +import ConfigProvider from "../../src/ConfigProvider" import ContractHandler from "../../src/keeper/ContractHandler" import Keeper from "../../src/keeper/Keeper" -import Web3Helper from "../../src/keeper/Web3Helper" -import Config from "../../src/models/Config" +import config from "../config" let keeper: Keeper before(async () => { - const config: Config = { - nodeUri: "http://localhost:8545", - } as Config - const web3Helper = new Web3Helper(config) - await ContractHandler.deployContracts(web3Helper) - keeper = await Keeper.getInstance(config, web3Helper) + ConfigProvider.configure(config) + await ContractHandler.deployContracts() + keeper = await Keeper.getInstance() }) describe("Keeper", () => { @@ -30,9 +27,14 @@ describe("Keeper", () => { it("should have token", () => { assert(keeper.token !== null) }) + }) - it("should have web3Helper", () => { - assert(keeper.web3Helper !== null) + describe("#getNetworkName()", () => { + + it("should get development as default", async () => { + const networkName: string = await keeper.getNetworkName() + assert(networkName === "development") }) + }) }) diff --git a/test/ocean/Account.test.ts b/test/ocean/Account.test.ts index bd38dcf..768d45f 100644 --- a/test/ocean/Account.test.ts +++ b/test/ocean/Account.test.ts @@ -1,44 +1,39 @@ import * as assert from "assert" +import ConfigProvider from "../../src/ConfigProvider" import ContractHandler from "../../src/keeper/ContractHandler" -import Keeper from "../../src/keeper/Keeper" -import Web3Helper from "../../src/keeper/Web3Helper" -import Config from "../../src/models/Config" +import Web3Provider from "../../src/keeper/Web3Provider" import Account from "../../src/ocean/Account" +import Ocean from "../../src/ocean/Ocean" +import config from "../config" -let keeper: Keeper - -const config: Config = { - nodeUri: "http://localhost:8545", -} as Config -const web3Helper = new Web3Helper(config) +let ocean: Ocean +let accounts: Account[] before(async () => { - await ContractHandler.deployContracts(web3Helper) - keeper = await Keeper.getInstance(config, web3Helper) + ConfigProvider.configure(config) + await ContractHandler.deployContracts() + ocean = await Ocean.getInstance(config) + + accounts = await ocean.getAccounts() }) describe("Account", () => { - describe("#getTokenBalance()", () => { + describe("#getOceanBalance()", () => { - it("should get initial balance", async () => { + it("should get initial ocean balance", async () => { - const account = new Account(keeper) - const accounts = await account.list() - const addr = accounts[1].name - const balance = await account.getTokenBalance(addr) + const balance = await accounts[0].getOceanBalance() assert(0 === balance) }) - it("should get balance the correct balance", async () => { + it("should get the correct balance", async () => { - const account = new Account(keeper) const amount: number = 100 - const accounts = await account.list() - const addr = accounts[0].name - await account.requestTokens(amount, addr) - const balance = await account.getTokenBalance(addr) + const account: Account = accounts[0] + await account.requestTokens(amount) + const balance = await account.getOceanBalance() assert(amount === balance) }) @@ -48,27 +43,24 @@ describe("Account", () => { it("should get initial balance", async () => { - const account = new Account(keeper) - const accounts = await account.list() - const addr = accounts[5].name - const balance = await account.getEthBalance(addr) - const web3 = web3Helper.getWeb3() + const account: Account = accounts[1] + const balance = await account.getEthBalance() + const web3 = Web3Provider.getWeb3() + assert(Number(web3.utils.toWei("100", "ether")) === balance) }) }) - describe("#list()", () => { + describe("#getBalance()", () => { - it("should list accounts", async () => { + it("should get initial balance", async () => { - const account = new Account(keeper) - const accounts = await account.list() + const account: Account = accounts[1] + const balance = await account.getBalance() + const web3 = Web3Provider.getWeb3() - assert(10 === accounts.length) - assert(0 === accounts[5].balance.ocn) - assert("string" === typeof accounts[0].name) + assert(Number(web3.utils.toWei("100", "ether")) === balance.eth) + assert(0 === balance.ocn) }) - }) - }) diff --git a/test/ocean/Asset.test.ts b/test/ocean/Asset.test.ts index 8c65047..373efee 100644 --- a/test/ocean/Asset.test.ts +++ b/test/ocean/Asset.test.ts @@ -1,73 +1,63 @@ import * as assert from "assert" +import ConfigProvider from "../../src/ConfigProvider" import ContractHandler from "../../src/keeper/ContractHandler" -import Keeper from "../../src/keeper/Keeper" -import Web3Helper from "../../src/keeper/Web3Helper" -import AssetModel from "../../src/models/Asset" -import Config from "../../src/models/Config" import Account from "../../src/ocean/Account" import Asset from "../../src/ocean/Asset" +import Ocean from "../../src/ocean/Ocean" +import config from "../config" -let keeper: Keeper +const testName = "Test Asset 2" +const testDescription = "This asset is pure owange" +const testPrice = 100 -const config: Config = { - nodeUri: "http://localhost:8545", -} as Config -const web3Helper = new Web3Helper(config) +let ocean: Ocean +let testAsset: Asset +let accounts: Account[] +let testPublisher: Account before(async () => { - await ContractHandler.deployContracts(web3Helper) - keeper = await Keeper.getInstance(config, web3Helper) + ConfigProvider.configure(config) + await ContractHandler.deployContracts() + ocean = await Ocean.getInstance(config) + accounts = await ocean.getAccounts() + testPublisher = accounts[0] + testAsset = new Asset(testName, testDescription, testPrice, testPublisher) + + await ocean.register(testAsset) }) describe("Asset", () => { - describe("#register()", () => { - - it("should register asset", async () => { - - const account = new Account(keeper) - const accounts = await account.list() - const addr = accounts[0].name - - const name = "Test Asset" - const description = "This asset is pure owange" - const price = 100 - - const asset = new Asset(keeper) - const finalAsset: AssetModel = await asset.registerAsset(name, description, price, addr) - - assert(finalAsset.assetId.length === 66) - assert(finalAsset.assetId.startsWith("0x")) - assert(finalAsset.publisherId === addr) - assert(finalAsset.price === price) - }) - }) - - describe("#isAssetActive()", () => { + describe("#isActive()", () => { it("should return true on new asset", async () => { - const account = new Account(keeper) - const accounts = await account.list() - const addr = accounts[0].name - - const name = "Test Asset 2" - const description = "This asset is pure owange" - const price = 100 - - const asset = new Asset(keeper) - const finalAsset = await asset.registerAsset(name, description, price, addr) - - const isAssetActive = await asset.isAssetActive(finalAsset.assetId) + const isAssetActive = await testAsset.isActive() assert(true === isAssetActive) }) it("should return false on unknown asset", async () => { - const asset = new Asset(keeper) - - const isAssetActive = await asset.isAssetActive("0x0000") + const isAssetActive = await new Asset(testName, testDescription, testPrice, testPublisher) + .isActive() assert(false === isAssetActive) }) }) + + describe("#purchase()", () => { + + it("should purchase an asset", async () => { + + // todo + await testAsset.purchase(accounts[5], 10000) + }) + }) + + describe("#purchase()", () => { + + it("should purchase an asset", async () => { + // todo + // await testAsset.finalizePurchaseAsset() + }) + }) }) diff --git a/test/ocean/Ocean.test.ts b/test/ocean/Ocean.test.ts index fca5ba1..c8289dd 100644 --- a/test/ocean/Ocean.test.ts +++ b/test/ocean/Ocean.test.ts @@ -1,43 +1,67 @@ import * as assert from "assert" +import ConfigProvider from "../../src/ConfigProvider" import ContractHandler from "../../src/keeper/ContractHandler" -import Web3Helper from "../../src/keeper/Web3Helper" -import Config from "../../src/models/Config" +import Account from "../../src/ocean/Account" +import Asset from "../../src/ocean/Asset" import Ocean from "../../src/ocean/Ocean" +import Logger from "../../src/utils/Logger" +import config from "../config" let ocean: Ocean +let accounts: Account[] before(async () => { - const config: Config = { - nodeUri: "http://localhost:8545", - } as Config - const web3Helper = new Web3Helper(config) - await ContractHandler.deployContracts(web3Helper) + ConfigProvider.configure(config) + await ContractHandler.deployContracts() ocean = await Ocean.getInstance(config) + accounts = await ocean.getAccounts() }) describe("Ocean", () => { - describe("public interface", () => { + describe("#getAccounts()", () => { - it("should have tribe", async () => { + it("should list accounts", async () => { - assert(ocean.tribe !== null) + const accs: Account[] = await ocean.getAccounts() + + assert(10 === accs.length) + assert(0 === (await accs[5].getBalance()).ocn) + assert("string" === typeof accs[0].getId()) }) - it("should have account", async () => { + }) - assert(ocean.account !== null) - }) + describe("#register()", () => { - it("should have order", async () => { + it("should register an asset", async () => { - assert(ocean.order !== null) - }) + const publisher: Account = accounts[0] - it("should have asset", async () => { + const name = "Test Asset 3" + const description = "This asset is pure owange" + const price = 100 - assert(ocean.asset !== null) + const asset = new Asset(name, description, price, publisher) + + const finalAsset: Asset = await ocean.register(asset) + + assert(finalAsset.getId().length === 66) + assert(finalAsset.getId().startsWith("0x")) + assert(finalAsset.publisher === publisher) + assert(finalAsset.price === price) }) }) + describe("#getOrdersByConsumer()", () => { + + it("should list orders", async () => { + + // todo + const orders = await ocean.getOrdersByConsumer(accounts[1]) + Logger.log(orders) + }) + + }) + }) diff --git a/test/ocean/OceanBase.test.ts b/test/ocean/OceanBase.test.ts new file mode 100644 index 0000000..d18fce9 --- /dev/null +++ b/test/ocean/OceanBase.test.ts @@ -0,0 +1,30 @@ +import * as assert from "assert" +import OceanBase from "./OceanBaseMock" + +describe("OceanBase", () => { + + describe("#getId()", () => { + + it("should get the id", async () => { + + const id = "test" + const oceanBase = new OceanBase(id) + + assert(oceanBase.getId() === id) + }) + + }) + + describe("#setId()", () => { + + it("should get the id", async () => { + + const id = "test" + const oceanBase = new OceanBase() + oceanBase.setId(id) + + assert(oceanBase.getId() === id) + }) + }) + +}) diff --git a/test/ocean/OceanBaseMock.ts b/test/ocean/OceanBaseMock.ts new file mode 100644 index 0000000..9be5815 --- /dev/null +++ b/test/ocean/OceanBaseMock.ts @@ -0,0 +1,5 @@ +import OceanBase from "../../src/ocean/OceanBase" + +export default class OceanBaseMock extends OceanBase { + +} diff --git a/test/ocean/Order.test.ts b/test/ocean/Order.test.ts index 783be44..f8cb88f 100644 --- a/test/ocean/Order.test.ts +++ b/test/ocean/Order.test.ts @@ -1,86 +1,44 @@ import * as assert from "assert" +import ConfigProvider from "../../src/ConfigProvider" import ContractHandler from "../../src/keeper/ContractHandler" -import Keeper from "../../src/keeper/Keeper" -import Web3Helper from "../../src/keeper/Web3Helper" -import AssetModel from "../../src/models/Asset" -import Config from "../../src/models/Config" -import OrderModel from "../../src/models/Order" import Account from "../../src/ocean/Account" import Asset from "../../src/ocean/Asset" +import Ocean from "../../src/ocean/Ocean" import Order from "../../src/ocean/Order" +import config from "../config" -let keeper: Keeper -let testAsset: AssetModel -let accounts -let buyerAddr +const testName = "Test Asset 333" +const testDescription = "This asset is pure owange" +const testPrice = 100 -const config: Config = { - nodeUri: "http://localhost:8545", -} as Config -const web3Helper = new Web3Helper(config) +let ocean: Ocean +let testAsset: Asset +let accounts: Account[] +let testPublisher: Account before(async () => { - await ContractHandler.deployContracts(web3Helper) - keeper = await Keeper.getInstance(config, web3Helper) - - const account = new Account(keeper) - accounts = await account.list() - - const sellerAddr = accounts[0].name - buyerAddr = accounts[2].name - - const name = "Order Test Asset" - const description = "This asset is pure owange" - const price = 100 - - const asset = new Asset(keeper) - testAsset = await asset.registerAsset(name, description, price, sellerAddr) - - // get tokens - await account.requestTokens(1000000000000000, buyerAddr) + ConfigProvider.configure(config) + await ContractHandler.deployContracts() + ocean = await Ocean.getInstance(config) + accounts = await ocean.getAccounts() + testPublisher = accounts[0] + // register an asset to play around with + testAsset = new Asset(testName, testDescription, testPrice, testPublisher) + await ocean.register(testAsset) }) -const timeout = 100000000000 - describe("Order", () => { - describe("#purchaseAsset()", () => { + describe("#pay()", () => { - it("should purchase an asset", async () => { + it("should pay for the order", async () => { - const order = new Order(keeper) - const finalOrder: OrderModel = await order.purchaseAsset(testAsset, timeout, buyerAddr) + const order: Order = await testAsset.purchase(accounts[0], 10000) + assert(order) - assert(finalOrder.assetId === testAsset.assetId) - assert(finalOrder.asset.assetId === testAsset.assetId) - assert(finalOrder.timeout === timeout) + await order.pay(accounts[0]) }) + }) - describe("#getOrdersByConsumer()", () => { - - it("should get orders by consumer if there is one", async () => { - - const order = new Order(keeper) - const finalOrder: OrderModel = await order.purchaseAsset(testAsset, timeout, buyerAddr) - - const orders: OrderModel[] = await order.getOrdersByConsumer(buyerAddr) - const datOrder = (await orders.filter((o) => o.id === finalOrder.id))[0] - - assert(datOrder !== null) - assert(datOrder.assetId === testAsset.assetId) - assert(datOrder.timeout === timeout) - assert(datOrder.paid === true) - assert(datOrder.status === 0) - }) - - it("should return empty array if no orders found", async () => { - - const order = new Order(keeper) - - const orders: OrderModel[] = await order.getOrdersByConsumer(accounts[4].name) - - assert(orders.length === 0) - }) - }) })