diff --git a/README.md b/README.md index 8ee99cc..0c45b99 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -

- Kleros API -

+# Kleros API

NPM Version @@ -15,55 +13,83 @@ Commitizen Friendly

-> This repository contains a Javascript library that makes it easy to build Relayers and other DApps that use the Kleros protocol. +> This repository contains a Javascript library that provides methods to interact with Kleros arbitrator + and Arbitrable contracts. It can be used to develop Relayers or DApps that use Kleros smart contracts. ## Installation +``` +yarn add kleros-api +``` -We assume that you have node and yarn installed. +## Basic Usage -```sh -yarn install -``` +See the full API docs [here](https://kleros.io/kleros-api/). -## Test +The base Kleros object initializes all of the different kleros api's with the contract +addresses you pass. This object is useful if your application interacts with both arbitrators, +arbitrable contracts and uses an off chain store to provide metadata on the different disputes +for the UI. -```sh -yarn run ganache-cli -yarn test ``` +// pay arbitration fee. +import Kleros from 'kleros-api' -## Develop +const KlerosInstance = new Kleros( + ETH_PROVIDER, // ethereum provider object + KLEROS_STORE_URI, // uri of off chain storage e.g. https://kleros.in + ARITRATOR_CONTRACT_ADDRESS, // address of a deployed Kleros arbitrator contract + ARBITRABLE_CONTRACT_ADDRESS // address of a deployed arbitrable transaction contract +) -```sh -yarn start +KlerosInstance.arbitrable.payArbitrationFeeByPartyA() // pay arbitration fee for an arbitrable contract ``` -## Build +You can also use the specific api that best suits your needs. -```sh -yarn run build +``` +// deploy a new contract and pay the arbitration fee. +import ArbitrableTransaction from 'kleros-api/contracts/implementations/arbitrable/ArbitrableTransaction' + +// deploy methods are static +const contractInstance = ArbitrableTransaction.deploy( + "0x67a3f2BB8B4B2060875Bd6543156401B817bEd22", // users address + 0.15, // amount of ETH to escrow + "0x0", // hash of the off chain contract + "0x3af76ef44932695a33ba2af52018cd24a74c904f", // arbitrator address + 3600, // number of seconds until there is a timeout + "0x0474b723bd4986808366E0DcC2E301515Aa742B4", // the other party in the contract + "0x0", // extra data in bytes. This can be used to interact with the arbitrator contract + ETH_PROVIDER, // provider object to be used to interact with the network + ) + +const address = contractInstance.address // get the address of your newly created contract + +const ArbitrableTransactionInstance = new ArbitrableTransaction(address) // instantiate instance of the api + +ArbitrableTransactionInstance.payArbitrationFeeByPartyA() // pay arbitration fee ``` -## Event Listeners +## Development -For notifications and event based updates, the api uses event listeners. In order to register and start listening to events, use these methods: +If you want to contribute to our api or modify it for your usage -##### Quick Start +## Setup -To register all events and start the listener, call: +We assume that you have node and yarn installed. ```sh -KlerosInstance.watchForEvents(arbitratorAddress, account, callback) +yarn install ``` -params: +## Test -* arbitratorAddress: Address of arbitrator contract. Needed to update the store for disputes. -* account: Address used for notification callbacks. If an address is provided, push notifications will only be sent for notifications that involve the address. If it is omitted and a callback is included, all notifications will be pushed. -* callback: Function to be called for push notifications. +```sh +yarn run ganache-cli +yarn test +``` -##### Stop Listener +## Build ```sh -KlerosInstance.eventListener.stopWatchingArbitratorEvents(arbitratorAddress) +yarn run build ``` diff --git a/src/constants/arbitrator.js b/constants/arbitrator.js similarity index 100% rename from src/constants/arbitrator.js rename to constants/arbitrator.js diff --git a/src/constants/contract.js b/constants/contract.js similarity index 100% rename from src/constants/contract.js rename to constants/contract.js diff --git a/src/constants/dispute.js b/constants/dispute.js similarity index 100% rename from src/constants/dispute.js rename to constants/dispute.js diff --git a/src/constants/error.js b/constants/error.js similarity index 100% rename from src/constants/error.js rename to constants/error.js diff --git a/src/constants/eth.js b/constants/eth.js similarity index 100% rename from src/constants/eth.js rename to constants/eth.js diff --git a/src/constants/notification.js b/constants/notification.js similarity index 100% rename from src/constants/notification.js rename to constants/notification.js diff --git a/deploy_docs.sh b/deploy_docs.sh new file mode 100755 index 0000000..8a1ee70 --- /dev/null +++ b/deploy_docs.sh @@ -0,0 +1,18 @@ +# Remove old docs dir +rm -rf docs || exit 0; +# Build docs +yarn run docs; + +# Set up new directory +mkdir ../esdocs; +cp -r docs/* ../esdocs/; + +# github pages. must be run by user with ssh write access to kleros-api +cd ../esdocs; +git init; +git add .; +git commit -m "Deploy to GitHub Pages"; +git push --force --quiet "git@github.com:kleros/kleros-api.git" master:gh-pages; + +# cleanup +rm -rf ../esdocs; diff --git a/package.json b/package.json index c5d8315..c474da0 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "license": "MIT", "private": false, "scripts": { + "docs": "esdoc", "prettify": "kleros-scripts prettify", "lint": "kleros-scripts lint:js --config ./.eslintrc.js", "ganache": "ganache-cli -a 15", diff --git a/src/contracts/AbstractContract.js b/src/contracts/AbstractContract.js index 7f8187e..1fb4f62 100644 --- a/src/contracts/AbstractContract.js +++ b/src/contracts/AbstractContract.js @@ -3,6 +3,11 @@ import _ from 'lodash' import isRequired from '../utils/isRequired' import delegateCalls from '../utils/delegateCalls' +/** + * Abstract Contract holds a contract implementation to make calls to the blockchain but + * also includes methods that interact with the off chain store. NOTE all methods that + * the underlying contract implementation expose can be called directly from an Abstract contract. + */ class AbstractContract { /** * AbstractContract wraps an implementation instance to provide access to higher level diff --git a/src/contracts/ContractImplementation.js b/src/contracts/ContractImplementation.js index 57a27bf..02d303d 100644 --- a/src/contracts/ContractImplementation.js +++ b/src/contracts/ContractImplementation.js @@ -2,9 +2,13 @@ import contract from 'truffle-contract' import _ from 'lodash' import isRequired from '../utils/isRequired' -import * as errorConstants from '../constants/error' +import * as errorConstants from '../../constants/error' import Web3Wrapper from '../utils/Web3Wrapper' +/** + * ContractImplementation is a parent class for on chain contracts. It loads the contract from the + * blockchain and exposes the contract instance for use by the child. + */ class ContractImplementation { constructor( web3Provider = isRequired('web3Provider'), @@ -120,6 +124,10 @@ class ContractImplementation { } } + /** + * Create a new Promise to be used in loading the contract. + * @returns {Promise} - Resolves to contract instance. + */ _newLoadingPromise = () => new Promise((resolve, reject) => { this._contractLoadedResolver = resolve @@ -127,7 +135,10 @@ class ContractImplementation { }) // we have getters so that abstract classes can provide public access to implementations variables - getContractInstance = () => this.contractInstance + /** + * Get the contract address for the currently instantiated contract. + * @returns {string} - The address of the contract. + */ getContractAddress = () => this.contractAddress } diff --git a/src/contracts/abstractions/Arbitrable.js b/src/contracts/abstractions/Arbitrable.js index 113b0ca..2081335 100644 --- a/src/contracts/abstractions/Arbitrable.js +++ b/src/contracts/abstractions/Arbitrable.js @@ -1,7 +1,10 @@ import AbstractContract from '../AbstractContract' /** - * Arbitrable Contract API. + * Arbitrable Abstract Contarct API. This wraps an arbitrable contract. It provides + * interaction with both the off chain store as well as the arbitrable instance. All + * arbitrable methods from the supplied contract implementation can be called from this + * object. */ class ArbitrableContract extends AbstractContract { /** diff --git a/src/contracts/abstractions/Arbitrator.js b/src/contracts/abstractions/Arbitrator.js index aca5dd9..ae38277 100644 --- a/src/contracts/abstractions/Arbitrator.js +++ b/src/contracts/abstractions/Arbitrator.js @@ -1,10 +1,13 @@ import _ from 'lodash' -import * as arbitratorConstants from '../../constants/arbitrator' +import * as arbitratorConstants from '../../../constants/arbitrator' import AbstractContract from '../AbstractContract' /** - * Arbitrator API. + * Arbitrator Abstract Contarct API. This wraps an arbitrator contract. It provides + * interaction with both the off chain store as well as the arbitrator instance. All + * arbitrator methods from the supplied contract implementation can be called from this + * object. */ class Arbitrator extends AbstractContract { /** diff --git a/src/contracts/implementations/PNK/PinakionPOC.js b/src/contracts/implementations/PNK/PinakionPOC.js index 916ff29..eb66599 100644 --- a/src/contracts/implementations/PNK/PinakionPOC.js +++ b/src/contracts/implementations/PNK/PinakionPOC.js @@ -1,17 +1,17 @@ import PinakionPOCArtifact from 'kleros/build/contracts/PinakionPOC' // FIXME: mock import _ from 'lodash' -import * as ethConstants from '../../../constants/eth' -import * as errorConstants from '../../../constants/error' +import * as ethConstants from '../../../../constants/eth' +import * as errorConstants from '../../../../constants/error' import ContractImplementation from '../../ContractImplementation' import deployContractAsync from '../../../utils/deployContractAsync' /** - * Kleros API + * Provides interaction with a PinakionPOC contract deployed on the blockchain. */ class PinakionPOC extends ContractImplementation { /** - * Constructor Kleros. + * Constructor PinakionPOC. * @param {object} web3Provider - web3 instance. * @param {string} contractAddress - of the contract (optionnal). */ @@ -20,7 +20,7 @@ class PinakionPOC extends ContractImplementation { } /** - * Kleros deploy. + * Deploy a new instance of PinakionPOC. * @param {string} account - account of user * @param {object} web3Provider - web3 provider object * @returns {object} - 'truffle-contract' Object | err The contract object or error deploy. @@ -37,7 +37,7 @@ class PinakionPOC extends ContractImplementation { } /** - * change the kleros contract in the PNK contract. + * Change the kleros contract variable in instance of PinakionPOC. * @param {string} klerosAddress - Address of Kleros POC contract. * @param {string} account - Address of user. * @returns {object} - The result transaction object. @@ -60,7 +60,7 @@ class PinakionPOC extends ContractImplementation { } /** - * transfer ownership of the PNK contract to the kleros POC contract. + * Transfer ownership of the PNK contract to the kleros POC contract. * @param {string} klerosAddress - Address of Kleros POC contract. * @param {string} account - Address of user. * @returns {object} - The result transaction object. diff --git a/src/contracts/implementations/RNG/BlockHashRNG.js b/src/contracts/implementations/RNG/BlockHashRNG.js index 94512d7..fe289c9 100644 --- a/src/contracts/implementations/RNG/BlockHashRNG.js +++ b/src/contracts/implementations/RNG/BlockHashRNG.js @@ -1,16 +1,16 @@ import BlockHashRNGArtifact from 'kleros-interaction/build/contracts/BlockHashRNG' import _ from 'lodash' -import * as ethConstants from '../../../constants/eth' +import * as ethConstants from '../../../../constants/eth' import ContractImplementation from '../../ContractImplementation' import deployContractAsync from '../../../utils/deployContractAsync' /** - * Kleros API + * Provides interaction with an instance of BlockHashRNG. */ class BlockHashRNG extends ContractImplementation { /** - * Constructor Kleros. + * Constructor BlockHashRNG. * @param {object} web3Provider - instance * @param {string} contractAddress - of the contract (optionnal) */ @@ -19,7 +19,7 @@ class BlockHashRNG extends ContractImplementation { } /** - * Kleros deploy. + * BlockHashRNG deploy. * @param {string} account - users account * @param {object} web3Provider - web3 provider object * @returns {object} - truffle-contract Object | err The contract object or error deploy diff --git a/src/contracts/implementations/RNG/index.js b/src/contracts/implementations/RNG/index.js index 6e89814..44d9eab 100644 --- a/src/contracts/implementations/RNG/index.js +++ b/src/contracts/implementations/RNG/index.js @@ -1,3 +1,3 @@ -import _BlockHashRNG from './BlockHashRNG' +import BlockHashRNG from './BlockHashRNG' -export const BlockHashRNG = _BlockHashRNG +export { BlockHashRNG } diff --git a/src/contracts/implementations/arbitrable/ArbitrableTransaction.js b/src/contracts/implementations/arbitrable/ArbitrableTransaction.js index 12ab61f..7772689 100644 --- a/src/contracts/implementations/arbitrable/ArbitrableTransaction.js +++ b/src/contracts/implementations/arbitrable/ArbitrableTransaction.js @@ -1,14 +1,14 @@ import arbitrableTransactionArtifact from 'kleros-interaction/build/contracts/ArbitrableTransaction' import _ from 'lodash' -import * as ethConstants from '../../../constants/eth' -import * as contractConstants from '../../../constants/contract' -import * as errorConstants from '../../../constants/error' +import * as ethConstants from '../../../../constants/eth' +import * as contractConstants from '../../../../constants/contract' +import * as errorConstants from '../../../../constants/error' import ContractImplementation from '../../ContractImplementation' import deployContractAsync from '../../../utils/deployContractAsync' /** - * ArbitrableTransaction API + * Provides interaction with an Arbitrable Transaction contract deployed on the blockchain. */ class ArbitrableTransaction extends ContractImplementation { /** @@ -217,7 +217,6 @@ class ArbitrableTransaction extends ContractImplementation { /** * Get ruling options from dispute via event - * FIXME this can be an abstract method as it is in the standard * @param {string} arbitratorAddress address of arbitrator contract * @param {number} disputeId index of dispute * @returns {object[]} an array of objects that specify the name and value of the resolution option diff --git a/src/contracts/implementations/arbitrator/KlerosPOC.js b/src/contracts/implementations/arbitrator/KlerosPOC.js index 594bc32..8975934 100644 --- a/src/contracts/implementations/arbitrator/KlerosPOC.js +++ b/src/contracts/implementations/arbitrator/KlerosPOC.js @@ -1,18 +1,18 @@ import klerosArtifact from 'kleros/build/contracts/KlerosPOC' import _ from 'lodash' -import * as ethConstants from '../../../constants/eth' -import * as errorConstants from '../../../constants/error' -import * as arbitratorConstants from '../../../constants/arbitrator' +import * as ethConstants from '../../../../constants/eth' +import * as errorConstants from '../../../../constants/error' +import * as arbitratorConstants from '../../../../constants/arbitrator' import ContractImplementation from '../../ContractImplementation' import deployContractAsync from '../../../utils/deployContractAsync' /** - * Kleros API + * Provides interaction with a KlerosPOC contract on the blockchain. */ class KlerosPOC extends ContractImplementation { /** - * Constructor Kleros. + * Create new KlerosPOC Implementation. * @param {object} web3Provider - web3 instance. * @param {string} contractAddress - Address of the KlerosPOC contract. */ @@ -21,7 +21,7 @@ class KlerosPOC extends ContractImplementation { } /** - * STATIC: Deploy a kleros instance + * STATIC: Deploy a KlerosPOC contract on the blockchain. * @param {string} rngAddress address of random number generator contract * @param {string} pnkAddress address of pinakion contract * @param {number[]} timesPerPeriod array of 5 ints indicating the time limit for each period of contract @@ -52,7 +52,7 @@ class KlerosPOC extends ContractImplementation { } /** - * Use Arbitrator.buyPNK + * Purchase PNK. * @param {string} amount - The number of pinakion to buy. * @param {string} account - The address of the user. * @returns {object} - The result transaction object. @@ -113,7 +113,6 @@ class KlerosPOC extends ContractImplementation { /** * Activate Pinakion tokens to be eligible to be a juror. - * FIXME use estimateGas * @param {string} amount - number of tokens to activate. * @param {string} account - address of user. * @returns {object} - PNK balance. @@ -141,7 +140,7 @@ class KlerosPOC extends ContractImplementation { } /** - * Fetch the cost of arbitration + * Fetch the cost of arbitration. * @param {bytes} contractExtraData - extra data from arbitrable contract. * @returns {number} - The cost of arbitration. */ diff --git a/src/kleros.js b/src/kleros.js index d652d7e..4da8e8b 100644 --- a/src/kleros.js +++ b/src/kleros.js @@ -5,6 +5,13 @@ import * as contracts from './contracts' import * as resources from './resources' import EventListener from './utils/EventListener' +/** + * The Kleros Api provides access to the full suite of functionality. It will initialize + * contract instances for you when possible and creates an object that you can use to + * call all of the other api modules. If you are only going to be interacting with + * specific apis, or you don't want certain functionality such as the off chain store, + * you might find it easier to initialze a specific instance of the api you want. + */ class Kleros { web3Wrapper = {} @@ -19,7 +26,7 @@ class Kleros { * @param {string} ethereumProvider - The Web3.js Provider instance you would like the * Kleros.js library to use for interacting with the * Ethereum network. - * @param {string} storeUri - The storage provider uri used to + * @param {string} storeUri - The storage provider uri used to * get metadata from the cloud for the UI. e.g. Kleros-Store, * IPFS, Swarm etc. * @param {string} arbitratorAddress - Address of the arbitrator contract we should @@ -73,11 +80,18 @@ class Kleros { ) } - setArbitrableContractAddress = contractAddress => + /** + * Set a new arbitrable contract for Kleros instance of arbitrableContracts + * @param {string} contractAddress - Address of arbitrable contract + */ + setArbitrableContractAddress = contractAddress => { this.arbitrableContracts.setContractInstance(contractAddress) + } /** - * Entry point to set up all event listerners and to start the events watcher + * Bootstraps an EventListener and adds all Kleros handlers for event logs. Use + * this if you want to watch the chain for notifications, or are using the off chain + * store for metadata. * @param {string} account Address of the user * @param {function} callback The function to be called once a notification */ @@ -106,7 +120,14 @@ class Kleros { } /** - * set store provider in all high level wrappers + * Stop watching for events on the Arbitrator initialized in the Kleros Instance. + */ + stopWatchingForEvents = () => { + this.eventListener.stopWatchingForEvents(this.arbitrator) + } + + /** + * Sets the store provider uri for all higher level apis in the Kleros Instance. * @param {string} storeUri - The URI that the store provider will use */ setStoreProvider = storeUri => { diff --git a/src/resources/Disputes.js b/src/resources/Disputes.js index 0002c44..7fa18c8 100644 --- a/src/resources/Disputes.js +++ b/src/resources/Disputes.js @@ -1,12 +1,12 @@ import _ from 'lodash' -import * as arbitratorConstants from '../constants/arbitrator' -import * as disputeConstants from '../constants/dispute' +import * as arbitratorConstants from '../../constants/arbitrator' +import * as disputeConstants from '../../constants/dispute' import isRequired from '../utils/isRequired' /** - * Disputes API. - * Requires Store Provider to be set to call methods. + * Disputes API. Provides cross arbitrator and arbitrable contracts functionality. + * Requires Store Provider to be set. */ class Disputes { constructor( @@ -44,6 +44,11 @@ class Disputes { // * Events * // // **************************** // + /** + * Method to register all dispute handlers to an EventListener. + * @param {object} eventListener - The EventListener instance. See utils/EventListener.js. + * @param {string} account - The address of the user. + */ registerStoreUpdateEventListeners = ( eventListener = isRequired('eventListener'), account = isRequired('account') @@ -66,7 +71,7 @@ class Disputes { } /** - * Store dispute in store upon creation + * Event listener handler that stores dispute in store upon creation * @param {string} event - The event log. */ _storeNewDisputeHandler = async event => { @@ -110,7 +115,7 @@ class Disputes { } /** - * Add or substract the stored Net PNK won/lost for a juror. + * Event listener handler that add or substract the stored Net PNK won/lost for a juror. * @param {string} event - The event log. * @param {string} account - The account. */ @@ -146,7 +151,7 @@ class Disputes { } /** - * Event listener that updates ruled at timestamp + * Event listener handler that updates ruled at timestamp * @param {object} event - The event log. * @param {string} account - The users eth account. */ @@ -198,7 +203,7 @@ class Disputes { } /** - * Event listener that sets the deadline for an appeal + * Event listener handler that sets the deadline for an appeal * @param {object} event - The event log. * @param {string} account - The users eth account. */ @@ -249,11 +254,11 @@ class Disputes { // * Public * // // **************************** // /** - * Get data for a dispute. + * Get data for a dispute. This method provides data from the store as well as both + * arbitrator and arbitrable contracts. Used to get all relevant data on a dispute. * @param {number} disputeId - The dispute's ID. * @param {string} account - The juror's address. * @returns {object} - Data object for the dispute that uses data from the contract and the store. - * TODO: Should we return what we have in the store even if dispute is not in the contract? */ getDataForDispute = async (disputeId, account) => { const arbitratorAddress = this._ArbitratorInstance.getContractAddress() diff --git a/src/resources/Notifications.js b/src/resources/Notifications.js index edf45ae..9f8cc79 100644 --- a/src/resources/Notifications.js +++ b/src/resources/Notifications.js @@ -1,13 +1,14 @@ import _ from 'lodash' -import * as arbitratorConstants from '../constants/arbitrator' -import * as notificationConstants from '../constants/notification' -import * as disputeConstants from '../constants/dispute' -import { MISSING_STORE_PROVIDER } from '../constants/error' +import * as arbitratorConstants from '../../constants/arbitrator' +import * as notificationConstants from '../../constants/notification' +import * as disputeConstants from '../../constants/dispute' +import { MISSING_STORE_PROVIDER } from '../../constants/error' import isRequired from '../utils/isRequired' /** - * Notifications API. + * Notifications API. Use this object to fetch notifications from the store, register + * event log handlers to update store and send push notifications. */ class Notifications { constructor( @@ -46,7 +47,7 @@ class Notifications { // **************************** // /** - * register event handlers for the arbitrator instance. + * Register event handlers for the arbitrator instance. * @param {string} account - Filter notifications for account. * @param {object} eventListener - Event Listener that will fetch logs and call callbacks * @param {function} callback - If we want notifications to be "pushed" provide a callback function to call when a new notification is created. @@ -99,7 +100,7 @@ class Notifications { */ if (currentPeriod === arbitratorConstants.PERIOD.ACTIVATION) { // FIXME use estimateGas - const contractInstance = this._ArbitratorInstance.getContractInstance() + const contractInstance = await this._ArbitratorInstance.loadContract() const lastActivatedSession = (await contractInstance.jurors( account ))[2].toNumber() @@ -435,8 +436,7 @@ class Notifications { } /** - * Handler for TokenShift event - * Sends notification informing + * Handler for TokenShift event. * NOTE: you will get a notification for each vote. So a juror that has 3 votes will receive 3 notifications * @param {object} event - The event log. * @param {string} account - The user account. diff --git a/src/utils/EventListener.js b/src/utils/EventListener.js index 79b53cf..ad98745 100644 --- a/src/utils/EventListener.js +++ b/src/utils/EventListener.js @@ -2,8 +2,13 @@ import _ from 'lodash' import PromiseQueue from '../utils/PromiseQueue' import isRequired from '../utils/isRequired' -import * as errorConstants from '../constants/error' +import * as errorConstants from '../../constants/error' +/** + * EventListener is used to watch events on the blockchain for a set of contracts. + * Handlers for specific events can be added. When an event log is found EventListener + * will fire all handlers registered for the contract. + */ class EventListener { /** * Listen for events in contract and handles callbacks with registered event handlers. @@ -24,7 +29,7 @@ class EventListener { } /** - * Fetch all logs from contractInstance in range. + * Fetch all logs from contractInstance in a block range. * @param {object} contractImplementationInstance - Contract Implementation instance. * @param {number} firstBlock - Lower bound of search range. * @param {number} lastBlock - Upper bound of search range. @@ -52,7 +57,7 @@ class EventListener { ) /** - * Fetch all logs from contractInstance for event in range. + * Fetch logs from contractInstance for a specific event in a block range. * @param {object} contractImplementationInstance - contract Implementation instance. * @param {string} eventName - Name of the event. * @param {number} firstBlock - Lower bound of search range. @@ -82,7 +87,7 @@ class EventListener { ) /** - * Add contract instance to poll for events. + * Add a contract instance to watch for new event logs. * @param {object} contractImplementationInstance - Contract Implementation instance */ addContractImplementation = contractImplementationInstance => { @@ -93,7 +98,7 @@ class EventListener { } /** - * Remove contract instance. Will also remove all handlers. + * Remove contract instance being watched. Will also remove all handlers. * @param {string} contractImplementationInstance - contract implementation instance */ removeContractInstance = ( @@ -118,7 +123,7 @@ class EventListener { } /** - * Add event handler that will be called when event is broadcasted. + * Add event handler that will be called when event log is found. * @param {string} contractImplementationInstance - Contract implementation instance * @param {string} eventName - Name of event. * @param {function} handler - Function to be called when event is consumed. @@ -135,7 +140,7 @@ class EventListener { } /** - * Watch for events on each contract instance. Call registered handlers on logs + * Watch for events on all contract instances. Call registered handlers when logs are found. * @param {number} fromBlock - A block number can be passed to catch up on missed logs * @returns {Promise} - Promise resolves when all watchers have been started */ @@ -169,7 +174,7 @@ class EventListener { ) /** - * Stop listening on contract. If no contractAddress supplied it stops all listeners + * Stop listening on contract. If no contractAddress supplied it stops all listeners. * @param {string} contractImplementationInstance - Address of the contract to stop watching */ stopWatchingForEvents = contractImplementationInstance => { diff --git a/src/utils/PromiseQueue.js b/src/utils/PromiseQueue.js index d86edb2..03d683f 100644 --- a/src/utils/PromiseQueue.js +++ b/src/utils/PromiseQueue.js @@ -1,3 +1,7 @@ +/** + * Chain promises so that they are evaluated in order. + * @returns {object} - The promise queue object. + */ const PromiseQueue = () => { let promise = Promise.resolve() diff --git a/src/utils/StoreProviderWrapper.js b/src/utils/StoreProviderWrapper.js index 0bbf65e..e386e0e 100644 --- a/src/utils/StoreProviderWrapper.js +++ b/src/utils/StoreProviderWrapper.js @@ -1,15 +1,29 @@ import _ from 'lodash' -import * as errorConstants from '../constants/error' +import * as errorConstants from '../../constants/error' import PromiseQueue from './PromiseQueue' +/** + * A wrapper for interacting with Kleros Store. + */ class StoreProviderWrapper { + /** + * Create a new instance of StoreProviderWrapper. + * @param {string} storeProviderUri - The uri of kleros store. + */ constructor(storeProviderUri) { this._storeUri = storeProviderUri this._storeQueue = new PromiseQueue() } + /** + * Helper method for sending an http request to kleros store. + * @param {string} verb - HTTP verb to be used in request. E.g. GET, POST, PUT. + * @param {string} uri - The uri to send the request to. + * @param {string} body - json string of the body. + * @returns {Promise} request promise that resolves to the HTTP response. + */ _makeRequest = (verb, uri, body = null) => { const httpRequest = new XMLHttpRequest() return new Promise((resolve, reject) => { @@ -56,7 +70,7 @@ class StoreProviderWrapper { /** * If we know we are waiting on some other write before we want to read we can add a read request to the end of the queue. * @param {string} uri uri to hit - * @returns {promise} promise of the result function + * @returns {Promise} promise of the result function */ queueReadRequest = uri => this._storeQueue.fetch(() => this._makeRequest('GET', uri)) @@ -65,6 +79,11 @@ class StoreProviderWrapper { // * Read * // // **************************** // + /** + * Fetch stored user profile. + * @param {string} userAddress - Address of user. + * @returns {object} - a response object. + */ getUserProfile = async userAddress => { const httpResponse = await this._makeRequest( 'GET', @@ -74,6 +93,14 @@ class StoreProviderWrapper { return httpResponse.body } + /** + * Get all stored data from a dispute. This includes data from the user profile as well + * as the user agnostic dispute data stored separately. + * @param {string} arbitratorAddress - Address of arbitrator contract. + * @param {number} disputeId - Index of the dispute. + * @param {string} userAddress - Address of user. + * @returns {object} - a response object. + */ getDisputeData = async (arbitratorAddress, disputeId, userAddress) => { const userProfile = await this.getUserProfile(userAddress) if (!userProfile) @@ -92,17 +119,12 @@ class StoreProviderWrapper { return Object.assign({}, httpResponse.body, disputeData[0]) } - getContractByHash = async (userAddress, hash) => { - const userProfile = await this.getUserProfile(userAddress) - if (!userProfile) - throw new Error(errorConstants.PROFILE_NOT_FOUND(userAddress)) - - let contractData = _.filter(userProfile.contracts, o => o.hash === hash) - - if (contractData.length === 0) return null - return contractData[0] - } - + /** + * Fetch stored data on a contract for a user. + * @param {string} userAddress - Address of the user. + * @param {string} addressContract - The address of the contract. + * @returns {object} - Contact data. + */ getContractByAddress = async (userAddress, addressContract) => { const userProfile = await this.getUserProfile(userAddress) if (!userProfile) @@ -116,8 +138,13 @@ class StoreProviderWrapper { return contract[0] } - getDisputesForUser = async address => { - const userProfile = await this.getUserProfile(address) + /** + * Fetch stored disputes for a user. + * @param {string} userAddress - Address of user. + * @returns {object} - a response object. + */ + getDisputesForUser = async userAddress => { + const userProfile = await this.getUserProfile(userAddress) if (!userProfile) return [] const disputes = [] @@ -139,12 +166,23 @@ class StoreProviderWrapper { return disputes } - getLastBlock = async account => { - const userProfile = await this.getUserProfile(account) + /** + * Fetch the last block seen for a user. This is commonly used with EventListerer. + * @param {string} userAddress - Address of user. + * @returns {number} The last block number. + */ + getLastBlock = async userAddress => { + const userProfile = await this.getUserProfile(userAddress) return userProfile.lastBlock || 0 } + /** + * Fetch user agnostic data stored on a dispute + * @param {string} arbitratorAddress - The address of the arbitrator contract. + * @param {number} disputeId - The index of the dispute. + * @returns {object} - a response object. + */ getDispute = async (arbitratorAddress, disputeId) => { const httpResponse = await this._makeRequest( 'GET', @@ -158,36 +196,20 @@ class StoreProviderWrapper { // * Write * // // **************************** // - resetUserProfile = async account => { - const getBodyFn = () => - new Promise(resolve => - resolve( - JSON.stringify({ - account - }) - ) - ) - - return this.queueWriteRequest( - getBodyFn, - 'POST', - `${this._storeUri}/${account}` - ) - } - /** - * Update user profile. NOTE: This should only be used for session and lastBlock. It is dangerous to overwrite arrays - * @param {string} account users account - * @param {object} params object containing kwargs to update - * @returns {promise} resulting profile + * Update user profile. WARNING: This should only be used for session and lastBlock. + * Overwriting arrays of unstructured data can lead to data loss. + * @param {string} userAddress - users userAddress + * @param {object} params - object containing kwargs to update + * @returns {promise} - resulting profile */ - updateUserProfile = (account, params = {}) => { + updateUserProfile = (userAddress, params = {}) => { const getBodyFn = async () => { - const currentProfile = (await this.getUserProfile(account)) || {} + const currentProfile = (await this.getUserProfile(userAddress)) || {} delete currentProfile._id delete currentProfile.created_at - params.address = account + params.address = userAddress return JSON.stringify({ ...currentProfile, ...params }) } @@ -195,35 +217,44 @@ class StoreProviderWrapper { return this.queueWriteRequest( getBodyFn, 'POST', - `${this._storeUri}/${account}` + `${this._storeUri}/${userAddress}` ) } /** - * Set up a new user profile if one does not exist - * @param {string} account user's address - * @returns {object} users existing or created profile + * Set up a new user profile if one does not exist. + * @param {string} userAddress - user's address + * @returns {object} - users existing or created profile */ - setUpUserProfile = async account => { - let userProfile = await this.getUserProfile(account) + setUpUserProfile = async userAddress => { + let userProfile = await this.getUserProfile(userAddress) if (_.isNull(userProfile)) { - this.updateUserProfile(account, {}) - userProfile = await this.queueReadRequest(`${this._storeUri}/${account}`) + this.updateUserProfile(userAddress, {}) + userProfile = await this.queueReadRequest( + `${this._storeUri}/${userAddress}` + ) } return userProfile } - updateContract = (account, address, params) => { + /** + * Update the stored data on a contract for a user. + * @param {string} userAddress - The user's address. + * @param {string} contractAddress - The address of the contract. + * @param {object} params - Params we want to update. + * @returns {Promise} - The resulting contract data. + */ + updateContract = (userAddress, contractAddress, params) => { const getBodyFn = async () => { let currentContractData = await this.getContractByAddress( - account, - address + userAddress, + contractAddress ) if (!currentContractData) currentContractData = {} delete currentContractData._id - params.address = address + params.address = contractAddress return JSON.stringify({ ...currentContractData, ...params }) } @@ -231,11 +262,27 @@ class StoreProviderWrapper { return this.queueWriteRequest( getBodyFn, 'POST', - `${this._storeUri}/${account}/contracts/${address}` + `${this._storeUri}/${userAddress}/contracts/${contractAddress}` ) } - addEvidenceContract = (address, account, name, description, url) => { + /** + * Adds new evidence to the store for a users contract. NOTE this will only update the + * stored evidence for the specified user, not all parties of the dispute. + * @param {string} contractAddress - Address of the contract + * @param {string} userAddress - Address of the user. + * @param {string} name - Name of evidence. + * @param {string} description - Description of evidence. + * @param {string} url - A link to the evidence. + * @returns {Promise} - The resulting evidence data. + */ + addEvidenceContract = ( + contractAddress, + userAddress, + name, + description, + url + ) => { // get timestamp for submission const submittedAt = new Date().getTime() @@ -254,13 +301,26 @@ class StoreProviderWrapper { return this.queueWriteRequest( getBodyFn, 'POST', - `${this._storeUri}/${account}/contracts/${address}/evidence` + `${this._storeUri}/${userAddress}/contracts/${contractAddress}/evidence` ) } - updateDisputeProfile = (account, arbitratorAddress, disputeId, params) => { + /** + * Update stored dispute data for a user. + * @param {string} userAddress - The address of the user. + * @param {string} arbitratorAddress - The address of the arbitrator contract. + * @param {number} disputeId - The index of the dispute. + * @param {object} params - The dispute data we are updating. + * @returns {Promise} The resulting dispute data. + */ + updateDisputeProfile = ( + userAddress, + arbitratorAddress, + disputeId, + params + ) => { const getBodyFn = async () => { - const userProfile = await this.getUserProfile(account) + const userProfile = await this.getUserProfile(userAddress) const disputeIndex = _.filter( userProfile.disputes, @@ -283,10 +343,17 @@ class StoreProviderWrapper { 'POST', `${ this._storeUri - }/${account}/arbitrators/${arbitratorAddress}/disputes/${disputeId}` + }/${userAddress}/arbitrators/${arbitratorAddress}/disputes/${disputeId}` ) } + /** + * Update the user agnostic data on a dispute. + * @param {string} arbitratorAddress - The address of the arbitrator contract. + * @param {number} disputeId - The index of the dispute. + * @param {object} params - The data we are updating. + * @returns {Promise} The resulting dispute data. + */ updateDispute = async (arbitratorAddress, disputeId, params) => { const getBodyFn = async () => { const currentDispute = @@ -307,8 +374,19 @@ class StoreProviderWrapper { ) } + /** + * Create a new notification in the store. + * @param {string} userAddress - The address of the user. + * @param {string} txHash - The transaction hash which produced this event log. Used as an identifier. + * @param {number} logIndex - The index of the log in the transaction. Used as an identifier. + * @param {number} notificationType - The type of the notification. See constants/notification. + * @param {string} message - The message to be stored with the notification. + * @param {object} data - Any extra data stored with the notification. + * @param {boolean} read - If the notification has been read or not. + * @returns {Promise} - The resulting notification. + */ newNotification = async ( - account, + userAddress, txHash, logIndex, notificationType, @@ -332,13 +410,26 @@ class StoreProviderWrapper { return this.queueWriteRequest( getBodyFn, 'POST', - `${this._storeUri}/${account}/notifications/${txHash}` + `${this._storeUri}/${userAddress}/notifications/${txHash}` ) } - markNotificationAsRead = async (account, txHash, logIndex, isRead = true) => { + /** + * Create a new notification in the store. + * @param {string} userAddress - The address of the user. + * @param {string} txHash - The transaction hash which produced this event log. Used as an identifier. + * @param {number} logIndex - The index of the log in the transaction. Used as an identifier. + * @param {boolean} isRead - If the notification has been read or not. + * @returns {Promise} - The resulting notification. + */ + markNotificationAsRead = async ( + userAddress, + txHash, + logIndex, + isRead = true + ) => { const getBodyFn = async () => { - const userProfile = await this.getUserProfile(account) + const userProfile = await this.getUserProfile(userAddress) const notificationIndex = await _.findIndex( userProfile.notifications, @@ -358,7 +449,7 @@ class StoreProviderWrapper { return this.queueWriteRequest( getBodyFn, 'POST', - `${this._storeUri}/${account}` + `${this._storeUri}/${userAddress}` ) } } diff --git a/src/utils/deployContractAsync.js b/src/utils/deployContractAsync.js index 33de985..82f66f3 100644 --- a/src/utils/deployContractAsync.js +++ b/src/utils/deployContractAsync.js @@ -1,12 +1,12 @@ import contract from 'truffle-contract' -import * as ethConstants from '../constants/eth' -import { UNABLE_TO_DEPLOY_CONTRACT } from '../constants/error' +import * as ethConstants from '../../constants/eth' +import { UNABLE_TO_DEPLOY_CONTRACT } from '../../constants/error' import isRequired from './isRequired' /** - * Deploy contract. + * Deploy a contract on the Ethereum network using the contract artifact. * @param {string} account - The account to deploy it under. * @param {number} value - The value to send. * @param {object} artifact - JSON artifact of the contract. diff --git a/src/utils/isRequired.js b/src/utils/isRequired.js index bb3e974..51c083e 100644 --- a/src/utils/isRequired.js +++ b/src/utils/isRequired.js @@ -1,5 +1,10 @@ -import { MISSING_PARAMETERS } from '../constants/error' +import { MISSING_PARAMETERS } from '../../constants/error' +/** + * Used as the default parameter for an arguemnt that is considered required. It will + * throw an error if the argument is not supplied by the user. + * @param {string} name - The name of the missing argument. + */ const isRequired = name => { throw new Error(MISSING_PARAMETERS(name)) } diff --git a/tests/integration/EventListener.test.js b/tests/integration/EventListener.test.js index 590dd23..7433e86 100644 --- a/tests/integration/EventListener.test.js +++ b/tests/integration/EventListener.test.js @@ -2,7 +2,7 @@ import Web3 from 'web3' import KlerosPOC from '../../src/contracts/implementations/arbitrator/KlerosPOC' import EventListener from '../../src/utils/EventListener' -import * as ethConstants from '../../src/constants/eth' +import * as ethConstants from '../../constants/eth' import setUpContracts from '../helpers/setUpContracts' import waitNotifications from '../helpers/waitNotifications' import delaySecond from '../helpers/delaySecond' diff --git a/tests/integration/contracts.test.js b/tests/integration/contracts.test.js index f83b66c..3e9b340 100644 --- a/tests/integration/contracts.test.js +++ b/tests/integration/contracts.test.js @@ -2,8 +2,8 @@ import Web3 from 'web3' import KlerosPOC from '../../src/contracts/implementations/arbitrator/KlerosPOC' import ArbitrableTransaction from '../../src/contracts/implementations/arbitrable/ArbitrableTransaction' -import * as ethConstants from '../../src/constants/eth' -import * as errorConstants from '../../src/constants/error' +import * as ethConstants from '../../constants/eth' +import * as errorConstants from '../../constants/error' import setUpContracts from '../helpers/setUpContracts' import delaySecond from '../helpers/delaySecond' diff --git a/tests/integration/disputeResolution.test.js b/tests/integration/disputeResolution.test.js index fc19127..a836d18 100644 --- a/tests/integration/disputeResolution.test.js +++ b/tests/integration/disputeResolution.test.js @@ -3,8 +3,8 @@ import Web3 from 'web3' import KlerosPOC from '../../src/contracts/implementations/arbitrator/KlerosPOC' import ArbitrableTransaction from '../../src/contracts/implementations/arbitrable/ArbitrableTransaction' import Notifications from '../../src/resources/Notifications' -import * as ethConstants from '../../src/constants/eth' -import * as notificationConstants from '../../src/constants/notification' +import * as ethConstants from '../../constants/eth' +import * as notificationConstants from '../../constants/notification' import setUpContracts from '../helpers/setUpContracts' import delaySecond from '../helpers/delaySecond'