├── .babelrc
├── .coveralls.yml
├── .esdoc.js
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── deploy_docs.sh
├── jest.config.js
├── package.json
├── src
├── constants
│ ├── arbitrator.js
│ ├── contract.js
│ ├── dispute.js
│ ├── error.js
│ ├── eth.js
│ └── notification.js
├── contracts
│ ├── AbstractContract.js
│ ├── ContractImplementation.js
│ ├── abstractions
│ │ ├── Arbitrable.js
│ │ ├── Arbitrator.js
│ │ ├── MultipleArbitrable.js
│ │ └── index.js
│ ├── implementations
│ │ ├── PNK
│ │ │ ├── MiniMePinakion.js
│ │ │ ├── PinakionPOC.js
│ │ │ ├── TokenFactory.js
│ │ │ └── index.js
│ │ ├── RNG
│ │ │ ├── BlockHashRNG.js
│ │ │ └── index.js
│ │ ├── arbitrable
│ │ │ ├── Arbitrable.js
│ │ │ ├── ArbitrablePermissionList.js
│ │ │ ├── ArbitrableTransaction.js
│ │ │ ├── MultipleArbitrableTransaction.js
│ │ │ └── index.js
│ │ ├── arbitrator
│ │ │ ├── Kleros.js
│ │ │ ├── KlerosPOC.js
│ │ │ └── index.js
│ │ └── index.js
│ └── index.js
├── index.js
├── kleros.js
├── resources
│ ├── Auth.js
│ ├── Disputes.js
│ ├── Notifications.js
│ └── index.js
└── utils
│ ├── EventListener.js
│ ├── PromiseQueue.js
│ ├── StoreProviderWrapper.js
│ ├── Web3Wrapper.js
│ ├── delegateCalls.js
│ ├── deployContractAsync.js
│ ├── getContractAddress.js
│ ├── httpRequest.js
│ └── isRequired.js
├── tests
├── helpers
│ ├── asyncMockResponse.js
│ ├── delaySecond.js
│ ├── setUpContracts.js
│ └── waitNotifications.js
├── integration
│ ├── EventListener.test.js
│ ├── contracts.test.js
│ ├── disputeResolution.test.js
│ └── kleros.test.js
└── unit
│ ├── contracts
│ └── abstractions
│ │ └── Arbitrator.test.js
│ ├── resources
│ └── Disputes.test.js
│ └── utils
│ └── PromiseQueue.test.js
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["env", { "modules": false }], "stage-2"],
3 | "plugins": ["transform-runtime"],
4 | "env": {
5 | "test": {
6 | "presets": ["env", "stage-2"]
7 | },
8 | "commonjs": {
9 | "presets": ["env", "stage-2"]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | repo_token: $COVERALLS_REPO_TOKEN
2 |
--------------------------------------------------------------------------------
/.esdoc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | source: './src',
3 | destination: './docs',
4 | excludes: ['constants/'],
5 | plugins: [
6 | { name: 'esdoc-standard-plugin' },
7 | { name: 'esdoc-ecmascript-proposal-plugin', option: { all: true } }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: './node_modules/kleros-scripts/.eslintrc.js',
3 | rules: {
4 | 'unicorn/filename-case': 0
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Dependencies
4 | /node_modules/
5 |
6 | # Testing
7 | /coverage/
8 |
9 | # Documentation
10 | /docs/
11 |
12 | # Production
13 | /umd/
14 | /es/
15 | /lib/
16 |
17 | # Logs
18 | /yarn-debug.log*
19 | /yarn-error.log*
20 |
21 | # Editors
22 | /.vscode/
23 | /.idea/*
24 |
25 | # Misc
26 | /.DS_Store
27 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /yarn.lock
3 |
4 | # Configuration
5 | .*
6 | config.json
7 | webpack.config.js
8 |
9 | # Documentation
10 | /docs/
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - v9.4.0
4 | cache:
5 | directories:
6 | - node_modules
7 | yarn: true
8 | addons:
9 | apt:
10 | sources:
11 | - ubuntu-toolchain-r-test
12 | install: yarn install --pure-lockfile
13 | script:
14 | - yarn run lint
15 | - nohup yarn run ganache &
16 | - yarn test
17 | - if [ -n "$COVERALLS_REPO_TOKEN" ]; then yarn run test:coveralls; fi
18 | - yarn run build
19 | notifications:
20 | slack: kleros:Ub8n81EgKJ3iRrMDyWyQIVJp
21 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | See [kleros.md](https://kleros.gitbooks.io/kleros-md).
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Smart Contract Solutions, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included
14 | in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kleros API
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | > This repository contains a Javascript library that provides methods to interact with Kleros arbitrator
17 | > and Arbitrable contracts. It can be used to develop Relayers or DApps that use Kleros smart contracts.
18 |
19 | ## Installation
20 |
21 | ```
22 | yarn add kleros-api
23 | ```
24 |
25 | ## Basic Usage
26 |
27 | See the full API docs [here](https://kleros.io/kleros-api/).
28 |
29 | The base Kleros object initializes all of the different kleros api's with the contract
30 | addresses you pass. This object is useful if your application interacts with both arbitrators,
31 | arbitrable contracts and uses an off chain store to provide metadata on the different disputes
32 | for the UI.
33 |
34 | ```
35 | // pay arbitration fee.
36 | import Kleros from 'kleros-api'
37 |
38 | const KlerosInstance = new Kleros(
39 | ETH_PROVIDER, // ethereum provider object
40 | KLEROS_STORE_URI, // uri of off chain storage e.g. https://kleros.in
41 | ARITRATOR_CONTRACT_ADDRESS, // address of a deployed Kleros arbitrator contract
42 | ARBITRABLE_CONTRACT_ADDRESS // address of a deployed arbitrable transaction contract
43 | )
44 |
45 | KlerosInstance.arbitrable.payArbitrationFeeByPartyA() // pay arbitration fee for an arbitrable contract
46 | ```
47 |
48 | You can also use the specific api that best suits your needs.
49 |
50 | ```
51 | // deploy a new contract and pay the arbitration fee.
52 | import ArbitrableTransaction from 'kleros-api/contracts/implementations/arbitrable/ArbitrableTransaction'
53 |
54 | // deploy methods are static
55 | const contractInstance = ArbitrableTransaction.deploy(
56 | "0x67a3f2BB8B4B2060875Bd6543156401B817bEd22", // users address
57 | 0.15, // amount of ETH to escrow
58 | "0x0", // hash of the off chain contract
59 | "0x3af76ef44932695a33ba2af52018cd24a74c904f", // arbitrator address
60 | 3600, // number of seconds until there is a timeout
61 | "0x0474b723bd4986808366E0DcC2E301515Aa742B4", // the other party in the contract
62 | "0x0", // extra data in bytes. This can be used to interact with the arbitrator contract
63 | ETH_PROVIDER, // provider object to be used to interact with the network
64 | )
65 |
66 | const address = contractInstance.address // get the address of your newly created contract
67 |
68 | const ArbitrableTransactionInstance = new ArbitrableTransaction(address) // instantiate instance of the api
69 |
70 | ArbitrableTransactionInstance.payArbitrationFeeByPartyA() // pay arbitration fee
71 | ```
72 |
73 | ## Development
74 |
75 | If you want to contribute to our api or modify it for your usage
76 |
77 | ## Setup
78 |
79 | We assume that you have node and yarn installed.
80 |
81 | ```sh
82 | yarn install
83 | ```
84 |
85 | ## Test
86 |
87 | ```sh
88 | yarn ganache
89 | yarn test
90 | ```
91 |
92 | ## Build
93 |
94 | ```sh
95 | yarn run build
96 | ```
97 |
--------------------------------------------------------------------------------
/deploy_docs.sh:
--------------------------------------------------------------------------------
1 | # Remove old docs dir
2 | rm -rf docs || exit 0;
3 | # Build docs
4 | yarn run docs;
5 |
6 | # Set up new directory
7 | mkdir ../esdocs;
8 | cp -r docs/* ../esdocs/;
9 |
10 | # github pages. must be run by user with ssh write access to kleros-api
11 | cd ../esdocs;
12 | git init;
13 | git add .;
14 | git commit -m "Deploy to GitHub Pages";
15 | git push --force --quiet "git@github.com:kleros/kleros-api.git" master:gh-pages;
16 |
17 | # cleanup
18 | rm -rf ../esdocs;
19 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: 'node',
3 | collectCoverage: true,
4 | collectCoverageFrom: ['src/**/*.js', '!**/node_modules/**']
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kleros-api-2",
3 | "version": "0.18.12",
4 | "description": "A Javascript library that makes it easy to build relayers and other DApps that use the Kleros protocol.",
5 | "keywords": [
6 | "Blockchain",
7 | "Ethereum",
8 | "Kleros"
9 | ],
10 | "main": "lib/index.js",
11 | "module": "es/index.js",
12 | "files": [
13 | "es",
14 | "lib",
15 | "umd"
16 | ],
17 | "homepage": "https://kleros.io",
18 | "repository": "github:kleros/kleros-api",
19 | "bugs": "https://github.com/kleros/kleros-api/issues",
20 | "author": "Kleros Team (https://github.com/kleros)",
21 | "contributors": [
22 | "Wagner Nicolas (https://github.com/n1c01a5)",
23 | "Sam Vitello (https://github.com/satello)",
24 | "Enrique Piqueras (https://github.com/epiqueras)"
25 | ],
26 | "license": "MIT",
27 | "private": false,
28 | "scripts": {
29 | "docs": "esdoc",
30 | "prettify": "kleros-scripts prettify",
31 | "lint": "kleros-scripts lint:js --config ./.eslintrc.js",
32 | "ganache": "ganache-cli -a 15 -s 1",
33 | "test": "GAS=3000000 jest --config ./jest.config.js",
34 | "test:coveralls": "coveralls < ./coverage/lcov.info",
35 | "commitmsg": "kleros-scripts commitmsg",
36 | "cz": "kleros-scripts cz",
37 | "start": "babel src --out-dir ./es --watch --source-maps",
38 | "build": "rimraf ./umd ./es ./lib && webpack --env.NODE_ENV=production -p && babel src --out-dir ./es --source-maps && cross-env BABEL_ENV=commonjs babel src --out-dir ./lib --source-maps"
39 | },
40 | "commitlint": {
41 | "extends": [
42 | "@commitlint/config-conventional"
43 | ]
44 | },
45 | "devDependencies": {
46 | "babel-cli": "^6.26.0",
47 | "babel-core": "^6.26.0",
48 | "babel-jest": "^22.4.1",
49 | "babel-loader": "^7.1.3",
50 | "babel-plugin-transform-runtime": "^6.23.0",
51 | "babel-preset-env": "^1.6.1",
52 | "babel-preset-stage-2": "^6.24.1",
53 | "coveralls": "^3.0.0",
54 | "cross-env": "^5.1.4",
55 | "esdoc": "^1.0.4",
56 | "esdoc-ecmascript-proposal-plugin": "^1.0.0",
57 | "esdoc-standard-plugin": "^1.0.0",
58 | "ganache-cli": "^6.1.8",
59 | "husky": "^0.14.3",
60 | "jest": "^22.4.2",
61 | "kleros-scripts": "^0.4.0",
62 | "rimraf": "^2.6.2",
63 | "standard-version": "^4.3.0",
64 | "webpack": "^4.0.1",
65 | "webpack-cli": "^2.0.9"
66 | },
67 | "dependencies": {
68 | "babel-runtime": "^6.26.0",
69 | "eth-sig-util": "^1.4.2",
70 | "ethereumjs-util": "^5.2.0",
71 | "ethjs": "^0.4.0",
72 | "kleros": "^0.0.6",
73 | "kleros-interaction": "^0.1.1",
74 | "lodash": "^4.17.4",
75 | "minimetoken": "^0.2.0",
76 | "truffle-contract": "^2.0.5",
77 | "web3": "^0.20.1",
78 | "web3-eth-personal": "^1.0.0-beta.34"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/constants/arbitrator.js:
--------------------------------------------------------------------------------
1 | export const DEFAULT_ARBITRATION_FEE = 0.15
2 |
3 | export const PERIOD = {
4 | ACTIVATION: 0,
5 | DRAW: 1,
6 | VOTE: 2,
7 | APPEAL: 3,
8 | EXECUTE: 4
9 | }
10 |
--------------------------------------------------------------------------------
/src/constants/contract.js:
--------------------------------------------------------------------------------
1 | export const STATUS = {
2 | NO_DISPUTE: 0,
3 | WAITING_SELLER: 1,
4 | WAITING_BUYER: 2,
5 | DISPUTE_CREATED: 3,
6 | RESOLVED: 4
7 | }
8 |
--------------------------------------------------------------------------------
/src/constants/dispute.js:
--------------------------------------------------------------------------------
1 | export const STATE = {
2 | OPEN: 0,
3 | RESOLVING: 1,
4 | EXECUTABLE: 2,
5 | RESOLVED: 3
6 | }
7 |
8 | export const DISPUTE_CACHE_URI =
9 | 'https://plkzpnwz7e.execute-api.us-east-2.amazonaws.com/session'
10 |
--------------------------------------------------------------------------------
/src/constants/error.js:
--------------------------------------------------------------------------------
1 | // Kleros
2 | export const MISSING_PARAMETERS = name => `Missing required parameter: ${name}`
3 |
4 | // StoreProviderWrapper
5 | export const PROFILE_NOT_FOUND = user => `No profile found for user: ${user}.`
6 | export const NOTIFICATION_NOT_FOUND = txHash =>
7 | `No notification with txHash ${txHash} exists.`
8 | export const REQUEST_FAILED = error =>
9 | `Request returned an error response: ${error}`
10 |
11 | // Contracts
12 | export const UNABLE_TO_DEPLOY_CONTRACT =
13 | 'Unable to deploy contract, are you sure the contract artifact is correct?'
14 | export const CONTRACT_NOT_DEPLOYED =
15 | 'Unable to load contract. Are you sure the contract is deployed and you are on the right network?'
16 | export const UNABLE_TO_LOAD_CONTRACT =
17 | 'Unable to load contract. Are you sure the contract artifact is correct?'
18 | export const MISSING_CONTRACT_PARAMETERS =
19 | 'Unable to load contract. Missing contractAddress or Artifact. Please call setContractInstance'
20 |
21 | // Implementation
22 | export const CONTRACT_INSTANCE_NOT_SET =
23 | 'No contract instance. Use setContractInstance'
24 |
25 | // PinakionPOC
26 | export const UNABLE_TO_SET_KLEROS = 'Unable to set Kleros.'
27 | export const UNABLE_TO_TRANSFER_OWNERSHIP = 'Unable to transfer ownership.'
28 |
29 | // KlerosPOC
30 | export const UNABLE_TO_BUY_PNK =
31 | 'Unable to buy PNK, are you sure you have enough ETH?'
32 | export const UNABLE_TO_ACTIVATE_PNK =
33 | 'Unable to activate PNK, are you sure you have enough?'
34 | export const UNABLE_TO_FETCH_ARBITRATION_COST =
35 | 'Unable to fetch arbitration cost.'
36 | export const UNABLE_TO_FETCH_APPEAL_COST = 'Unable to fetch appeal cost.'
37 | export const UNABLE_TO_FETCH_TIME_PER_PERIOD = 'Unable to fetch time per period'
38 | export const UNABLE_TO_PASS_PERIOD = 'Unable to pass period.'
39 | export const UNABLE_TO_FETCH_DISPUTE = 'Unable to fetch dispute.'
40 | export const UNABLE_TO_FETCH_AMOUNT_OF_JURORS =
41 | 'Unable to fetch amount of jurors'
42 | export const UNABLE_TO_SUBMIT_VOTES = 'Unable to submit votes.'
43 | export const UNABLE_TO_APPEAL = 'Unable to appeal.'
44 | export const UNABLE_TO_REPARTITION_TOKENS = 'Unable to repartition tokens.'
45 | export const UNABLE_TO_EXECUTE_RULING = 'Unable to execute ruling.'
46 | export const ACCOUNT_NOT_A_JUROR_FOR_CONTRACT = (account, contractAddress) =>
47 | `${account} is not a juror for contract ${contractAddress}`
48 | export const PERIOD_OUT_OF_RANGE = periodNumber =>
49 | `Period ${periodNumber} does not have a time associated with it.`
50 | export const DISPUTE_DOES_NOT_EXIST = disputeID =>
51 | `Dispute ${disputeID} does not exist`
52 |
53 | // ArbitrableTransaction
54 | export const UNABLE_TO_CREATE_TRANSACTION =
55 | 'Unable to create a new transaction.'
56 | export const UNABLE_TO_PAY_ARBITRATION_FEE =
57 | 'Unable to pay fee, are you sure you have enough PNK?'
58 | export const UNABLE_TO_RAISE_AN_APPEAL =
59 | 'Unable to raise appeal, are you sure you are in the appeal period?'
60 | export const UNABLE_TO_PAY_SELLER =
61 | 'Unable to pay the seller, are you sure you have enough ETH?'
62 | export const UNABLE_TO_REIMBURSE_BUYER = 'Unable to reimburse the buyer.'
63 | export const UNABLE_TO_CALL_TIMEOUT = 'Unable to call timeout.'
64 | export const CONTRACT_IS_NOT_WAITING_ON_OTHER_PARTY =
65 | 'Unable to call timeout, because the contract is not waiting on the other party.'
66 | export const TIMEOUT_NOT_REACHED =
67 | 'Unable to call timeout, because it has not been reached yet.'
68 |
69 | // AbstractContract
70 | export const NO_ARBITRATOR_IMPLEMENTATION_SPECIFIED =
71 | 'No Arbitrator Contract Implementation specified. Please call setArbitrator.'
72 | export const NO_ARBITRABLE_IMPLEMENTATION_SPECIFIED =
73 | 'No Arbitrable Contract Implementation specified. Please call setArbitrable.'
74 | export const NO_STORE_PROVIDER_SPECIFIED =
75 | 'No Store Provider Specified. Please call setStoreProvider'
76 |
77 | // Disputes
78 | export const NO_STORE_DATA_FOR_DISPUTE = account =>
79 | `Account ${account} does not have store data for dispute`
80 |
81 | // Notifications
82 | export const MISSING_STORE_PROVIDER =
83 | 'This method requires the use of an off chain store. Please call setStoreProviderInstance.'
84 |
85 | // Event Listener
86 | export const MISSING_CONTRACT_INSTANCE = contractAddress =>
87 | `No contract instance stored for ${contractAddress}. Please call addContractInstance.`
88 | export const ERROR_FETCHING_EVENTS = error => `Unable to fetch events: ${error}`
89 |
90 | // Auth
91 | export const UNABLE_TO_SIGN_TOKEN = `There was an error signing the auth token. Please try again`
92 | export const INVALID_AUTH_TOKEN = msg =>
93 | `Authorization Token is invalid: ${msg}`
94 |
--------------------------------------------------------------------------------
/src/constants/eth.js:
--------------------------------------------------------------------------------
1 | export const LOCALHOST_ETH_PROVIDER = 'http://localhost:8545'
2 | export const LOCALHOST_STORE_PROVIDER = 'https://kleros.in'
3 | // export const LOCALHOST_STORE_PROVIDER = 'http://localhost:3001'
4 |
5 | export const NULL_ADDRESS = '0x'
6 |
7 | export const TRANSACTION = {
8 | GAS: 6400000,
9 | VALUE: 0
10 | }
11 |
--------------------------------------------------------------------------------
/src/constants/notification.js:
--------------------------------------------------------------------------------
1 | export const TYPE = {
2 | DISPUTE_CREATED: 0,
3 | APPEAL_POSSIBLE: 1,
4 | RULING_APPEALED: 2,
5 | TOKEN_SHIFT: 3,
6 | ARBITRATION_REWARD: 4,
7 | CAN_ACTIVATE: 5,
8 | CAN_REPARTITION: 6,
9 | CAN_EXECUTE: 7,
10 | CAN_PAY_FEE: 8
11 | }
12 |
--------------------------------------------------------------------------------
/src/contracts/AbstractContract.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | import isRequired from '../utils/isRequired'
4 | import delegateCalls from '../utils/delegateCalls'
5 |
6 | /**
7 | * Abstract Contract holds a contract implementation to make calls to the blockchain but
8 | * also includes methods that interact with the off chain store. NOTE all methods that
9 | * the underlying contract implementation expose can be called directly from an Abstract contract.
10 | */
11 | class AbstractContract {
12 | /**
13 | * AbstractContract wraps an implementation instance to provide access to higher level
14 | * services such as an off chain store, as well as the functionality of the underlying
15 | * implementation.
16 | * @param {object} implementationInstance - Contract Implementation object to extend
17 | * @param {object} storeProviderInstance - StoreProvider wrapper object.
18 | */
19 | constructor(
20 | implementationInstance = isRequired('implementationInstance'),
21 | storeProviderInstance = isRequired('storeProviderInstance')
22 | ) {
23 | this._StoreProvider = storeProviderInstance
24 | this._contractImplementation = implementationInstance
25 | delegateCalls(this, implementationInstance)
26 | }
27 |
28 | /**
29 | * Set store provider instance.
30 | * @param {object} storeProviderInstance - instance of store provider wrapper.
31 | */
32 | setStoreProviderInstance = storeProviderInstance => {
33 | this._StoreProvider = storeProviderInstance
34 | }
35 | }
36 |
37 | export default AbstractContract
38 |
--------------------------------------------------------------------------------
/src/contracts/ContractImplementation.js:
--------------------------------------------------------------------------------
1 | import contract from 'truffle-contract'
2 | import _ from 'lodash'
3 |
4 | import isRequired from '../utils/isRequired'
5 | import * as errorConstants from '../constants/error'
6 | import Web3Wrapper from '../utils/Web3Wrapper'
7 |
8 | /**
9 | * ContractImplementation is a parent class for on chain contracts. It loads the contract from the
10 | * blockchain and exposes the contract instance for use by the child.
11 | */
12 | class ContractImplementation {
13 | constructor(
14 | web3Provider = isRequired('web3Provider'),
15 | artifact = isRequired('artifact'),
16 | contractAddress
17 | ) {
18 | this.contractAddress = contractAddress
19 | this.artifact = artifact
20 | this.contractInstance = null
21 | this._Web3Wrapper = new Web3Wrapper(web3Provider)
22 | // loading params
23 | this._contractLoadedResolver = null
24 | this._contractLoadedRejecter = null
25 | this._loadingContractInstance = null
26 | this.isLoading = false
27 | }
28 |
29 | /**
30 | * Load contract instance if not yet initialized. Returns loading promise
31 | * @returns {Promise} resolves to contractInstance
32 | */
33 | loadContract = async () => {
34 | if (this.isLoading) return this._loadingContractInstance
35 | if (this.contractInstance) return this.contractInstance
36 |
37 | if (!this.contractAddress || !this.artifact)
38 | throw new Error(errorConstants.CONTRACT_INSTANCE_NOT_SET)
39 |
40 | const newLoadingPromise = this._newLoadingPromise()
41 | this._loadingContractInstance = newLoadingPromise
42 | this._load()
43 | return newLoadingPromise
44 | }
45 |
46 | /**
47 | * Get the web3 provider object from the initialized web3 instance
48 | * @returns {object} web3 provider object
49 | */
50 | getWeb3Provider = () => this._Web3Wrapper.getProvider()
51 |
52 | /**
53 | * MetaMask safe get block data by blockNumber
54 | * @param {Int} blockNumber - Block number
55 | * @returns {Promise} block object
56 | */
57 | getBlock = async blockNumber => this._Web3Wrapper.getBlock(blockNumber)
58 |
59 | /**
60 | * Set a new contract instance
61 | * @param {string} contractAddress - The address of the contract
62 | * @param {object} artifact - Contract artifact to use to load contract
63 | * @returns {object} contractInstance object
64 | */
65 | setContractInstance = async (
66 | contractAddress = this.contractAddress,
67 | artifact = this.artifact
68 | ) => {
69 | this.contractAddress = contractAddress
70 | this.artifact = artifact
71 | this.contractInstance = null
72 | return this.loadContract()
73 | }
74 |
75 | /**
76 | * Load an existing contract from the current artifact and address
77 | */
78 | _load = async () => {
79 | this.isLoading = true
80 | try {
81 | this.contractInstance = await this._instantiateContractIfExistsAsync(
82 | this.artifact,
83 | this.contractAddress
84 | )
85 |
86 | this.isLoading = false
87 | this._contractLoadedResolver(this.contractInstance)
88 | } catch (err) {
89 | this.isLoading = false
90 | this._contractLoadedRejecter(err)
91 | }
92 | }
93 |
94 | /**
95 | * Instantiate contract.
96 | * @private
97 | * @param {object} artifact - The contract artifact.
98 | * @param {string} address - The hex encoded contract Ethereum address
99 | * @returns {object} - The contract instance.
100 | */
101 | _instantiateContractIfExistsAsync = async (artifact, address) => {
102 | try {
103 | const c = await contract(artifact)
104 | await c.setProvider(await this._Web3Wrapper.getProvider())
105 | const contractInstance = _.isUndefined(address)
106 | ? await c.deployed()
107 | : await c.at(address)
108 |
109 | // Estimate gas before sending transactions
110 | for (const funcABI of contractInstance.abi) {
111 | // Check for non-constant functions
112 | if (funcABI.type === 'function' && funcABI.constant === false) {
113 | const func = contractInstance[funcABI.name]
114 |
115 | // eslint-disable-next-line no-loop-func
116 | contractInstance[funcABI.name] = async (...args) => {
117 | await func.estimateGas(...args) // Estimate gas (also checks for possible failures)
118 | return func(...args) // Call original function
119 | }
120 |
121 | // Keep reference to the original function for special cases
122 | contractInstance[funcABI.name].original = func
123 |
124 | // Forward other accessors to the original function
125 | Object.setPrototypeOf(contractInstance[funcABI.name], func)
126 | }
127 | }
128 |
129 | return contractInstance
130 | } catch (err) {
131 | console.error(err)
132 |
133 | if (_.includes(err.message, 'not been deployed to detected network'))
134 | throw new Error(errorConstants.CONTRACT_NOT_DEPLOYED)
135 |
136 | throw new Error(errorConstants.UNABLE_TO_LOAD_CONTRACT)
137 | }
138 | }
139 |
140 | /**
141 | * Create a new Promise to be used in loading the contract.
142 | * @returns {Promise} - Resolves to contract instance.
143 | */
144 | _newLoadingPromise = () =>
145 | new Promise((resolve, reject) => {
146 | this._contractLoadedResolver = resolve
147 | this._contractLoadedRejecter = reject
148 | })
149 |
150 | /**
151 | * Get the contract address for the currently instantiated contract.
152 | * @returns {string} - The address of the contract.
153 | */
154 | getContractAddress = () => this.contractAddress
155 |
156 | /**
157 | * Returns the web3 wrapper
158 | * @returns {object} - Web3 Wrapper
159 | */
160 | getWeb3Wrapper = () => this._Web3Wrapper
161 | }
162 |
163 | export default ContractImplementation
164 |
--------------------------------------------------------------------------------
/src/contracts/abstractions/Arbitrable.js:
--------------------------------------------------------------------------------
1 | import Eth from 'ethjs'
2 |
3 | import getContractAddress from '../../utils/getContractAddress'
4 | import AbstractContract from '../AbstractContract'
5 |
6 | /**
7 | * Arbitrable Abstract Contarct API. This wraps an arbitrable contract. It provides
8 | * interaction with both the off chain store as well as the arbitrable instance. All
9 | * arbitrable methods from the supplied contract implementation can be called from this
10 | * object.
11 | */
12 | class ArbitrableContract extends AbstractContract {
13 | /**
14 | * Deploy a contract and add to the Store.
15 | * @param {string} account - Ethereum address.
16 | * @param {int} value - funds to be placed in contract.
17 | * @param {string} arbitratorAddress - The address of the arbitrator contract.
18 | * @param {int} timeout - Time after which a party automatically loose a dispute.
19 | * @param {string} partyB - Ethereum address of the other party in the contract.
20 | * @param {bytes} arbitratorExtraData - Extra data for the arbitrator.
21 | * @param {string} email - Email address of the contract creator (default empty string).
22 | * @param {object} metaEvidence - The metaEvidence object associated with the Arbitarble Contract.
23 | * @param {...any} args - Extra arguments for the contract.
24 | * @returns {object | Error} - The contract object or an error.
25 | */
26 | deploy = async (
27 | account,
28 | value,
29 | arbitratorAddress,
30 | timeout,
31 | partyB,
32 | arbitratorExtraData = '',
33 | email = '',
34 | metaEvidence = {},
35 | ...args
36 | ) => {
37 | const web3Provider = this._contractImplementation.getWeb3Provider()
38 | const eth = new Eth(web3Provider)
39 | const txCount = await eth.getTransactionCount(account)
40 | // determine the contract address WARNING if the nonce changes this will produce a different address
41 | const contractAddress = getContractAddress(account, txCount)
42 | const metaEvidenceUri = this._StoreProvider.getMetaEvidenceUri(
43 | account,
44 | contractAddress
45 | )
46 | const contractInstance = await this._contractImplementation.constructor.deploy(
47 | account,
48 | value,
49 | arbitratorAddress,
50 | timeout,
51 | partyB,
52 | arbitratorExtraData,
53 | metaEvidenceUri,
54 | web3Provider,
55 | ...args
56 | )
57 |
58 | if (contractInstance.address !== contractAddress)
59 | throw new Error('Contract address does not match meta-evidence uri')
60 |
61 | const newContract = await this._StoreProvider.updateContract(
62 | account,
63 | contractInstance.address,
64 | {
65 | partyA: account,
66 | partyB,
67 | email,
68 | metaEvidence
69 | }
70 | )
71 |
72 | return newContract
73 | }
74 |
75 | /**
76 | * Submit evidence. FIXME should we determine the hash for the user?
77 | * @param {string} account - ETH address of user.
78 | * @param {string} name - Name of evidence.
79 | * @param {string} description - Description of evidence.
80 | * @param {string} url - A link to an evidence using its URI.
81 | * @param {string} hash - A hash of the evidence at the URI. No hash if content is dynamic
82 | * @returns {string} - txHash Hash transaction.
83 | */
84 | submitEvidence = async (account, name, description, url, hash) => {
85 | const contractAddress = this._contractImplementation.contractAddress
86 | // get the index of the new evidence
87 | const evidenceIndex = await this._StoreProvider.addEvidenceContract(
88 | contractAddress,
89 | account,
90 | name,
91 | description,
92 | url,
93 | hash
94 | )
95 | // construct the unique URI
96 | const evidenceUri = this._StoreProvider.getEvidenceUri(
97 | account,
98 | contractAddress,
99 | evidenceIndex
100 | )
101 | const txHash = await this._contractImplementation.submitEvidence(
102 | account,
103 | evidenceUri
104 | )
105 |
106 | return txHash
107 | }
108 |
109 | /**
110 | * Get all contracts TODO do we need to get contract data from blockchain?
111 | * @param {string} account - Address of user.
112 | * @returns {object[]} - Contract data from store.
113 | */
114 | getContractsForUser = async account => {
115 | // fetch user profile
116 | const userProfile = await this._StoreProvider.newUserProfile(account)
117 |
118 | return userProfile.contracts
119 | }
120 |
121 | /**
122 | * Fetch all data from the store on the current contract.
123 | * @returns {object} - Store data for contract.
124 | */
125 | getDataFromStore = async () => {
126 | const contractInstance = await this._contractImplementation.loadContract()
127 | const partyA = await contractInstance.partyA()
128 |
129 | return this._StoreProvider.getContractByAddress(
130 | partyA,
131 | this._contractImplementation.contractAddress
132 | )
133 | }
134 | }
135 |
136 | export default ArbitrableContract
137 |
--------------------------------------------------------------------------------
/src/contracts/abstractions/Arbitrator.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | import * as arbitratorConstants from '../../constants/arbitrator'
4 | import AbstractContract from '../AbstractContract'
5 | import httpRequest from '../../utils/httpRequest'
6 | import { DISPUTE_CACHE_URI } from '../../constants/dispute'
7 | /**
8 | * Arbitrator Abstract Contarct API. This wraps an arbitrator contract. It provides
9 | * interaction with both the off chain store as well as the arbitrator instance. All
10 | * arbitrator methods from the supplied contract implementation can be called from this
11 | * object.
12 | */
13 | class Arbitrator extends AbstractContract {
14 | /**
15 | * Get disputes for user with extra data from arbitrated transaction and store
16 | * @param {string} account Address of user
17 | * @param {bool} allowOffChainCache Should open disputes be pulled from off chain cache.
18 | * The cache is maintained by Kleros for performance. To pull open disputes from the blockchain
19 | * set false
20 | * @returns {object[]} dispute data objects for user
21 | */
22 | getDisputesForUser = async (account, allowOffChainCache = true) => {
23 | // contract data
24 | const [period, currentSession] = await Promise.all([
25 | this._contractImplementation.getPeriod(),
26 | this._contractImplementation.getSession()
27 | ])
28 |
29 | // new jurors have not been chosen yet. don't update
30 | if (period !== arbitratorConstants.PERIOD.VOTE) {
31 | return this.getDisputesForUserFromStore(account)
32 | }
33 |
34 | let profile = await this._StoreProvider.newUserProfile(account)
35 | if (currentSession !== profile.session) {
36 | // pull open disputes from off chain cache
37 | let cachedDisputes = null
38 | if (allowOffChainCache) {
39 | const cachedDisputesResponse = await httpRequest(
40 | 'GET',
41 | `${DISPUTE_CACHE_URI}/${currentSession}`
42 | )
43 |
44 | if (
45 | cachedDisputesResponse.body &&
46 | cachedDisputesResponse.body.open_disputes
47 | ) {
48 | cachedDisputes = await Promise.all(
49 | cachedDisputesResponse.body.open_disputes.map(disputeIDString =>
50 | this._contractImplementation.getDispute(Number(disputeIDString))
51 | )
52 | )
53 | }
54 | }
55 |
56 | // get disputes for juror
57 | const myDisputes = await this._contractImplementation.getDisputesForJuror(
58 | account,
59 | cachedDisputes
60 | )
61 |
62 | // update user profile for each dispute
63 | await Promise.all(
64 | myDisputes.map(async dispute => {
65 | if (dispute.appealDraws[dispute.numberOfAppeals].length > 0) {
66 | const disputeCreationLog = await this._contractImplementation.getDisputeCreationEvent(
67 | dispute.disputeID
68 | )
69 |
70 | if (!disputeCreationLog)
71 | throw new Error('Could not fetch dispute creation event log')
72 | // update profile for account
73 | await this._StoreProvider.updateDisputeProfile(
74 | account,
75 | dispute.arbitratorAddress,
76 | dispute.disputeID,
77 | {
78 | blockNumber: disputeCreationLog.blockNumber
79 | }
80 | )
81 | // add draws separately for appeals
82 | await this._StoreProvider.addNewDrawsDisputeProfile(
83 | account,
84 | dispute.arbitratorAddress,
85 | dispute.disputeID,
86 | dispute.appealDraws[dispute.numberOfAppeals],
87 | dispute.numberOfAppeals
88 | )
89 | }
90 | })
91 | )
92 |
93 | // FIXME do we want to store session?
94 | this._StoreProvider.updateUserSession(account, currentSession)
95 | }
96 |
97 | return this.getDisputesForUserFromStore(account)
98 | }
99 |
100 | /**
101 | * Get disputes from the store.
102 | * @param {string} account - The users account.
103 | * @returns {object[]} The dispute objects.
104 | */
105 | getDisputesForUserFromStore = async account => {
106 | const aribtratorAddress = this._contractImplementation.getContractAddress()
107 | return Promise.all(
108 | (await this._StoreProvider.getDisputes(account))
109 | .filter(dispute => dispute.arbitratorAddress === aribtratorAddress)
110 | .map(dispute =>
111 | this._contractImplementation.getDispute(dispute.disputeId)
112 | )
113 | )
114 | }
115 |
116 | /**
117 | * Buy PNK.
118 | * @param {number} amount - Number of pinakion to buy.
119 | * @param {string} account - Address of user.
120 | * @returns {object[]} - Balance of user.
121 | */
122 | buyPNK = async (amount, account) => {
123 | await this._contractImplementation.buyPNK(amount, account)
124 | return this._contractImplementation.getPNKBalance(account)
125 | }
126 | }
127 |
128 | export default Arbitrator
129 |
--------------------------------------------------------------------------------
/src/contracts/abstractions/MultipleArbitrable.js:
--------------------------------------------------------------------------------
1 | import AbstractContract from '../AbstractContract'
2 |
3 | /**
4 | * Arbitrable Abstract Contarct API. This wraps an arbitrable contract. It provides
5 | * interaction with both the off chain store as well as the arbitrable instance. All
6 | * arbitrable methods from the supplied contract implementation can be called from this
7 | * object.
8 | */
9 | class ArbitrableContract extends AbstractContract {
10 | /**
11 | * Submit evidence. FIXME should we determine the hash for the user?
12 | * @param {string} account - ETH address of user.
13 | * @param {string} arbitrableTransactionId - Id of the arbitrable transaction.
14 | * @param {string} name - Name of evidence.
15 | * @param {string} description - Description of evidence.
16 | * @param {string} url - A link to an evidence using its URI.
17 | * @param {string} hash - A hash of the evidence at the URI. No hash if content is dynamic
18 | * @returns {string} - txHash Hash transaction.
19 | */
20 | submitEvidence = async (
21 | account,
22 | arbitrableTransactionId,
23 | name,
24 | description,
25 | url,
26 | hash
27 | ) => {
28 | const contractAddress = this._contractImplementation.contractAddress
29 |
30 | // get the index of the new evidence
31 | const evidenceIndex = await this._StoreProvider.addEvidenceContract(
32 | contractAddress,
33 | arbitrableTransactionId,
34 | account,
35 | name,
36 | description,
37 | url,
38 | hash
39 | )
40 |
41 | // construct the unique URI
42 | const evidenceUri = this._StoreProvider.getEvidenceUri(
43 | account,
44 | contractAddress,
45 | arbitrableTransactionId,
46 | evidenceIndex
47 | )
48 |
49 | const txHash = await this._contractImplementation.submitEvidence(
50 | account,
51 | arbitrableTransactionId,
52 | evidenceUri
53 | )
54 |
55 | return txHash
56 | }
57 | }
58 |
59 | export default ArbitrableContract
60 |
--------------------------------------------------------------------------------
/src/contracts/abstractions/index.js:
--------------------------------------------------------------------------------
1 | import Arbitrator from './Arbitrator'
2 | import Arbitrable from './Arbitrable'
3 |
4 | export { Arbitrator, Arbitrable }
5 |
--------------------------------------------------------------------------------
/src/contracts/implementations/PNK/MiniMePinakion.js:
--------------------------------------------------------------------------------
1 | import PinakionPOCArtifact from 'kleros-interaction/build/contracts/MiniMeTokenERC20'
2 | import _ from 'lodash'
3 |
4 | import * as errorConstants from '../../../constants/error'
5 | import ContractImplementation from '../../ContractImplementation'
6 | import deployContractAsync from '../../../utils/deployContractAsync'
7 | import isRequired from '../../../utils/isRequired'
8 |
9 | /**
10 | * Provides interaction with a PinakionPOC contract deployed on the blockchain.
11 | */
12 | class MiniMePinakion extends ContractImplementation {
13 | /**
14 | * Constructor PinakionPOC.
15 | * @param {object} web3Provider - web3 instance.
16 | * @param {string} contractAddress - of the contract (optionnal).
17 | */
18 | constructor(web3Provider, contractAddress) {
19 | super(web3Provider, PinakionPOCArtifact, contractAddress)
20 | }
21 |
22 | /**
23 | * Deploy a new instance of PinakionPOC.
24 | * @param {string} account - account of user
25 | * @param {object} web3Provider - web3 provider object
26 | * @param {string} tokenFactoryAddress - The address of the MiniMeTokenFactory contract that will create the Clone token contracts
27 | * @param {string} parentTokenAddress - Address of the parent token, set to 0x0 if it is a new token
28 | * @param {number} parentSnapshotBlock - Block of the parent token that will determine the initial distribution of the clone token, set to 0 if it is a new token
29 | * @param {string} tokenName - Name of the token.
30 | * @param {number} decimalUnits - Number of decimal units token is divisible by.
31 | * @param {string} tokenSymbol - Abreviated symbol to represent the token.
32 | * @param {bool} transfersEnabled - If users can transfer tokens.
33 | * @returns {object} - 'truffle-contract' Object | err The contract object or error deploy.
34 | */
35 | static deploy = async (
36 | account = isRequired('account'),
37 | web3Provider = isRequired('web3Provider'),
38 | tokenFactoryAddress = isRequired('tokenFactoryAddress'),
39 | parentTokenAddress = '0x0',
40 | parentSnapshotBlock = 0,
41 | tokenName = 'Pinakion',
42 | decimalUnits = 18,
43 | tokenSymbol = 'PNK',
44 | transfersEnabled = true
45 | ) => {
46 | const contractDeployed = await deployContractAsync(
47 | account,
48 | 0, // value
49 | PinakionPOCArtifact,
50 | web3Provider,
51 | tokenFactoryAddress, // args
52 | parentTokenAddress,
53 | parentSnapshotBlock,
54 | tokenName,
55 | decimalUnits,
56 | tokenSymbol,
57 | transfersEnabled
58 | )
59 |
60 | return contractDeployed
61 | }
62 |
63 | /**
64 | * Transfer ownership of the PNK contract to the kleros POC contract.
65 | * @param {string} newControllerAddress - Address of the new controller.
66 | * @param {string} controllerAccount - Address of the current controller. (They must sign the tx)
67 | * @returns {object} - The result transaction object.
68 | */
69 | changeController = async (newControllerAddress, controllerAccount) => {
70 | await this.loadContract()
71 |
72 | try {
73 | return this.contractInstance.changeController(newControllerAddress, {
74 | from: controllerAccount
75 | })
76 | } catch (err) {
77 | console.error(err)
78 | throw new Error(errorConstants.UNABLE_TO_TRANSFER_OWNERSHIP)
79 | }
80 | }
81 |
82 | /**
83 | * Approve the arbitrator contract to transfer PNK to the contract and call the arbitrators
84 | * receiveApproval()
85 | * @param {string} arbitratorAddress - The address of the arbitrator contract.
86 | * @param {number} amount - The amount of PNK to transfer in wei.
87 | * @param {string} account - The users account.
88 | * @returns {bool} If the transfer succeeded or not
89 | */
90 | approveAndCall = async (arbitratorAddress, amount, account) => {
91 | await this.loadContract()
92 |
93 | return this.contractInstance.approveAndCall(
94 | arbitratorAddress,
95 | amount,
96 | '0x0',
97 | {
98 | from: account
99 | }
100 | )
101 | }
102 |
103 | /**
104 | * Get the token balance for an account
105 | * @param {string} account - The users account.
106 | * @returns {number} the amount of tokens.
107 | */
108 | getTokenBalance = async account => {
109 | await this.loadContract()
110 |
111 | return this.contractInstance.balanceOf(account)
112 | }
113 |
114 | /**
115 | * Fetch the controller of the contract.
116 | * @returns {string} The ETH address of the controller.
117 | */
118 | getController = async () => {
119 | await this.loadContract()
120 |
121 | return this.contractInstance.controller()
122 | }
123 | }
124 |
125 | export default MiniMePinakion
126 |
--------------------------------------------------------------------------------
/src/contracts/implementations/PNK/PinakionPOC.js:
--------------------------------------------------------------------------------
1 | import PinakionPOCArtifact from 'kleros/build/contracts/PinakionPOC' // FIXME: mock
2 | import _ from 'lodash'
3 |
4 | import * as ethConstants from '../../../constants/eth'
5 | import * as errorConstants from '../../../constants/error'
6 | import ContractImplementation from '../../ContractImplementation'
7 | import deployContractAsync from '../../../utils/deployContractAsync'
8 |
9 | /**
10 | * Provides interaction with a PinakionPOC contract deployed on the blockchain.
11 | */
12 | class PinakionPOC extends ContractImplementation {
13 | /**
14 | * Constructor PinakionPOC.
15 | * @param {object} web3Provider - web3 instance.
16 | * @param {string} contractAddress - of the contract (optionnal).
17 | */
18 | constructor(web3Provider, contractAddress) {
19 | super(web3Provider, PinakionPOCArtifact, contractAddress)
20 | }
21 |
22 | /**
23 | * Deploy a new instance of PinakionPOC.
24 | * @param {string} account - account of user
25 | * @param {object} web3Provider - web3 provider object
26 | * @returns {object} - 'truffle-contract' Object | err The contract object or error deploy.
27 | */
28 | static deploy = async (account, web3Provider) => {
29 | const contractDeployed = await deployContractAsync(
30 | account,
31 | ethConstants.TRANSACTION.VALUE,
32 | PinakionPOCArtifact,
33 | web3Provider
34 | )
35 |
36 | return contractDeployed
37 | }
38 |
39 | /**
40 | * Change the kleros contract variable in instance of PinakionPOC.
41 | * @param {string} klerosAddress - Address of Kleros POC contract.
42 | * @param {string} account - Address of user.
43 | * @returns {object} - The result transaction object.
44 | */
45 | setKleros = async (klerosAddress, account) => {
46 | await this.loadContract()
47 |
48 | try {
49 | return this.contractInstance.setKleros(klerosAddress, {
50 | from: account
51 | })
52 | } catch (err) {
53 | console.error(err)
54 | throw new Error(errorConstants.UNABLE_TO_SET_KLEROS)
55 | }
56 | }
57 |
58 | /**
59 | * Transfer ownership of the PNK contract to the kleros POC contract.
60 | * @param {string} klerosAddress - Address of Kleros POC contract.
61 | * @param {string} account - Address of user.
62 | * @returns {object} - The result transaction object.
63 | */
64 | transferOwnership = async (klerosAddress, account) => {
65 | await this.loadContract()
66 |
67 | try {
68 | return this.contractInstance.transferOwnership(klerosAddress, {
69 | from: account
70 | })
71 | } catch (err) {
72 | console.error(err)
73 | throw new Error(errorConstants.UNABLE_TO_TRANSFER_OWNERSHIP)
74 | }
75 | }
76 |
77 | /**
78 | * Get data from PNK contract.
79 | * @returns {object} - Data from PNK contract.
80 | */
81 | getData = async () => {
82 | await this.loadContract()
83 | const [owner, kleros] = await Promise.all([
84 | this.contractInstance.owner(),
85 | this.contractInstance.kleros()
86 | ])
87 |
88 | return {
89 | owner,
90 | kleros
91 | }
92 | }
93 | }
94 |
95 | export default PinakionPOC
96 |
--------------------------------------------------------------------------------
/src/contracts/implementations/PNK/TokenFactory.js:
--------------------------------------------------------------------------------
1 | import {
2 | MiniMeTokenFactoryAbi,
3 | MiniMeTokenFactoryByteCode
4 | } from 'minimetoken/build/MiniMeToken.sol'
5 | import _ from 'lodash'
6 |
7 | import ContractImplementation from '../../ContractImplementation'
8 | import deployContractAsync from '../../../utils/deployContractAsync'
9 |
10 | /**
11 | * Provides interaction with a PinakionPOC contract deployed on the blockchain.
12 | */
13 | class TokenFactory extends ContractImplementation {
14 | /**
15 | * Constructor PinakionPOC.
16 | * @param {object} web3Provider - web3 instance.
17 | * @param {string} contractAddress - of the contract (optionnal).
18 | */
19 | constructor(web3Provider, contractAddress) {
20 | const artifact = {
21 | abi: MiniMeTokenFactoryAbi,
22 | bytecode: MiniMeTokenFactoryByteCode
23 | }
24 | super(web3Provider, artifact, contractAddress)
25 | }
26 |
27 | /**
28 | * Deploy a new instance of PinakionPOC.
29 | * @param {string} account - account of user
30 | * @param {object} web3Provider - web3 provider object
31 | * @returns {object} - 'truffle-contract' Object | err The contract object or error deploy.
32 | */
33 | static deploy = async (account, web3Provider) => {
34 | const artifact = {
35 | abi: MiniMeTokenFactoryAbi,
36 | bytecode: MiniMeTokenFactoryByteCode
37 | }
38 |
39 | const contractDeployed = await deployContractAsync(
40 | account, // account
41 | 0, // value
42 | artifact, // artifact
43 | web3Provider // web3
44 | )
45 |
46 | return contractDeployed
47 | }
48 | }
49 |
50 | export default TokenFactory
51 |
--------------------------------------------------------------------------------
/src/contracts/implementations/PNK/index.js:
--------------------------------------------------------------------------------
1 | import PinakionPOC from './PinakionPOC'
2 | import MiniMePinakion from './MiniMePinakion'
3 | import TokenFactory from './TokenFactory'
4 |
5 | export { PinakionPOC, MiniMePinakion, TokenFactory }
6 |
--------------------------------------------------------------------------------
/src/contracts/implementations/RNG/BlockHashRNG.js:
--------------------------------------------------------------------------------
1 | import BlockHashRNGArtifact from 'kleros-interaction/build/contracts/BlockHashRNGFallback'
2 | import _ from 'lodash'
3 |
4 | import ContractImplementation from '../../ContractImplementation'
5 | import deployContractAsync from '../../../utils/deployContractAsync'
6 |
7 | /**
8 | * Provides interaction with an instance of BlockHashRNG.
9 | */
10 | class BlockHashRNG extends ContractImplementation {
11 | /**
12 | * Constructor BlockHashRNG.
13 | * @param {object} web3Provider - instance
14 | * @param {string} contractAddress - of the contract (optionnal)
15 | */
16 | constructor(web3Provider, contractAddress) {
17 | super(web3Provider, BlockHashRNGArtifact, contractAddress)
18 | }
19 |
20 | /**
21 | * BlockHashRNG deploy.
22 | * @param {string} account - users account
23 | * @param {object} web3Provider - web3 provider object
24 | * @returns {object} - truffle-contract Object | err The contract object or error deploy
25 | */
26 | static deploy = async (account, web3Provider) => {
27 | const contractDeployed = await deployContractAsync(
28 | account,
29 | 0,
30 | BlockHashRNGArtifact,
31 | web3Provider
32 | )
33 |
34 | return contractDeployed
35 | }
36 | }
37 |
38 | export default BlockHashRNG
39 |
--------------------------------------------------------------------------------
/src/contracts/implementations/RNG/index.js:
--------------------------------------------------------------------------------
1 | import BlockHashRNG from './BlockHashRNG'
2 |
3 | export { BlockHashRNG }
4 |
--------------------------------------------------------------------------------
/src/contracts/implementations/arbitrable/Arbitrable.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | import ContractImplementation from '../../ContractImplementation'
4 | import EventListener from '../../../utils/EventListener'
5 | import httpRequest from '../../../utils/httpRequest'
6 |
7 | /**
8 | * Provides interaction with standard Arbitrable contracts
9 | */
10 | class Arbitrable extends ContractImplementation {
11 | /**
12 | * Constructor ArbitrableTransaction.
13 | * @param {object} web3Provider instance.
14 | * @param {object} contractArtifact The abi JSON of the arbitrable contract.
15 | * @param {string} contractAddress of the contract.
16 | */
17 | constructor(web3Provider, contractArtifact, contractAddress) {
18 | super(web3Provider, contractArtifact, contractAddress)
19 |
20 | this.metaEvidenceCache = {}
21 | }
22 |
23 | /**
24 | * Get the meta evidence for the contract. Arbitrable Transaction can only have
25 | * one meta-evidence that is submitted on contract creation. Look up meta-evidence event
26 | * and make an http request to the resource.
27 | * @returns {object} The metaEvidence object
28 | */
29 | getMetaEvidence = async (metaEvidenceID = 0) => {
30 | if (this.metaEvidenceCache[metaEvidenceID])
31 | return this.metaEvidenceCache[metaEvidenceID]
32 |
33 | const metaEvidenceLog = await EventListener.getEventLogs(
34 | this,
35 | 'MetaEvidence',
36 | 0,
37 | 'latest',
38 | { _metaEvidenceID: metaEvidenceID }
39 | )
40 |
41 | if (!metaEvidenceLog[0]) return {} // NOTE better to throw errors for missing meta-evidence?
42 |
43 | const metaEvidenceUri = metaEvidenceLog[0].args._evidence
44 | // FIXME caching issue need a query param to fetch from AWS
45 | const metaEvidenceResponse = await httpRequest('GET', metaEvidenceUri)
46 |
47 | if (metaEvidenceResponse.status >= 400)
48 | throw new Error(`Unable to fetch meta-evidence at ${metaEvidenceUri}`)
49 |
50 | this.metaEvidenceCache[metaEvidenceID] =
51 | metaEvidenceResponse.body || metaEvidenceResponse
52 | return metaEvidenceResponse.body || metaEvidenceResponse
53 | }
54 |
55 | /**
56 | * Get the evidence submitted in a dispute.
57 | * @returns {object[]} An array of evidence objects.
58 | */
59 | getEvidence = async (arbitratorAddress, disputeID = 0) => {
60 | const evidenceLogs = await EventListener.getEventLogs(
61 | this,
62 | 'Evidence',
63 | 0,
64 | 'latest',
65 | { _disputeID: disputeID, _arbitrator: arbitratorAddress }
66 | )
67 |
68 | // TODO verify hash and data are valid if hash exists
69 | return Promise.all(
70 | evidenceLogs.map(async evidenceLog => {
71 | const evidenceURI = evidenceLog.args._evidence
72 | const evidence = await httpRequest('GET', evidenceURI)
73 | const submittedAt = (await this._Web3Wrapper.getBlock(
74 | evidenceLog.blockNumber
75 | )).timestamp
76 | return {
77 | ...evidence.body,
78 | ...{ submittedBy: evidenceLog.args._party, submittedAt }
79 | }
80 | })
81 | )
82 | }
83 |
84 | /**
85 | * Fetch all standard contract data.
86 | */
87 | getContractData = async () => {
88 | await this.loadContract()
89 |
90 | const [metaEvidence] = await Promise.all([this.getMetaEvidence()])
91 |
92 | return {
93 | metaEvidence
94 | }
95 | }
96 | }
97 |
98 | export default Arbitrable
99 |
--------------------------------------------------------------------------------
/src/contracts/implementations/arbitrable/ArbitrablePermissionList.js:
--------------------------------------------------------------------------------
1 | import ArbitrablePermissionListArtifact from 'kleros-interaction/build/contracts/ArbitrablePermissionList'
2 |
3 | import Arbitrable from './Arbitrable'
4 |
5 | /**
6 | * Provides interaction with an Arbitrable Transaction contract deployed on the blockchain.
7 | */
8 | class ArbitrablePermissionList extends Arbitrable {
9 | /**
10 | * Constructor ArbitrableTransaction.
11 | * @param {object} web3Provider instance
12 | * @param {string} contractAddress of the contract
13 | */
14 | constructor(web3Provider, contractAddress) {
15 | super(web3Provider, ArbitrablePermissionListArtifact, contractAddress)
16 | }
17 |
18 | /**
19 | * Fetch an item hash by disputeID
20 | * @param {number} disputeID The index of the dispute.
21 | * @returns {string} The item hash.
22 | */
23 | getItemByDisputeId = async disputeID => {
24 | await this.loadContract()
25 |
26 | return this.contractInstance.disputeIDToItem(disputeID)
27 | }
28 | }
29 |
30 | export default ArbitrablePermissionList
31 |
--------------------------------------------------------------------------------
/src/contracts/implementations/arbitrable/ArbitrableTransaction.js:
--------------------------------------------------------------------------------
1 | import arbitrableTransactionArtifact from 'kleros-interaction/build/contracts/ArbitrableTransaction'
2 | import _ from 'lodash'
3 |
4 | import * as ethConstants from '../../../constants/eth'
5 | import * as contractConstants from '../../../constants/contract'
6 | import * as errorConstants from '../../../constants/error'
7 | import deployContractAsync from '../../../utils/deployContractAsync'
8 |
9 | import Arbitrable from './Arbitrable'
10 |
11 | /**
12 | * Provides interaction with an Arbitrable Transaction contract deployed on the blockchain.
13 | */
14 | class ArbitrableTransaction extends Arbitrable {
15 | /**
16 | * Constructor ArbitrableTransaction.
17 | * @param {object} web3Provider instance
18 | * @param {string} contractAddress of the contract
19 | */
20 | constructor(web3Provider, contractAddress) {
21 | super(web3Provider, arbitrableTransactionArtifact, contractAddress)
22 | }
23 |
24 | /**
25 | * Deploy ArbitrableTransaction.
26 | * @param {object} account Ethereum account
27 | * @param {number} value funds to be placed in contract
28 | * @param {string} arbitratorAddress The address of the arbitrator contract
29 | * @param {number} timeout Time after which a party automatically loose a dispute.
30 | * @param {string} partyB The recipient of the transaction.
31 | * @param {bytes} arbitratorExtraData Extra data for the arbitrator. (default empty string)
32 | * @param {string} metaEvidenceUri The uri for the metaEvidence
33 | * @param {object} web3Provider web3 provider object
34 | * @returns {object} truffle-contract Object | err The deployed contract or an error
35 | */
36 | static deploy = async (
37 | account,
38 | value = ethConstants.TRANSACTION.VALUE,
39 | arbitratorAddress,
40 | timeout,
41 | partyB,
42 | arbitratorExtraData = '',
43 | metaEvidenceUri,
44 | web3Provider
45 | ) => {
46 | const contractDeployed = await deployContractAsync(
47 | account,
48 | value,
49 | arbitrableTransactionArtifact,
50 | web3Provider,
51 | arbitratorAddress,
52 | timeout,
53 | partyB,
54 | arbitratorExtraData,
55 | metaEvidenceUri
56 | )
57 |
58 | return contractDeployed
59 | }
60 |
61 | /**
62 | * Pay the party B. To be called when the good is delivered or the service rendered.
63 | * @param {string} account - Ethereum address.
64 | * @returns {object} - The result transaction object.
65 | */
66 | pay = async account => {
67 | await this.loadContract()
68 |
69 | try {
70 | return this.contractInstance.pay({
71 | from: account,
72 | value: 0
73 | })
74 | } catch (err) {
75 | console.error(err)
76 | throw new Error(errorConstants.UNABLE_TO_PAY_SELLER)
77 | }
78 | }
79 |
80 | /**
81 | * Pay the arbitration fee to raise a dispute. To be called by the party A.
82 | * @param {string} account - Ethereum address.
83 | * @param {number} arbitrationCost - Arbitration fee in wei.
84 | * @returns {object} - The result transaction object.
85 | */
86 | payArbitrationFeeByPartyA = async (account, arbitrationCost) => {
87 | await this.loadContract()
88 |
89 | try {
90 | return this.contractInstance.payArbitrationFeeByPartyA({
91 | from: account,
92 | value: arbitrationCost
93 | })
94 | } catch (err) {
95 | console.error(err)
96 | throw new Error(errorConstants.UNABLE_TO_PAY_ARBITRATION_FEE)
97 | }
98 | }
99 |
100 | /**
101 | * Pay the arbitration fee to raise a dispute. To be called by the party B.
102 | * @param {string} account Ethereum address.
103 | * @param {number} arbitrationCost Amount to pay the arbitrator.
104 | * @returns {object} - The result transaction object.
105 | */
106 | payArbitrationFeeByPartyB = async (account, arbitrationCost) => {
107 | await this.loadContract()
108 |
109 | try {
110 | return this.contractInstance.payArbitrationFeeByPartyB({
111 | from: account,
112 | value: arbitrationCost,
113 | gas: process.env.GAS || undefined
114 | })
115 | } catch (err) {
116 | console.error(err)
117 | throw new Error(errorConstants.UNABLE_TO_PAY_ARBITRATION_FEE)
118 | }
119 | }
120 |
121 | /**
122 | * Submit evidence.
123 | * @param {string} account ETH address of user.
124 | * @param {string} url A link to an evidence using its URI.
125 | * @returns {string} txHash Hash transaction.
126 | */
127 | submitEvidence = async (account, url) => {
128 | await this.loadContract()
129 |
130 | const txHashObj = await this.contractInstance.submitEvidence(url, {
131 | from: account,
132 | value: 0
133 | })
134 |
135 | return txHashObj.tx
136 | }
137 |
138 | /**
139 | * Call by partyA if partyB is timeout
140 | * @param {string} account ETH address of user
141 | * @returns {object} The result transaction object.
142 | */
143 | callTimeOutPartyA = async account => {
144 | await this.loadContract()
145 |
146 | const status = (await this.contractInstance.status()).toNumber()
147 | const timeout = (await this.contractInstance.timeout()).toNumber()
148 | const lastInteraction = (await this.contractInstance.lastInteraction()).toNumber()
149 |
150 | if (status !== contractConstants.STATUS.WAITING_PARTY_B) {
151 | throw new Error(errorConstants.CONTRACT_IS_NOT_WAITING_ON_OTHER_PARTY)
152 | } else if (Date.now() >= lastInteraction + timeout) {
153 | throw new Error(errorConstants.TIMEOUT_NOT_REACHED)
154 | }
155 |
156 | try {
157 | return this.contractInstance.timeOutByPartyA({
158 | from: account,
159 | value: 0
160 | })
161 | } catch (err) {
162 | console.error(err)
163 | throw new Error(errorConstants.UNABLE_TO_CALL_TIMEOUT)
164 | }
165 | }
166 |
167 | /**
168 | * Call by partyB if partyA is timeout.
169 | * @param {string} account - ETH address of user.
170 | * @returns {object} The result transaction object.
171 | */
172 | callTimeOutPartyB = async account => {
173 | await this.loadContract()
174 |
175 | const status = await this.contractInstance.status()
176 | const timeout = await this.contractInstance.timeout()
177 | const lastInteraction = await this.contractInstance.lastInteraction()
178 |
179 | if (status !== contractConstants.STATUS.WAITING_PARTY_A) {
180 | throw new Error(errorConstants.CONTRACT_IS_NOT_WAITING_ON_OTHER_PARTY)
181 | } else if (Date.now() >= lastInteraction + timeout) {
182 | throw new Error(errorConstants.TIMEOUT_NOT_REACHED)
183 | }
184 |
185 | try {
186 | return this.contractInstance.timeOutByPartyB({
187 | from: account,
188 | value: 0
189 | })
190 | } catch (err) {
191 | console.error(err)
192 | throw new Error(errorConstants.UNABLE_TO_CALL_TIMEOUT)
193 | }
194 | }
195 |
196 | /**
197 | * Appeal an appealable ruling.
198 | * @param {string} account Ethereum account (default account[0]).
199 | * @param {number} appealCost Fee for the appeal
200 | * @param {bytes} extraData for the arbitrator appeal procedure.
201 | * @returns {object} - The result transaction object.
202 | */
203 | appeal = async (account, appealCost, extraData = 0x0) => {
204 | await this.loadContract()
205 |
206 | try {
207 | return this.contractInstance.appeal(extraData, {
208 | from: account,
209 | value: appealCost
210 | })
211 | } catch (err) {
212 | console.error(err)
213 | throw new Error(errorConstants.UNABLE_TO_RAISE_AN_APPEAL)
214 | }
215 | }
216 |
217 | /**
218 | * Fetch the parties involved in the arbitrable transaction contract.
219 | * @returns {object} returns a mapping of partyA and partyB to ETH addresses.
220 | */
221 | getParties = async () => {
222 | await this.loadContract()
223 |
224 | const [partyA, partyB] = await Promise.all([
225 | this.contractInstance.partyA(),
226 | this.contractInstance.partyB()
227 | ])
228 |
229 | return {
230 | partyA,
231 | partyB
232 | }
233 | }
234 |
235 | /**
236 | * Data of the contract
237 | * @returns {object} Object Data of the contract.
238 | */
239 | getData = async () => {
240 | await this.loadContract()
241 |
242 | const [
243 | arbitrator,
244 | extraData,
245 | timeout,
246 | partyA,
247 | partyB,
248 | status,
249 | arbitratorExtraData,
250 | disputeID,
251 | partyAFee,
252 | partyBFee,
253 | lastInteraction,
254 | amount,
255 | evidence,
256 | metaEvidence
257 | ] = await Promise.all([
258 | this.contractInstance.arbitrator(),
259 | this.contractInstance.arbitratorExtraData(),
260 | // this.contractInstance.hashContract(),
261 | this.contractInstance.timeout(),
262 | this.contractInstance.partyA(),
263 | this.contractInstance.partyB(),
264 | this.contractInstance.status(),
265 | this.contractInstance.arbitratorExtraData(),
266 | this.contractInstance.disputeID(),
267 | this.contractInstance.partyAFee(),
268 | this.contractInstance.partyBFee(),
269 | this.contractInstance.lastInteraction(),
270 | this.contractInstance.amount(),
271 | this.getEvidence(),
272 | this.getMetaEvidence()
273 | ])
274 |
275 | return {
276 | address: this.getContractAddress(),
277 | arbitrator,
278 | extraData,
279 | timeout: timeout.toNumber(),
280 | partyA,
281 | partyB,
282 | status: status.toNumber(),
283 | arbitratorExtraData,
284 | disputeID: disputeID.toNumber(),
285 | partyAFee,
286 | partyBFee,
287 | lastInteraction: lastInteraction.toNumber(),
288 | amount: amount.toNumber(),
289 | evidence,
290 | metaEvidence
291 | }
292 | }
293 | }
294 |
295 | export default ArbitrableTransaction
296 |
--------------------------------------------------------------------------------
/src/contracts/implementations/arbitrable/MultipleArbitrableTransaction.js:
--------------------------------------------------------------------------------
1 | import multipleArbitrableTransactionArtifact from 'kleros-interaction/build/contracts/MultipleArbitrableTransaction'
2 | import _ from 'lodash'
3 |
4 | import * as contractConstants from '../../../constants/contract'
5 | import * as errorConstants from '../../../constants/error'
6 | import deployContractAsync from '../../../utils/deployContractAsync'
7 |
8 | import Arbitrable from './Arbitrable'
9 |
10 | /**
11 | * Provides interaction with an Arbitrable Transaction contract deployed on the blockchain.
12 | */
13 | class MultipleArbitrableTransaction extends Arbitrable {
14 | /**
15 | * Constructor ArbitrableTransaction.
16 | * @param {object} web3Provider instance
17 | * @param {string} contractAddress of the contract
18 | */
19 | constructor(web3Provider, contractAddress) {
20 | super(web3Provider, multipleArbitrableTransactionArtifact, contractAddress)
21 |
22 | this.arbitrableTransactionId = null
23 | }
24 |
25 | /**
26 | * Deploy MultipleArbitrableTransaction.
27 | * @param {object} account Ethereum account
28 | * @param {object} web3Provider web3 provider object
29 | * @returns {object} truffle-contract Object | err The deployed contract or an error
30 | */
31 | static deploy = async (account, web3Provider) => {
32 | const contractDeployed = await deployContractAsync(
33 | account,
34 | 0,
35 | multipleArbitrableTransactionArtifact,
36 | web3Provider
37 | )
38 |
39 | return contractDeployed
40 | }
41 |
42 | /**
43 | * Create MultipleArbitrableTransaction.
44 | * @param {object} account Ethereum account
45 | * @param {string} arbitratorAddress The address of the arbitrator contract
46 | * @param {object} seller Seller Ethereum account
47 | * @param {number} value funds to be placed in contract
48 | * @param {number} timeout Time (seconds) after which a party automatically loose a dispute. (default 3600)
49 | * @param {bytes} arbitratorExtraData Extra data for the arbitrator. (default empty string)
50 | * @param {string} metaEvidenceUri Uri meta-evidence. (default empty string)
51 | * @returns {object} truffle-contract Object | err The deployed contract or an error
52 | */
53 | createArbitrableTransaction = async (
54 | account,
55 | arbitratorAddress,
56 | seller,
57 | value,
58 | timeout = 3600,
59 | arbitratorExtraData = 0x0,
60 | metaEvidenceUri
61 | ) => {
62 | await this.loadContract()
63 |
64 | try {
65 | return this.contractInstance.createTransaction(
66 | arbitratorAddress,
67 | timeout,
68 | seller,
69 | arbitratorExtraData,
70 | metaEvidenceUri,
71 | {
72 | from: account,
73 | value: value,
74 | gas: process.env.GAS || undefined
75 | }
76 | )
77 | } catch (err) {
78 | console.error(err)
79 | throw new Error(errorConstants.UNABLE_TO_CREATE_TRANSACTION)
80 | }
81 | }
82 |
83 | /**
84 | * Pay the seller. To be called when the good is delivered or the service rendered.
85 | * @param {string} account - Ethereum account.
86 | * @param {number} arbitrableTransactionId - The index of the transaction.
87 | * @param {amount} amount - Part or all of the amount of the good or the service.
88 | * @returns {object} - The result transaction object.
89 | */
90 | pay = async (account, arbitrableTransactionId, amount) => {
91 | await this.loadContract()
92 |
93 | try {
94 | return this.contractInstance.pay(
95 | arbitrableTransactionId,
96 | this._Web3Wrapper.toWei(amount, 'ether'),
97 | {
98 | from: account,
99 | value: 0
100 | }
101 | )
102 | } catch (err) {
103 | console.error(err)
104 | throw new Error(errorConstants.UNABLE_TO_PAY_SELLER)
105 | }
106 | }
107 |
108 | /**
109 | * Reimburse the seller. To be called when the good is not delivered or the service rendered.
110 | * @param {string} account - Ethereum account.
111 | * @param {number} arbitrableTransactionId - The index of the transaction.
112 | * @param {amount} amount - Part or all of the amount of the good or the service.
113 | * @returns {object} - The result transaction object.
114 | */
115 | reimburse = async (
116 | account,
117 | arbitrableTransactionId,
118 | amount
119 | ) => {
120 | await this.loadContract()
121 |
122 | try {
123 | return this.contractInstance.reimburse(
124 | arbitrableTransactionId,
125 | this._Web3Wrapper.toWei(amount, 'ether'),
126 | {
127 | from: account,
128 | value: 0
129 | }
130 | )
131 | } catch (err) {
132 | console.error(err)
133 | throw new Error(errorConstants.UNABLE_TO_REIMBURSE_BUYER)
134 | }
135 | }
136 |
137 | /**
138 | * Pay the arbitration fee to raise a dispute. To be called by the buyer.
139 | * @param {string} account - Ethereum account.
140 | * @param {number} arbitrableTransactionId - The index of the transaction.
141 | * @param {number} arbitrationCost - Arbitration cost.
142 | * @returns {object} - The result transaction object.
143 | */
144 | payArbitrationFeeByBuyer = async (
145 | account,
146 | arbitrableTransactionId,
147 | arbitrationCost
148 | ) => {
149 | await this.loadContract()
150 |
151 | try {
152 | return this.contractInstance.payArbitrationFeeByBuyer(
153 | arbitrableTransactionId,
154 | {
155 | from: account,
156 | value: arbitrationCost
157 | }
158 | )
159 | } catch (err) {
160 | console.error(err)
161 | throw new Error(errorConstants.UNABLE_TO_PAY_ARBITRATION_FEE)
162 | }
163 | }
164 |
165 | /**
166 | * Pay the arbitration fee to raise a dispute. To be called by the seller.
167 | * @param {string} account Ethereum account.
168 | * @param {number} arbitrableTransactionId - The index of the transaction.
169 | * @param {number} arbitrationCost - Arbitration cost.
170 | * @returns {object} - The result transaction object.
171 | */
172 | payArbitrationFeeBySeller = async (
173 | account,
174 | arbitrableTransactionId,
175 | arbitrationCost
176 | ) => {
177 | await this.loadContract()
178 |
179 | try {
180 | return this.contractInstance.payArbitrationFeeBySeller(
181 | arbitrableTransactionId,
182 | {
183 | from: account,
184 | value: arbitrationCost,
185 | gas: process.env.GAS || undefined
186 | }
187 | )
188 | } catch (err) {
189 | console.error(err)
190 | throw new Error(errorConstants.UNABLE_TO_PAY_ARBITRATION_FEE)
191 | }
192 | }
193 |
194 | /**
195 | * Submit evidence.
196 | * @param {string} account ETH address of user.
197 | * @param {number} arbitrableTransactionId - The index of the transaction.
198 | * @param {string} url A link to an evidence using its URI.
199 | * @returns {string} txHash Hash transaction.
200 | */
201 | submitEvidence = async (
202 | account,
203 | arbitrableTransactionId,
204 | url
205 | ) => {
206 | await this.loadContract()
207 |
208 | const txHashObj = await this.contractInstance.submitEvidence(
209 | arbitrableTransactionId,
210 | url,
211 | {
212 | from: account,
213 | value: 0
214 | }
215 | )
216 |
217 | return txHashObj.tx
218 | }
219 |
220 | /**
221 | * Call by buyer if seller is timeout
222 | * @param {string} account ETH address of user
223 | * @param {number} arbitrableTransactionId - The index of the transaction.
224 | * @returns {object} The result transaction object.
225 | */
226 | callTimeOutBuyer = async (
227 | account,
228 | arbitrableTransactionId
229 | ) => {
230 | await this.loadContract()
231 |
232 | const transactionArbitrableData = await this.getData(
233 | arbitrableTransactionId
234 | )
235 |
236 | const status = transactionArbitrableData.status
237 | const timeout = transactionArbitrableData.timeout
238 | const lastInteraction = transactionArbitrableData.lastInteraction
239 |
240 | if (status !== contractConstants.STATUS.WAITING_SELLER) {
241 | throw new Error(errorConstants.CONTRACT_IS_NOT_WAITING_ON_OTHER_PARTY)
242 | } else if (Math.trunc(Date.now() / 1000) <= lastInteraction + timeout) {
243 | throw new Error(errorConstants.TIMEOUT_NOT_REACHED)
244 | }
245 |
246 | try {
247 | return this.contractInstance.timeOutByBuyer(arbitrableTransactionId, {
248 | from: account,
249 | value: 0
250 | })
251 | } catch (err) {
252 | console.error(err)
253 | throw new Error(errorConstants.UNABLE_TO_CALL_TIMEOUT)
254 | }
255 | }
256 |
257 | /**
258 | * Call by seller if buyer is timeout.
259 | * @param {string} account - ETH address of user.
260 | * @param {number} arbitrableTransactionId - The index of the transaction.
261 | * @param {string} contractAddress - ETH address of contract.
262 | * @returns {object} The result transaction object.
263 | */
264 | callTimeOutSeller = async (
265 | account,
266 | arbitrableTransactionId
267 | ) => {
268 | await this.loadContract()
269 |
270 | const status = await this.contractInstance.status()
271 | const timeout = await this.contractInstance.timeout()
272 | const lastInteraction = await this.contractInstance.lastInteraction()
273 |
274 | if (status !== contractConstants.STATUS.WAITING_BUYER) {
275 | throw new Error(errorConstants.CONTRACT_IS_NOT_WAITING_ON_OTHER_PARTY)
276 | } else if (Date.now() >= lastInteraction + timeout) {
277 | throw new Error(errorConstants.TIMEOUT_NOT_REACHED)
278 | }
279 |
280 | try {
281 | return this.contractInstance.timeOutBySeller(arbitrableTransactionId, {
282 | from: account,
283 | value: 0
284 | })
285 | } catch (err) {
286 | console.error(err)
287 | throw new Error(errorConstants.UNABLE_TO_CALL_TIMEOUT)
288 | }
289 | }
290 |
291 | /**
292 | * Appeal an appealable ruling.
293 | * @param {string} account Ethereum account.
294 | * @param {number} arbitrableTransactionId - The index of the transaction.
295 | * @param {bytes} extraData for the arbitrator appeal procedure.
296 | * @param {number} appealCost Amount to pay the arbitrator. (default 0.35 ether).
297 | * @returns {object} - The result transaction object.
298 | */
299 | appeal = async (
300 | account,
301 | arbitrableTransactionId,
302 | extraData = 0x0,
303 | appealCost = 0.3
304 | ) => {
305 | await this.loadContract()
306 |
307 | try {
308 | return this.contractInstance.appeal(arbitrableTransactionId, extraData, {
309 | from: account,
310 | value: appealCost
311 | })
312 | } catch (err) {
313 | console.error(err)
314 | throw new Error(errorConstants.UNABLE_TO_RAISE_AN_APPEAL)
315 | }
316 | }
317 |
318 | /**
319 | * Set the arbitrable transaction id
320 | * @param {number} arbitrableTransactionId - The index of the transaction.
321 | * @returns {object} Object Data of the contract.
322 | */
323 | setArbitrableTransactionId = arbitrableTransactionId =>
324 | (this.arbitrableTransactionId = arbitrableTransactionId)
325 |
326 | /**
327 | * Fetch the parties involved in the arbitrable transaction contract.
328 | * @returns {object} returns a mapping of partyA and partyB to ETH addresses.
329 | */
330 | getParties = async (arbitrableTransactionId) => {
331 | await this.loadContract()
332 |
333 | const arbitrableTransaction = await this.contractInstance.transactions(
334 | arbitrableTransactionId
335 | )
336 |
337 | return {
338 | seller: arbitrableTransaction[0],
339 | buyer: arbitrableTransaction[1]
340 | }
341 | }
342 |
343 | /**
344 | * Data of the contract
345 | * @param {number} arbitrableTransactionId - The index of the transaction.
346 | * @returns {object} Object Data of the contract.
347 | */
348 | getData = async arbitrableTransactionId => {
349 | await this.loadContract()
350 |
351 | const arbitrableTransaction = await this.contractInstance.transactions(
352 | arbitrableTransactionId
353 | )
354 |
355 | return {
356 | seller: arbitrableTransaction[0],
357 | buyer: arbitrableTransaction[1],
358 | amount: this._Web3Wrapper.fromWei(
359 | arbitrableTransaction[2].toNumber(),
360 | 'ether'
361 | ),
362 | timeout: arbitrableTransaction[3].toNumber(),
363 | disputeId: arbitrableTransaction[4].toNumber(),
364 | arbitrator: arbitrableTransaction[5],
365 | arbitratorExtraData: arbitrableTransaction[6],
366 | sellerFee: this._Web3Wrapper.fromWei(arbitrableTransaction[7], 'ether'),
367 | buyerFee: this._Web3Wrapper.fromWei(arbitrableTransaction[8], 'ether'),
368 | lastInteraction: arbitrableTransaction[9].toNumber(),
369 | status: arbitrableTransaction[10].toNumber(),
370 | metaEvidence: await this.getMetaEvidence(arbitrableTransactionId),
371 | evidence: await this.getEvidence(
372 | arbitrableTransaction[5],
373 | arbitrableTransaction[4].toNumber()
374 | )
375 | }
376 | }
377 | }
378 |
379 | export default MultipleArbitrableTransaction
380 |
--------------------------------------------------------------------------------
/src/contracts/implementations/arbitrable/index.js:
--------------------------------------------------------------------------------
1 | import ArbitrableTransaction from './ArbitrableTransaction'
2 | import MultipleArbitrableTransaction from './MultipleArbitrableTransaction'
3 | import ArbitrablePermissionList from './ArbitrablePermissionList'
4 |
5 | export {
6 | ArbitrableTransaction,
7 | MultipleArbitrableTransaction,
8 | ArbitrablePermissionList
9 | }
10 |
--------------------------------------------------------------------------------
/src/contracts/implementations/arbitrator/KlerosPOC.js:
--------------------------------------------------------------------------------
1 | import klerosPOCArtifact from 'kleros/build/contracts/KlerosPOC'
2 | import _ from 'lodash'
3 |
4 | import * as ethConstants from '../../../constants/eth'
5 | import deployContractAsync from '../../../utils/deployContractAsync'
6 |
7 | import Kleros from './Kleros'
8 |
9 | /**
10 | * Provides interaction with a KlerosPOC contract on the blockchain.
11 | */
12 | class KlerosPOC extends Kleros {
13 | /**
14 | * Create new KlerosPOC Implementation.
15 | * @param {object} web3Provider - web3 instance.
16 | * @param {string} contractAddress - Address of the KlerosPOC contract.
17 | */
18 | constructor(web3Provider, contractAddress) {
19 | super(web3Provider, contractAddress, klerosPOCArtifact)
20 | }
21 |
22 | /**
23 | * STATIC: Deploy a KlerosPOC contract on the blockchain.
24 | * @param {string} rngAddress address of random number generator contract
25 | * @param {string} pnkAddress address of pinakion contract
26 | * @param {number[]} timesPerPeriod array of 5 ints indicating the time limit for each period of contract
27 | * @param {string} account address of user
28 | * @param {number} value amout of eth to send to contract
29 | * @param {object} web3Provider web3 provider object NOTE: NOT Kleros Web3Wrapper
30 | * @returns {object} truffle-contract Object | err The contract object or error deploy
31 | */
32 | static deploy = async (
33 | rngAddress,
34 | pnkAddress,
35 | timesPerPeriod = [300, 0, 300, 300, 300],
36 | account,
37 | value = ethConstants.TRANSACTION.VALUE,
38 | web3Provider
39 | ) => {
40 | const contractDeployed = await deployContractAsync(
41 | account,
42 | value,
43 | klerosPOCArtifact,
44 | web3Provider,
45 | pnkAddress,
46 | rngAddress,
47 | timesPerPeriod
48 | )
49 |
50 | return contractDeployed
51 | }
52 |
53 | /**
54 | * Purchase PNK.
55 | * @param {string} amount - The amount of pinakion to buy in wei.
56 | * @param {string} account - The address of the user.
57 | * @returns {object} - The result transaction object.
58 | */
59 | buyPNK = async (amount, account) => {
60 | await this.loadContract()
61 | return this.contractInstance.buyPinakion({
62 | from: account,
63 | value: amount,
64 | gas: process.env.GAS || undefined
65 | })
66 | }
67 | }
68 |
69 | export default KlerosPOC
70 |
--------------------------------------------------------------------------------
/src/contracts/implementations/arbitrator/index.js:
--------------------------------------------------------------------------------
1 | import KlerosPOC from './KlerosPOC'
2 |
3 | export { KlerosPOC }
4 |
--------------------------------------------------------------------------------
/src/contracts/implementations/index.js:
--------------------------------------------------------------------------------
1 | import * as arbitrator from './arbitrator'
2 | import * as arbitrable from './arbitrable'
3 | import * as PNK from './PNK'
4 | import * as RNG from './RNG'
5 |
6 | export { arbitrator, arbitrable, PNK, RNG }
7 |
--------------------------------------------------------------------------------
/src/contracts/index.js:
--------------------------------------------------------------------------------
1 | import * as abstractions from './abstractions'
2 | import * as implementations from './implementations'
3 |
4 | export { abstractions, implementations }
5 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Kleros from './kleros.js'
2 |
3 | export { Kleros }
4 |
--------------------------------------------------------------------------------
/src/kleros.js:
--------------------------------------------------------------------------------
1 | import isRequired from './utils/isRequired'
2 | import Web3Wrapper from './utils/Web3Wrapper'
3 | import StoreProviderWrapper from './utils/StoreProviderWrapper'
4 | import * as contracts from './contracts'
5 | import * as resources from './resources'
6 | import EventListener from './utils/EventListener'
7 |
8 | /**
9 | * The Kleros Api provides access to the full suite of functionality. It will initialize
10 | * contract instances for you when possible and creates an object that you can use to
11 | * call all of the other api modules. If you are only going to be interacting with
12 | * specific apis, or you don't want certain functionality such as the off chain store,
13 | * you might find it easier to initialze a specific instance of the api you want.
14 | */
15 | class Kleros {
16 | web3Wrapper = {}
17 |
18 | storeWrapper = {}
19 |
20 | eventListener = null
21 |
22 | /**
23 | * Instantiates a new Kelros instance that provides the public interface
24 | * to Kleros contracts and library. All params are required. To use an individual
25 | * portion of the API import a class and initialize it yourself.
26 | * @param {string} ethereumProvider - The Web3.js Provider instance you would like the
27 | * Kleros.js library to use for interacting with the
28 | * Ethereum network.
29 | * @param {string} storeUri - The storage provider uri used to
30 | * get metadata from the cloud for the UI. e.g. Kleros-Store,
31 | * IPFS, Swarm etc.
32 | * @param {string} arbitratorAddress - Address of the arbitrator contract we should
33 | * use when initializing KlerosPOC
34 | * @param {string} arbitrableContractAddress - Address of the arbitrator contract we should
35 | * use when initializing KlerosPOC
36 | */
37 | constructor(
38 | ethereumProvider = isRequired('ethereumProvider'),
39 | storeUri = isRequired('storeUri'),
40 | arbitratorAddress,
41 | arbitrableContractAddress
42 | ) {
43 | /**
44 | * We need a set of implementations that we expose to the outside api and a set we use
45 | * internally. This is because the implementation class itself sets the contract instance
46 | * and we don't want race conditions between external and internal calls.
47 | *
48 | * FIXME this is an ugly way of doing this and still has some race conditions. See issue #138.
49 | */
50 | // EXTERNAL
51 | const _klerosPOC = new contracts.implementations.arbitrator.KlerosPOC(
52 | ethereumProvider,
53 | arbitratorAddress
54 | )
55 | const _arbitrableTransaction = new contracts.implementations.arbitrable.MultipleArbitrableTransaction(
56 | ethereumProvider,
57 | arbitrableContractAddress
58 | )
59 | // INTERNAL
60 | const _klerosPOCInternal = new contracts.implementations.arbitrator.KlerosPOC(
61 | ethereumProvider,
62 | arbitratorAddress
63 | )
64 | const _arbitrableTransactionInternal = new contracts.implementations.arbitrable.MultipleArbitrableTransaction(
65 | ethereumProvider,
66 | arbitrableContractAddress
67 | )
68 |
69 | // **************************** //
70 | // * INITIALIZED CLASSES * //
71 | // **************************** //
72 | // KLEROS WRAPPERS
73 | this.web3Wrapper = new Web3Wrapper(ethereumProvider)
74 | this.storeWrapper = new StoreProviderWrapper(storeUri)
75 | // ARBITRATOR
76 | this.arbitrator = new contracts.abstractions.Arbitrator(
77 | _klerosPOC,
78 | this.storeWrapper
79 | )
80 | // ARBITRABLE CONTRACTS
81 | this.arbitrable = new contracts.abstractions.Arbitrable(
82 | _arbitrableTransaction,
83 | this.storeWrapper
84 | )
85 |
86 | // Create new instance of arbitator and arbitrable for behind the scene task runners to use
87 | const _arbitrator = new contracts.abstractions.Arbitrator(
88 | _klerosPOCInternal,
89 | this.storeWrapper
90 | )
91 | const _arbitrable = new contracts.abstractions.Arbitrable(
92 | _arbitrableTransactionInternal,
93 | this.storeWrapper
94 | )
95 | // DISPUTES
96 | this.disputes = new resources.Disputes(
97 | _arbitrator,
98 | _arbitrable,
99 | this.storeWrapper
100 | )
101 | // NOTIFICATIONS
102 | this.notifications = new resources.Notifications(
103 | _arbitrator,
104 | _arbitrable,
105 | this.storeWrapper
106 | )
107 | // AUTH
108 | this.auth = new resources.Auth(this.web3Wrapper, this.storeWrapper)
109 | }
110 |
111 | /**
112 | * Set a new arbitrable contract for Kleros instance of arbitrableContracts
113 | * @param {string} contractAddress - Address of arbitrable contract
114 | */
115 | setArbitrableContractAddress = contractAddress => {
116 | this.arbitrable.setContractInstance(contractAddress)
117 | }
118 |
119 | /**
120 | * Bootstraps an EventListener and adds all Kleros handlers for event logs. Use
121 | * this if you want to watch the chain for notifications, or are using the off chain
122 | * store for metadata.
123 | * @param {string} account Address of the user
124 | * @param {function} callback The function to be called once a notification
125 | * @returns {Promise} the watcher promise so that user can wait for event watcher to start before taking other actions.
126 | */
127 | watchForEvents = async (
128 | account,
129 | callback // for notification callback
130 | ) => {
131 | // stop current event listeners
132 | if (this.eventListener) {
133 | this.eventListener.stopWatchingForEvents()
134 | }
135 | // reinitialize with current arbitrator contract instance
136 | this.eventListener = new EventListener([this.arbitrator])
137 | // add handlers for notifications
138 | this.notifications.registerArbitratorNotifications(
139 | account,
140 | this.eventListener,
141 | callback
142 | )
143 | // add handlers for event driven store updates
144 | this.disputes.registerStoreUpdateEventListeners(account, this.eventListener)
145 | // fetch last block for user
146 | const fromBlock = await this.storeWrapper.getLastBlock(account)
147 | // start event listener
148 | return this.eventListener.watchForEvents(fromBlock)
149 | }
150 |
151 | /**
152 | * Stop watching for events on the Arbitrator initialized in the Kleros Instance.
153 | */
154 | stopWatchingForEvents = () => {
155 | if (this.eventListener)
156 | this.eventListener.removeContractInstance(this.arbitrator)
157 | }
158 |
159 | /**
160 | * Sets the store provider uri for all higher level apis in the Kleros Instance.
161 | * @param {string} storeUri - The URI that the store provider will use
162 | */
163 | setStoreProvider = storeUri => {
164 | this.storeWrapper = new StoreProviderWrapper(storeUri)
165 |
166 | this.disputes.setStoreProviderInstance(this.storeWrapper)
167 | this.arbitrable.setStoreProviderInstance(this.storeWrapper)
168 | this.arbitrator.setStoreProviderInstance(this.storeWrapper)
169 | this.notifications.setStoreProviderInstance(this.storeWrapper)
170 | this.auth.setStoreProviderInstance(this.storeWrapper)
171 | }
172 | }
173 |
174 | export default Kleros
175 |
--------------------------------------------------------------------------------
/src/resources/Auth.js:
--------------------------------------------------------------------------------
1 | import Personal from 'web3-eth-personal'
2 |
3 | import isRequired from '../utils/isRequired'
4 | import { UNABLE_TO_SIGN_TOKEN } from '../constants/error'
5 |
6 | class Auth {
7 | constructor(
8 | web3Wrapper = isRequired('web3Wrapper'),
9 | storeProviderInstance = isRequired('storeProviderInstance')
10 | ) {
11 | this._Web3Wrapper = web3Wrapper
12 | this._StoreProviderInstance = storeProviderInstance
13 | }
14 |
15 | /**
16 | * Set store provider instance.
17 | * @param {object} storeProviderInstance - instance of store provider wrapper.
18 | */
19 | setStoreProviderInstance = storeProviderInstance => {
20 | this._StoreProviderInstance = storeProviderInstance
21 | }
22 |
23 | /**
24 | * Set an auth token in the Store Provider. Call this instead of validateNewAuthToken
25 | * if you have a signed token saved.
26 | * @param {string} token - Hex representation of signed token.
27 | */
28 | setAuthToken = token => {
29 | this._StoreProviderInstance.setAuthToken(token)
30 | }
31 |
32 | /**
33 | * Validate a new auth token. Note if you validate a new token old signed tokens
34 | * will not longer be valid regardless of their expiration time.
35 | * @param {string} userAddress - Address of the user profile
36 | * @returns {string} Signed token for future use.
37 | */
38 | getNewAuthToken = async userAddress => {
39 | const unsignedToken = (await this._StoreProviderInstance.newAuthToken(
40 | userAddress
41 | )).unsignedToken
42 |
43 | const signedToken = await this.signMessage(userAddress, unsignedToken)
44 | // make sure token is valid
45 | if (!await this.validateAuthToken(userAddress, signedToken))
46 | throw new Error(UNABLE_TO_SIGN_TOKEN)
47 |
48 | return signedToken
49 | }
50 |
51 | /**
52 | * Sign a message with your private key. Uses web3 1.0 personal sign
53 | * @param {string} userAddress - The address with which we want to sign the message
54 | * @param {string} data - Hex encoded data to sign
55 | * @returns {string} signed data
56 | */
57 | signMessage = (userAddress, data) => {
58 | const ethPersonal = new Personal(this._Web3Wrapper.getProvider())
59 | return new Promise((resolve, reject) => {
60 | ethPersonal.sign(data, userAddress, (error, result) => {
61 | if (error) reject(error)
62 |
63 | resolve(result)
64 | })
65 | })
66 | }
67 |
68 | /**
69 | * Validate an auth token.
70 | * @param {string} userAddress - The address of the user.
71 | * @param {string} authToken - Token to check.
72 | * @returns {Promise} resolves to True if token is valid.
73 | */
74 | validateAuthToken = (userAddress, authToken) =>
75 | this._StoreProviderInstance.isTokenValid(userAddress, authToken)
76 | }
77 |
78 | export default Auth
79 |
--------------------------------------------------------------------------------
/src/resources/Disputes.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | import * as arbitratorConstants from '../constants/arbitrator'
4 | import * as disputeConstants from '../constants/dispute'
5 | import isRequired from '../utils/isRequired'
6 |
7 | /**
8 | * Disputes API. Provides cross arbitrator and arbitrable contracts functionality.
9 | * Requires Store Provider to be set.
10 | */
11 | class Disputes {
12 | constructor(
13 | arbitratorInstance = isRequired('arbitratorInstance'),
14 | arbitrableInstance = isRequired('arbitrableInstance'),
15 | storeProviderInstance = isRequired('storeProviderInstance')
16 | ) {
17 | this._ArbitratorInstance = arbitratorInstance
18 | this._ArbitrableInstance = arbitrableInstance
19 | this._StoreProviderInstance = storeProviderInstance
20 | this.disputeCache = {}
21 | }
22 | /**
23 | * Set arbitrator instance.
24 | * @param {object} arbitratorInstance - instance of an arbitrator contract.
25 | */
26 | setArbitratorInstance = arbitratorInstance => {
27 | this._ArbitratorInstance = arbitratorInstance
28 | }
29 | /**
30 | * Set arbitrable instance.
31 | * @param {object} arbitrableInstance - instance of an arbitrable contract.
32 | */
33 | setArbitrableInstance = arbitrableInstance => {
34 | this._ArbitrableInstance = arbitrableInstance
35 | }
36 | /**
37 | * Set store provider instance.
38 | * @param {object} storeProviderInstance - instance of store provider wrapper.
39 | */
40 | setStoreProviderInstance = storeProviderInstance => {
41 | this._StoreProviderInstance = storeProviderInstance
42 | }
43 |
44 | // **************************** //
45 | // * Events * //
46 | // **************************** //
47 |
48 | /**
49 | * Method to register all dispute handlers to an EventListener.
50 | * @param {string} account - The address of the user.
51 | * @param {object} eventListener - The EventListener instance. See utils/EventListener.js.
52 | */
53 | registerStoreUpdateEventListeners = (
54 | account = isRequired('account'),
55 | eventListener = isRequired('eventListener')
56 | ) => {
57 | const eventHandlerMap = {
58 | DisputeCreation: [this._storeNewDisputeHandler]
59 | }
60 |
61 | for (let event in eventHandlerMap) {
62 | if (eventHandlerMap.hasOwnProperty(event)) {
63 | eventHandlerMap[event].forEach(handler => {
64 | eventListener.addEventHandler(this._ArbitratorInstance, event, args =>
65 | handler(args, account)
66 | )
67 | })
68 | }
69 | }
70 | }
71 |
72 | /**
73 | * Event listener handler that stores dispute in store upon creation
74 | * @param {string} event - The event log.
75 | * @param {string} account - Account of user to update store data for
76 | */
77 | _storeNewDisputeHandler = async (event, account) => {
78 | // There is no need to handle this event if we are not using the store
79 | const disputeID = event.args._disputeID.toNumber()
80 |
81 | const arbitratorAddress = this._ArbitratorInstance.getContractAddress()
82 | // arbitrator data
83 | const disputeData = await this._ArbitratorInstance.getDispute(disputeID)
84 | // arbitrable contract data
85 | await this._ArbitrableInstance.setContractInstance(
86 | disputeData.arbitrableContractAddress
87 | )
88 |
89 | const arbitrableContractData = await this._ArbitrableInstance.getData(
90 | account
91 | )
92 |
93 | if (
94 | account === arbitrableContractData.partyA ||
95 | account === arbitrableContractData.partyB
96 | ) {
97 | await this._StoreProviderInstance.updateDisputeProfile(
98 | account,
99 | arbitratorAddress,
100 | disputeID,
101 | {
102 | contractAddress: disputeData.arbitrableContractAddress,
103 | partyA: arbitrableContractData.partyA,
104 | partyB: arbitrableContractData.partyB,
105 | blockNumber: event.blockNumber
106 | }
107 | )
108 | }
109 | }
110 |
111 | // **************************** //
112 | // * Internal * //
113 | // **************************** //
114 |
115 | /**
116 | * Add new data to the cache
117 | * @param {number} disputeID - The index of the dispute. Used as the key in cache
118 | * @param {object} newCacheData - Freeform data to cache. Will overwrite data with the same keys.
119 | */
120 | _updateDisputeCache = (disputeID, newCacheData = {}) => {
121 | this.disputeCache[disputeID] = {
122 | ...this.disputeCache[disputeID],
123 | ...newCacheData
124 | }
125 | }
126 |
127 | /**
128 | * Get the block at which a dispute was created. Used to find timestamps for dispute.
129 | * The start block is cached after it has been found once as it will never change.
130 | * @param {number} disputeID - The index of the dispute.
131 | * @param {string} account - The address of the user.
132 | * @returns {number} The block number that the dispute was created.
133 | */
134 | _getDisputeStartBlock = async (disputeID, account) => {
135 | const cachedDispute = this.disputeCache[disputeID]
136 | if (cachedDispute && cachedDispute.startBlock)
137 | return cachedDispute.startBlock
138 |
139 | const arbitratorAddress = this._ArbitratorInstance.getContractAddress()
140 |
141 | let blockNumber
142 |
143 | try {
144 | const userData = await this._StoreProviderInstance.getDispute(
145 | account,
146 | arbitratorAddress,
147 | disputeID
148 | )
149 | blockNumber = userData.blockNumber
150 | // eslint-disable-next-line no-unused-vars
151 | } catch (err) {}
152 | // if block number is not stored we can look it up
153 | if (!blockNumber) {
154 | // Fetching a dispute will fail if it hasn't been added to the store yet. This is ok, we can just not return store data
155 | // see if we can get dispute start block from events
156 | const disputeCreationEvent = await this._ArbitratorInstance.getDisputeCreationEvent(
157 | disputeID
158 | )
159 | if (disputeCreationEvent) {
160 | blockNumber = disputeCreationEvent.blockNumber
161 | }
162 | }
163 |
164 | // cache start block for dispute
165 | this._updateDisputeCache(disputeID, { startBlock: blockNumber })
166 | return blockNumber
167 | }
168 |
169 | // **************************** //
170 | // * Public * //
171 | // **************************** //
172 | /**
173 | * Fetch the shared dispute data from the store.
174 | * @param {string} account - The users account.
175 | * @param {string} disputeID - The index of the dispute.
176 | * @returns {Promise} The dispute data in the store.
177 | */
178 | getDisputeFromStore = async (account, disputeID) => {
179 | const arbitratorAddress = this._ArbitratorInstance.getContractAddress()
180 | return this._StoreProviderInstance.getDispute(
181 | account,
182 | arbitratorAddress,
183 | disputeID
184 | )
185 | }
186 |
187 | /**
188 | * Get the dispute deadline for the appeal.
189 | * @param {number} disputeID - The index of the dispute.
190 | * @param {number} appeal - The appeal number. 0 if there have been no appeals.
191 | * @returns {number} timestamp of the appeal
192 | */
193 | getDisputeDeadline = async (disputeID, appeal = 0) => {
194 | const cachedDispute = this.disputeCache[disputeID] || {}
195 | if (cachedDispute.appealDeadlines && cachedDispute.appealDeadlines[appeal])
196 | return cachedDispute.appealDeadlines[appeal]
197 |
198 | const dispute = await this._ArbitratorInstance.getDispute(disputeID)
199 |
200 | const deadlineTimestamp = await this._ArbitratorInstance.getDisputeDeadlineTimestamp(
201 | dispute.firstSession + appeal
202 | )
203 |
204 | if (deadlineTimestamp) {
205 | const currentDeadlines = cachedDispute.appealDeadlines || []
206 | currentDeadlines[appeal] = deadlineTimestamp
207 | // cache the deadline for the appeal
208 | this._updateDisputeCache(disputeID, {
209 | appealDeadlines: currentDeadlines
210 | })
211 | }
212 |
213 | return deadlineTimestamp
214 | }
215 |
216 | /**
217 | * Get the timestamp on when the dispute's ruling was finalized.
218 | * @param {number} disputeID - The index of the dispute.
219 | * @param {number} appeal - The appeal number. 0 if there have been no appeals.
220 | * @returns {number} timestamp of the appeal
221 | */
222 | getAppealRuledAt = async (disputeID, appeal = 0) => {
223 | const cachedDispute = this.disputeCache[disputeID] || {}
224 | if (
225 | cachedDispute.appealRuledAt &&
226 | cachedDispute.appealRuledAt[appeal]
227 | )
228 | return cachedDispute.appealRuledAt[appeal]
229 |
230 | const dispute = await this._ArbitratorInstance.getDispute(disputeID)
231 | const appealRuledAtTimestamp = await this._ArbitratorInstance.getAppealRuledAtTimestamp(
232 | dispute.firstSession + appeal
233 | )
234 |
235 | // cache the deadline for the appeal
236 | if (appealRuledAtTimestamp) {
237 | const currentRuledAt = cachedDispute.appealRuledAt || []
238 | currentRuledAt[appeal] = appealRuledAtTimestamp
239 | this._updateDisputeCache(disputeID, {
240 | appealRuledAt: currentRuledAt
241 | })
242 | }
243 |
244 | return appealRuledAtTimestamp
245 | }
246 |
247 | /**
248 | * Get the timestamp on when the dispute's appeal was created
249 | * @param {number} disputeID - The index of the dispute.
250 | * @param {string} account - The users address.
251 | * @param {number} appeal - The appeal number. 0 if there have been no appeals.
252 | * @returns {number} timestamp of the appeal
253 | */
254 | getAppealCreatedAt = async (disputeID, account, appeal = 0) => {
255 | const cachedDispute = this.disputeCache[disputeID] || {}
256 | if (
257 | cachedDispute.appealCreatedAt &&
258 | cachedDispute.appealCreatedAt[appeal]
259 | )
260 | return cachedDispute.appealCreatedAt[appeal]
261 |
262 | const dispute = await this._ArbitratorInstance.getDispute(disputeID)
263 |
264 | let appealCreatedAtTimestamp = null
265 | if (appeal === 0) {
266 | const creationBlock = await this._getDisputeStartBlock(disputeID, account)
267 | if (creationBlock) {
268 | const timestampSeconds = await this._ArbitratorInstance._getTimestampForBlock(
269 | creationBlock
270 | )
271 |
272 | appealCreatedAtTimestamp = timestampSeconds * 1000
273 | }
274 | } else {
275 | appealCreatedAtTimestamp = await this._ArbitratorInstance.getAppealCreationTimestamp(
276 | dispute.firstSession + (appeal - 1) // appeal was created during previous session
277 | )
278 |
279 | // cache the deadline for the appeal
280 | if (appealCreatedAtTimestamp) {
281 | const currentCreatedAt = cachedDispute.appealCreatedAt || []
282 | currentCreatedAt[appeal] = appealCreatedAtTimestamp
283 | this._updateDisputeCache(disputeID, {
284 | appealCreatedAt: currentCreatedAt
285 | })
286 | }
287 | }
288 |
289 | return appealCreatedAtTimestamp
290 | }
291 |
292 | /**
293 | * Get data for a dispute. This method provides data from the store as well as both
294 | * arbitrator and arbitrable contracts. Used to get all relevant data on a dispute.
295 | * @param {number} disputeID - The dispute's ID.
296 | * @param {string} account - The juror's address.
297 | * @returns {object} - Data object for the dispute that uses data from the contract and the store.
298 | */
299 | getDataForDispute = async (disputeID, account) => {
300 | const arbitratorAddress = this._ArbitratorInstance.getContractAddress()
301 | // Get dispute data from contract. Also get the current session and period.
302 | const [dispute, period, session] = await Promise.all([
303 | this._ArbitratorInstance.getDispute(disputeID, true),
304 | this._ArbitratorInstance.getPeriod(),
305 | this._ArbitratorInstance.getSession()
306 | ])
307 |
308 | // Get arbitrable contract data and evidence
309 | const arbitrableContractAddress = dispute.arbitrableContractAddress
310 | await this._ArbitrableInstance.setContractInstance(
311 | arbitrableContractAddress
312 | )
313 | const [metaEvidence, evidence, parties] = await Promise.all([
314 | this._ArbitrableInstance.getMetaEvidence(),
315 | this._ArbitrableInstance.getEvidence(),
316 | this._ArbitrableInstance.getParties()
317 | ])
318 |
319 | // Get dispute data from the store
320 | let appealDraws = []
321 |
322 | // get draws if they have been added to store.
323 | try {
324 | const userData = await this._StoreProviderInstance.getDispute(
325 | account,
326 | arbitratorAddress,
327 | disputeID
328 | )
329 | if (userData.appealDraws) appealDraws = userData.appealDraws || []
330 | // eslint-disable-next-line no-unused-vars
331 | } catch (err) {
332 | // Dispute exists on chain but not in store. We have lost draws for past disputes.
333 | console.error('Dispute does not exist in store.')
334 | }
335 |
336 | const netPNK = await this._ArbitratorInstance.getNetTokensForDispute(
337 | disputeID,
338 | account
339 | )
340 |
341 | // Build juror info and ruling arrays, indexed by appeal number
342 | const lastSession = dispute.firstSession + dispute.numberOfAppeals
343 | const appealJuror = []
344 | const appealRulings = []
345 |
346 | for (let appeal = 0; appeal <= dispute.numberOfAppeals; appeal++) {
347 | const isLastAppeal = dispute.firstSession + appeal === lastSession
348 | // Get appeal data
349 | const draws = appealDraws[appeal] || []
350 | let canRule = false
351 | let canRepartition = false
352 | let canExecute = false
353 | let ruling
354 | const rulingPromises = [
355 | this._ArbitratorInstance.currentRulingForDispute(disputeID)
356 | ]
357 |
358 | // Extra info for the last appeal
359 | if (isLastAppeal) {
360 | if (draws.length > 0)
361 | rulingPromises.push(
362 | this._ArbitratorInstance.canRuleDispute(disputeID, draws, account)
363 | )
364 |
365 | if (session && period)
366 | canRepartition =
367 | lastSession <= session && // Not appealed to the next session
368 | period === arbitratorConstants.PERIOD.EXECUTE && // Executable period
369 | dispute.state === disputeConstants.STATE.OPEN // Open dispute
370 | canExecute = dispute.state === disputeConstants.STATE.EXECUTABLE // Executable state
371 | }
372 |
373 | // Wait for parallel requests to complete
374 | ;[ruling, canRule] = await Promise.all(rulingPromises)
375 |
376 | let jurorRuling = null
377 | // if can't rule that means they already did or they missed it
378 | if (!canRule) {
379 | jurorRuling = await this._ArbitratorInstance.getVoteForJuror(
380 | dispute.disputeID,
381 | appeal,
382 | account
383 | )
384 | }
385 |
386 | const appealCreatedAt = await this.getAppealCreatedAt(
387 | dispute.disputeID,
388 | account,
389 | appeal
390 | )
391 | const appealDeadline = await this.getDisputeDeadline(
392 | dispute.disputeID,
393 | appeal
394 | )
395 | const appealRuledAt = await this.getAppealRuledAt(
396 | dispute.disputeID,
397 | appeal
398 | )
399 |
400 | appealJuror[appeal] = {
401 | createdAt: appealCreatedAt,
402 | fee: dispute.arbitrationFeePerJuror.mul(draws.length),
403 | draws,
404 | jurorRuling,
405 | canRule
406 | }
407 | appealRulings[appeal] = {
408 | voteCounter: dispute.voteCounters[appeal],
409 | deadline: appealDeadline,
410 | ruledAt: appealRuledAt,
411 | ruling,
412 | canRepartition,
413 | canExecute
414 | }
415 | }
416 |
417 | return {
418 | // Arbitrable Contract Data
419 | arbitrableContractAddress,
420 | arbitratorAddress,
421 | parties,
422 | evidence,
423 | metaEvidence,
424 |
425 | // Dispute Data
426 | disputeID,
427 | firstSession: dispute.firstSession,
428 | lastSession,
429 | numberOfAppeals: dispute.numberOfAppeals,
430 | disputeState: dispute.state,
431 | disputeStatus: dispute.status,
432 | appealJuror,
433 | appealRulings,
434 | netPNK
435 | }
436 | }
437 | }
438 |
439 | export default Disputes
440 |
--------------------------------------------------------------------------------
/src/resources/index.js:
--------------------------------------------------------------------------------
1 | import Disputes from './Disputes'
2 | import Notifications from './Notifications'
3 | import Auth from './Auth'
4 |
5 | export { Disputes, Notifications, Auth }
6 |
--------------------------------------------------------------------------------
/src/utils/EventListener.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | import PromiseQueue from '../utils/PromiseQueue'
4 | import isRequired from '../utils/isRequired'
5 | import * as errorConstants from '../constants/error'
6 |
7 | /**
8 | * EventListener is used to watch events on the blockchain for a set of contracts.
9 | * Handlers for specific events can be added. When an event log is found EventListener
10 | * will fire all handlers registered for the contract.
11 | */
12 | class EventListener {
13 | /**
14 | * Listen for events in contract and handles callbacks with registered event handlers.
15 | * @param {object[]} _contractImplementations - Contract Implementation instances to fetch event logs for.
16 | */
17 | constructor(_contractImplementations = []) {
18 | this.contractInstances = []
19 | // map address -> { event: [handlers], ... }
20 | this.contractEventHandlerMap = {}
21 | // map address -> watcher instance
22 | this.watcherInstances = {}
23 | // event handler queue
24 | this.eventHandlerQueue = new PromiseQueue()
25 | // initialize class variables for new contract instances
26 | _contractImplementations.forEach(instance => {
27 | this.addContractImplementation(instance)
28 | })
29 | }
30 |
31 | /**
32 | * Fetch all logs from contractInstance in a block range.
33 | * @param {object} contractImplementationInstance - Contract Implementation instance.
34 | * @param {number} firstBlock - Lower bound of search range.
35 | * @param {number} lastBlock - Upper bound of search range.
36 | * @returns {Promise} All events in block range.
37 | */
38 | static getAllEventLogs = async (
39 | contractImplementationInstance = isRequired(
40 | 'contractImplementationInstance'
41 | ),
42 | firstBlock = 0,
43 | lastBlock = 'latest'
44 | ) =>
45 | Promise.all(
46 | (await contractImplementationInstance.loadContract())
47 | .allEvents({
48 | fromBlock: firstBlock,
49 | toBlock: lastBlock
50 | })
51 | .get((error, result) => {
52 | if (error)
53 | throw new Error(errorConstants.ERROR_FETCHING_EVENTS(error))
54 |
55 | return result
56 | })
57 | )
58 |
59 | /**
60 | * Fetch logs from contractInstance for a specific event in a block range.
61 | * @param {object} contractImplementationInstance - contract Implementation instance.
62 | * @param {string} eventName - Name of the event.
63 | * @param {number} firstBlock - Lower bound of search range.
64 | * @param {number} lastBlock - Upper bound of search range.
65 | * @param {object} filters - Extra filters
66 | * @returns {Promise} All events in block range.
67 | */
68 | static getEventLogs = async (
69 | contractImplementationInstance = isRequired(
70 | 'contractImplementationInstance'
71 | ),
72 | eventName = isRequired('eventName'),
73 | firstBlock = 0,
74 | lastBlock = 'latest',
75 | filters = {}
76 | ) => {
77 | await contractImplementationInstance.loadContract()
78 | return new Promise((resolve, reject) => {
79 | contractImplementationInstance.contractInstance[eventName](filters, {
80 | fromBlock: firstBlock,
81 | toBlock: lastBlock
82 | }).get((error, result) => {
83 | if (error) reject(errorConstants.ERROR_FETCHING_EVENTS(error))
84 |
85 | resolve(result)
86 | })
87 | })
88 | }
89 |
90 | /**
91 | * Add a contract instance to watch for new event logs.
92 | * @param {object} contractImplementationInstance - Contract Implementation instance
93 | */
94 | addContractImplementation = contractImplementationInstance => {
95 | this.contractInstances.push(contractImplementationInstance)
96 | this.contractEventHandlerMap[
97 | contractImplementationInstance.getContractAddress()
98 | ] = {}
99 | }
100 |
101 | /**
102 | * Remove contract instance being watched. Will also remove all handlers.
103 | * @param {string} contractImplementationInstance - contract implementation instance
104 | */
105 | removeContractInstance = (
106 | contractImplementationInstance = isRequired(
107 | 'contractImplementationInstance'
108 | )
109 | ) => {
110 | const contractAddress = contractImplementationInstance.getContractAddress()
111 | // remove instance from this.contractInstances
112 | const removedInstance = _.remove(
113 | this.contractInstances,
114 | instance => instance.getContractAddress() === contractAddress
115 | )
116 | // if we didn't remove anything throw error
117 | if (removedInstance.length === 0)
118 | throw new Error(errorConstants.MISSING_CONTRACT_INSTANCE(contractAddress))
119 | // stop watching on these instances
120 | removedInstance.forEach(instance => this.stopWatchingForEvents(instance))
121 |
122 | // remove handlers for contract instance
123 | delete this.contractEventHandlerMap[contractAddress]
124 | }
125 |
126 | /**
127 | * Add event handler that will be called when event log is found.
128 | * @param {string} contractImplementationInstance - Contract implementation instance
129 | * @param {string} eventName - Name of event.
130 | * @param {function} handler - Function to be called when event is consumed.
131 | */
132 | addEventHandler = (
133 | contractImplementationInstance = isRequired('contractAddress'),
134 | eventName = isRequired('eventName'),
135 | handler = isRequired('handler')
136 | ) => {
137 | const contractAddress = contractImplementationInstance.getContractAddress()
138 | if (!this.contractEventHandlerMap[contractAddress][eventName])
139 | this.contractEventHandlerMap[contractAddress][eventName] = []
140 | this.contractEventHandlerMap[contractAddress][eventName].push(handler)
141 | }
142 |
143 | /**
144 | * Watch for events on all contract instances. Call registered handlers when logs are found.
145 | * @param {number} fromBlock - A block number can be passed to catch up on missed logs
146 | * @returns {Promise} - Promise resolves when all watchers have been started
147 | */
148 | watchForEvents = async (fromBlock = 'latest') =>
149 | Promise.all(
150 | this.contractInstances.map(async contractImplementation => {
151 | const instance = await contractImplementation.loadContract()
152 | const newWatcherInstance = instance.allEvents({
153 | fromBlock: fromBlock,
154 | lastBlock: 'latest'
155 | })
156 |
157 | // NOTE: should we allow more than one listener per contract instance?
158 | if (this.watcherInstances[instance.address])
159 | this.watcherInstances[instance.address].stopWatching()
160 |
161 | this.watcherInstances[instance.address] = newWatcherInstance
162 | newWatcherInstance.watch((error, result) => {
163 | if (!error) {
164 | const handlers = this.contractEventHandlerMap[instance.address][
165 | result.event
166 | ]
167 | if (handlers) {
168 | handlers.forEach(handler => {
169 | this._queueEvent(handler, result)
170 | })
171 | }
172 | }
173 | })
174 | })
175 | )
176 |
177 | /**
178 | * Stop listening on contract. If no contractAddress supplied it stops all listeners.
179 | * @param {string} contractImplementationInstance - Address of the contract to stop watching
180 | */
181 | stopWatchingForEvents = contractImplementationInstance => {
182 | if (contractImplementationInstance) {
183 | const watcherInstance = this.watcherInstances[
184 | contractImplementationInstance.getContractAddress()
185 | ]
186 |
187 | if (watcherInstance) watcherInstance.stopWatching()
188 | } else
189 | this.contractInstances.forEach(instance => {
190 | this.watcherInstances[instance.getContractAddress()].stopWatching()
191 | })
192 | }
193 |
194 | /**
195 | * Queues an event.
196 | * @param {function} handler - The handler.
197 | * @param {object} event - The event.
198 | */
199 | _queueEvent = async (handler, event) => {
200 | const eventTask = async () => {
201 | await handler(event)
202 | }
203 |
204 | this.eventHandlerQueue.push(eventTask)
205 | }
206 | }
207 |
208 | export default EventListener
209 |
--------------------------------------------------------------------------------
/src/utils/PromiseQueue.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Chain promises so that they are evaluated in order.
3 | * @returns {object} - The promise queue object.
4 | */
5 | const PromiseQueue = () => {
6 | let promise = Promise.resolve()
7 |
8 | return {
9 | push: fn => {
10 | promise = promise.then(fn, fn)
11 | },
12 | fetch: fn => {
13 | let returnResolver
14 | let returnRejecter
15 | const returnPromise = new Promise((resolve, reject) => {
16 | returnResolver = resolve
17 | returnRejecter = reject
18 | })
19 | promise = promise
20 | .then(fn, fn)
21 | .then(res => returnResolver(res), err => returnRejecter(err))
22 |
23 | return returnPromise
24 | }
25 | }
26 | }
27 |
28 | export default PromiseQueue
29 |
--------------------------------------------------------------------------------
/src/utils/StoreProviderWrapper.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | import * as errorConstants from '../constants/error'
4 |
5 | import PromiseQueue from './PromiseQueue'
6 | import httpRequest from './httpRequest'
7 |
8 | /**
9 | * A wrapper for interacting with Kleros Store.
10 | */
11 | class StoreProviderWrapper {
12 | /**
13 | * Create a new instance of StoreProviderWrapper.
14 | * @param {string} storeProviderUri - The uri of kleros store.
15 | */
16 | constructor(storeProviderUri) {
17 | this._storeUri = storeProviderUri
18 | this._storeQueue = new PromiseQueue()
19 | this._cachedProfiles = {}
20 | }
21 |
22 | /**
23 | * use the queue for write request. this allows a function to be passed so we can read immediately before we write
24 | * @param {fn} getBodyFn async function to call before we write. Should to reads and return JSON to be used as body.
25 | * @param {string} verb POST or PUT
26 | * @param {string} uri uri to call
27 | * @param {string} userAddress The users ETH address. Used to clear cache.
28 | * @returns {promise} promise that returns result of request. wait on this if you need it to be syncronous
29 | */
30 | queueWriteRequest = (getBodyFn, verb, uri = null, userAddress) => {
31 | // Clear cache on write TODO update cache after every write
32 | this._cachedProfiles[userAddress] = null
33 |
34 | return this._storeQueue.fetch(() =>
35 | getBodyFn().then(result => httpRequest(verb, uri, result))
36 | )
37 | }
38 |
39 | /**
40 | * 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.
41 | * @param {string} uri uri to hit
42 | * @returns {Promise} promise of the result function
43 | */
44 | queueReadRequest = uri =>
45 | this._storeQueue.fetch(() => httpRequest('GET', uri))
46 |
47 | getMetaEvidenceUri = (
48 | userAddress,
49 | contractAddress,
50 | arbitrableTransactionIndex
51 | ) =>
52 | `${
53 | this._storeUri
54 | }/${userAddress}/contracts/${contractAddress}/arbitrable-transaction/${arbitrableTransactionIndex}/meta-evidence`
55 |
56 | getEvidenceUri = (
57 | userAddress,
58 | contractAddress,
59 | arbitrableTransactionIndex,
60 | evidenceIndex
61 | ) =>
62 | `${
63 | this._storeUri
64 | }/${userAddress}/contracts/${contractAddress}/arbitrable-transaction/${arbitrableTransactionIndex}/evidence/${evidenceIndex}`
65 |
66 | // **************************** //
67 | // * Read * //
68 | // **************************** //
69 |
70 | /**
71 | * Fetch stored user profile.
72 | * @param {string} userAddress - Address of user.
73 | * @returns {object} - a response object.
74 | */
75 | getUserProfile = async userAddress => {
76 | const httpResponse = await httpRequest(
77 | 'GET',
78 | `${this._storeUri}/${userAddress}`
79 | )
80 | this._cachedProfiles[userAddress] = httpResponse.body
81 |
82 | return httpResponse.body
83 | }
84 |
85 | /**
86 | * Fetch stored data on a contract for a user.
87 | * @param {string} userAddress - Address of the user.
88 | * @param {string} addressContract - The address of the contract.
89 | * @returns {object} - Contact data.
90 | */
91 | getContractByAddress = async (userAddress, addressContract) => {
92 | const userProfile = await this.getUserProfile(userAddress)
93 | if (!userProfile) return {}
94 |
95 | let contract = _.filter(
96 | userProfile.contracts,
97 | contract => contract.address === addressContract
98 | )
99 |
100 | return contract[0]
101 | }
102 |
103 | /**
104 | * Get all stored data for a dispute. Must exist in User Profile.
105 | * @param {string} userAddress - Address of user.
106 | * @param {string} arbitratorAddress - Address of arbitrator contract.
107 | * @param {number} disputeID - Index of the dispute.
108 | * @returns {object} - a response object.
109 | */
110 | getDispute = async (userAddress, arbitratorAddress, disputeID) => {
111 | const userProfile = await this.getUserProfile(userAddress)
112 | if (!userProfile)
113 | throw new Error(errorConstants.PROFILE_NOT_FOUND(userAddress))
114 |
115 | const dispute = _.filter(
116 | userProfile.disputes,
117 | o =>
118 | o.arbitratorAddress === arbitratorAddress && o.disputeId === disputeID
119 | )[0] || {}
120 | dispute.disputeID = dispute.disputeId
121 | return dispute
122 | }
123 |
124 | /**
125 | * Fetch stored disputes for a user.
126 | * @param {string} userAddress - Address of user.
127 | * @returns {object} - a response object.
128 | */
129 | getDisputes = async userAddress => {
130 | const userProfile = await this.getUserProfile(userAddress)
131 | if (!userProfile) return []
132 |
133 | return userProfile.disputes.map(dispute => {
134 | dispute.disputeID = dispute.disputeId
135 | return dispute
136 | })
137 | }
138 |
139 | /**
140 | * Fetch the last block seen for a user. This is commonly used with EventListerer.
141 | * @param {string} userAddress - Address of user.
142 | * @returns {number} The last block number.
143 | */
144 | getLastBlock = async userAddress => {
145 | let userProfile
146 | try {
147 | userProfile = (await this.newUserProfile(userAddress)) || {}
148 | // eslint-disable-next-line no-unused-vars
149 | } catch (err) {
150 | userProfile = {}
151 | }
152 |
153 | return userProfile.lastBlock || 0
154 | }
155 |
156 | // **************************** //
157 | // * Write * //
158 | // **************************** //
159 |
160 | /**
161 | * Set up a new user profile if one does not exist.
162 | * @param {string} userAddress - user's address
163 | * @returns {object} - users existing or created profile
164 | */
165 | newUserProfile = async userAddress => {
166 | let userProfile = await this.getUserProfile(userAddress)
167 | if (_.isNull(userProfile)) {
168 | // we can safely make request without queuing because all other writes for profile will fail if it hasn't been created.
169 | const response = await httpRequest(
170 | 'POST',
171 | `${this._storeUri}/${userAddress}`
172 | )
173 | userProfile = response.body
174 | }
175 |
176 | return userProfile
177 | }
178 |
179 | /**
180 | * Update users last block seen. This is the only item in user profile that can be overwritten.
181 | * @param {string} userAddress - User's address.
182 | * @param {string} blockNumber - The newest block number seen by user.
183 | * @returns {object} - HTTP response.
184 | */
185 | updateLastBlock = async (userAddress, blockNumber) => {
186 | const getBodyFn = () =>
187 | new Promise(resolve =>
188 | resolve(
189 | JSON.stringify({
190 | lastBlock: blockNumber
191 | })
192 | )
193 | )
194 |
195 | return this.queueWriteRequest(
196 | getBodyFn,
197 | 'POST',
198 | `${this._storeUri}/${userAddress}/lastBlock`
199 | )
200 | }
201 |
202 | /**
203 | * Update users last processed session.
204 | * @param {string} userAddress - User's address.
205 | * @param {string} session - The current session that the user has processed
206 | * @returns {object} - HTTP response.
207 | */
208 | updateUserSession = async (userAddress, session) => {
209 | const getBodyFn = () =>
210 | new Promise(resolve =>
211 | resolve(
212 | JSON.stringify({
213 | session
214 | })
215 | )
216 | )
217 |
218 | return this.queueWriteRequest(
219 | getBodyFn,
220 | 'POST',
221 | `${this._storeUri}/${userAddress}/session`
222 | )
223 | }
224 |
225 | /**
226 | * Update the stored data on a contract for a user. Note that you cannot overwrite contract data.
227 | * @param {string} userAddress - The user's address.
228 | * @param {string} contractAddress - The address of the contract.
229 | * @param {object} params - Params we want to update.
230 | * @returns {Promise} - The resulting contract data.
231 | */
232 | updateContract = async (userAddress, contractAddress, params) => {
233 | const getBodyFn = async () => {
234 | let currentContractData = await this.getContractByAddress(
235 | userAddress,
236 | contractAddress
237 | )
238 | if (!currentContractData) currentContractData = {}
239 | delete currentContractData._id
240 |
241 | params.address = contractAddress
242 |
243 | return JSON.stringify({ ...currentContractData, ...params })
244 | }
245 |
246 | const httpResponse = await this.queueWriteRequest(
247 | getBodyFn,
248 | 'POST',
249 | `${this._storeUri}/${userAddress}/contracts/${contractAddress}`
250 | )
251 |
252 | return _.filter(
253 | httpResponse.body[0].contracts,
254 | contract => contract.address === contractAddress
255 | )[0]
256 | }
257 |
258 | /**
259 | * Adds new evidence to the store for a users contract. NOTE this will only update the
260 | * stored evidence for the specified user, not all parties of the dispute.
261 | * @param {string} contractAddress - Address of the contract
262 | * @param {string} userAddress - Address of the user.
263 | * @param {string} arbitrableTransactionIndex - Id of the arbitrable transaction.
264 | * @param {string} name - Name of evidence.
265 | * @param {string} description - Description of evidence.
266 | * @param {string} url - A link to the evidence.
267 | * @param {string} hash - The hash of the evidence.
268 | * @returns {number} - The index of the evidence
269 | */
270 | addEvidenceContract = async (
271 | contractAddress,
272 | userAddress,
273 | arbitrableTransactionIndex,
274 | name,
275 | description,
276 | url,
277 | hash
278 | ) => {
279 | const getBodyFn = () =>
280 | new Promise(resolve =>
281 | resolve(
282 | JSON.stringify({
283 | name,
284 | description,
285 | URI: url,
286 | hash
287 | })
288 | )
289 | )
290 |
291 | const response = await this.queueWriteRequest(
292 | getBodyFn,
293 | 'POST',
294 | `${
295 | this._storeUri
296 | }/${userAddress}/contracts/${contractAddress}/arbitrable-transaction/${arbitrableTransactionIndex}/evidence`
297 | )
298 |
299 | if (response.status !== 201)
300 | throw new Error(
301 | errorConstants.REQUEST_FAILED('Unable to submit evidence')
302 | )
303 |
304 | return response.body.evidenceIndex
305 | }
306 |
307 | /**
308 | * Update stored dispute data for a user. Note this will not overwrite data.
309 | * @param {string} userAddress - The address of the user.
310 | * @param {string} arbitratorAddress - The address of the arbitrator contract.
311 | * @param {number} disputeID - The index of the dispute.
312 | * @param {object} params - The dispute data we are updating.
313 | * @returns {Promise} The resulting dispute data.
314 | */
315 | updateDisputeProfile = (
316 | userAddress,
317 | arbitratorAddress,
318 | disputeID,
319 | params
320 | ) => {
321 | const getBodyFn = async () => {
322 | const userProfile = await this.newUserProfile(userAddress)
323 |
324 | const currentDisputeProfile =
325 | _.filter(
326 | userProfile.disputes,
327 | dispute =>
328 | dispute.arbitratorAddress === arbitratorAddress &&
329 | dispute.disputeId === disputeID
330 | )[0] || {}
331 |
332 | delete currentDisputeProfile._id
333 | // set these so if it is a new dispute they are included
334 | params.disputeId = disputeID
335 | params.arbitratorAddress = arbitratorAddress
336 |
337 | return JSON.stringify({ ...currentDisputeProfile, ...params })
338 | }
339 |
340 | return this.queueWriteRequest(
341 | getBodyFn,
342 | 'POST',
343 | `${
344 | this._storeUri
345 | }/${userAddress}/arbitrators/${arbitratorAddress}/disputes/${disputeID}`
346 | )
347 | }
348 |
349 | /**
350 | * Adds draws for juror to dispute profile.
351 | * @param {string} userAddress - The address of the user.
352 | * @param {string} arbitratorAddress - The address of the arbitrator contract.
353 | * @param {number} disputeID - The index of the dispute.
354 | * @param {number[]} draws - The draws the juror has.
355 | * @param {number} appeal - The appeal for which it is for.
356 | * @returns {Promise} The resulting dispute data.
357 | */
358 | addNewDrawsDisputeProfile = (
359 | userAddress,
360 | arbitratorAddress,
361 | disputeID,
362 | draws,
363 | appeal
364 | ) => {
365 | const getBodyFn = () =>
366 | new Promise(resolve =>
367 | resolve(
368 | JSON.stringify({
369 | draws,
370 | appeal
371 | })
372 | )
373 | )
374 |
375 | return this.queueWriteRequest(
376 | getBodyFn,
377 | 'POST',
378 | `${
379 | this._storeUri
380 | }/${userAddress}/arbitrators/${arbitratorAddress}/disputes/${disputeID}/draws`
381 | )
382 | }
383 |
384 | /**
385 | * Create a new notification in the store.
386 | * @param {string} userAddress - The address of the user.
387 | * @param {string} txHash - The transaction hash which produced this event log. Used as an identifier.
388 | * @param {number} logIndex - The index of the log in the transaction. Used as an identifier.
389 | * @param {number} notificationType - The type of the notification. See constants/notification.
390 | * @param {string} message - The message to be stored with the notification.
391 | * @param {object} data - Any extra data stored with the notification.
392 | * @param {boolean} read - If the notification has been read or not.
393 | * @returns {Promise} - The resulting notification.
394 | */
395 | newNotification = async (
396 | userAddress,
397 | txHash,
398 | logIndex,
399 | notificationType,
400 | message = '',
401 | data = {},
402 | read = false
403 | ) => {
404 | const getBodyFn = () =>
405 | new Promise(resolve =>
406 | resolve(
407 | JSON.stringify({
408 | notificationType,
409 | logIndex,
410 | read,
411 | message,
412 | data
413 | })
414 | )
415 | )
416 |
417 | return this.queueWriteRequest(
418 | getBodyFn,
419 | 'POST',
420 | `${this._storeUri}/${userAddress}/notifications/${txHash}`
421 | )
422 | }
423 |
424 | /**
425 | * Create a new notification in the store.
426 | * @param {string} userAddress - The address of the user.
427 | * @param {string} txHash - The transaction hash which produced this event log. Used as an identifier.
428 | * @param {number} logIndex - The index of the log in the transaction. Used as an identifier.
429 | * @param {boolean} isRead - If the notification has been read or not.
430 | * @returns {Promise} - The resulting notification.
431 | */
432 | markNotificationAsRead = async (
433 | userAddress,
434 | txHash,
435 | logIndex,
436 | isRead = true
437 | ) => {
438 | const getBodyFn = () =>
439 | new Promise(resolve =>
440 | resolve(
441 | JSON.stringify({
442 | logIndex,
443 | isRead
444 | })
445 | )
446 | )
447 |
448 | const result = await this.queueWriteRequest(
449 | getBodyFn,
450 | 'POST',
451 | `${this._storeUri}/${userAddress}/notifications/${txHash}/read`
452 | )
453 | return result.body.notifications
454 | }
455 | }
456 |
457 | export default StoreProviderWrapper
458 |
--------------------------------------------------------------------------------
/src/utils/Web3Wrapper.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import Web3 from 'web3'
3 |
4 | class Web3Wrapper {
5 | /**
6 | * Constructor Web3 wrapper.
7 | * @param {object} web3Provider - The web3 instance.
8 | */
9 | constructor(web3Provider) {
10 | this._web3 = new Web3(web3Provider)
11 | }
12 |
13 | isAddress = address => this._web3.isAddress(address)
14 |
15 | getAccount = index => this._web3.eth.accounts[index]
16 |
17 | getProvider = () => this._web3.currentProvider
18 |
19 | getCoinbase = () => this._web3.eth.coinbase
20 |
21 | getNonce = async address => {
22 | const nonce = await this._web3.eth.getTransactionCount(address)
23 | return nonce
24 | }
25 |
26 | toWei = (amount, unit) => {
27 | const newAmount = this._web3.toWei(amount, unit)
28 | return newAmount.toNumber ? newAmount.toNumber() : Number(newAmount)
29 | }
30 |
31 | fromWei = (amount, unit) => {
32 | const newAmount = this._web3.fromWei(amount, unit)
33 | return newAmount.toNumber ? newAmount.toNumber() : Number(newAmount)
34 | }
35 |
36 | toBigNumber = number => this._web3.toBigNumber(number)
37 |
38 | blockNumber = () => this._web3.eth.blockNumber
39 |
40 | sign = (userAddress, data) =>
41 | new Promise((resolve, reject) => {
42 | this._web3.eth.sign(userAddress, data, (error, result) => {
43 | if (error) reject(error)
44 |
45 | resolve(result)
46 | })
47 | })
48 |
49 | getBlock = blockNumber =>
50 | new Promise((resolve, reject) => {
51 | this._web3.eth.getBlock(blockNumber, (error, result) => {
52 | if (error) reject(error)
53 |
54 | resolve(result)
55 | })
56 | })
57 |
58 | doesContractExistAtAddressAsync = async address => {
59 | const code = await this._web3.eth.getCode(address)
60 | // Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients
61 | const codeIsEmpty = /^0x0{0,40}$/i.test(code)
62 |
63 | return !codeIsEmpty
64 | }
65 |
66 | _getNetworkIdIfExistsAsync = async () => {
67 | if (!_.isUndefined(this.networkIdIfExists)) {
68 | return this.networkIdIfExists
69 | }
70 |
71 | try {
72 | const networkId = await this._getNetworkAsync()
73 |
74 | this.networkIdIfExists = Number(networkId)
75 | return this.networkIdIfExists
76 | } catch (err) {
77 | console.log(err)
78 | return undefined
79 | }
80 | }
81 |
82 | _getNetworkAsync = async () => {
83 | const networkId = await this._web3.version.network
84 |
85 | return networkId
86 | }
87 |
88 | getBalanceInWeiAsync = owner => {
89 | if (_.isUndefined(owner)) {
90 | owner = this._web3.eth.accounts[0]
91 | }
92 |
93 | let balanceInWei = this._web3.eth.getBalance(owner)
94 |
95 | return balanceInWei.toString()
96 | }
97 | }
98 |
99 | export default Web3Wrapper
100 |
--------------------------------------------------------------------------------
/src/utils/delegateCalls.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | /**
4 | * delegate calls from a baseClass to a providerClass if the call does not exist in base class
5 | * @param {object} baseClass - The base object that has first priority to make calls
6 | * @param {object} providerClass - The class whose methods will be called by baseClass
7 | * @param {function} middlewareCall - Middleware function that can act as
8 | * intermediary between base and provider. Should take a two params which is the provider
9 | * classes method call and the params passed
10 | */
11 | const delegateCalls = (baseClass, providerClass, middlewareCall) => {
12 | // we don't want to delegate any calls that are part of the base class
13 | const existingMethods = Object.getOwnPropertyNames(baseClass).concat(
14 | Object.getPrototypeOf(baseClass)
15 | )
16 | // methods of provider
17 | const providerMethods = Object.getOwnPropertyNames(providerClass)
18 | // calls we will delegate
19 | const delegatableMethods = providerMethods.filter(method => {
20 | if (
21 | !providerClass.hasOwnProperty(method) ||
22 | typeof providerClass[method] !== 'function'
23 | ) {
24 | return false
25 | }
26 |
27 | return !_.includes(existingMethods, method)
28 | })
29 |
30 | // delegate calls in baseClass
31 | delegatableMethods.forEach(methodName => {
32 | let curriedCall
33 | if (middlewareCall)
34 | curriedCall = (...args) =>
35 | middlewareCall(providerClass[methodName], ...args)
36 | else curriedCall = (...args) => providerClass[methodName](...args)
37 | // set method in baseClass
38 | baseClass[methodName] = curriedCall
39 | })
40 | }
41 |
42 | /**
43 | { constant: true,
44 | inputs: [],
45 | name: 'lastPeriodChange',
46 | outputs: [ [Object] ],
47 | payable: false,
48 | stateMutability: 'view',
49 | type: 'function' },
50 | */
51 |
52 | export default delegateCalls
53 |
--------------------------------------------------------------------------------
/src/utils/deployContractAsync.js:
--------------------------------------------------------------------------------
1 | import contract from 'truffle-contract'
2 |
3 | import * as ethConstants from '../constants/eth'
4 | import { UNABLE_TO_DEPLOY_CONTRACT } from '../constants/error'
5 |
6 | import isRequired from './isRequired'
7 |
8 | /**
9 | * Deploy a contract on the Ethereum network using the contract artifact.
10 | * @param {string} account - The account to deploy it under.
11 | * @param {number} value - The value to send.
12 | * @param {object} artifact - JSON artifact of the contract.
13 | * @param {object} web3Provider - Web3 Provider object (NOTE NOT Kleros Web3Wrapper)
14 | * @param {...any} args - Extra arguments.
15 | * @returns {object} - truffle-contract Object | err The contract object or an error
16 | */
17 | const deployContractAsync = async (
18 | account = isRequired('account'),
19 | value = isRequired('value'),
20 | artifact = isRequired('artifact'),
21 | web3Provider = isRequired('web3Provider'),
22 | ...args
23 | ) => {
24 | try {
25 | const MyContract = contract({
26 | abi: artifact.abi,
27 | unlinked_binary: artifact.bytecode
28 | ? artifact.bytecode
29 | : artifact.unlinked_binary
30 | })
31 | MyContract.setProvider(web3Provider)
32 |
33 | return MyContract.new(...args, {
34 | from: account,
35 | value: value,
36 | gas: ethConstants.TRANSACTION.GAS
37 | })
38 | } catch (err) {
39 | console.error(err)
40 | throw new Error(UNABLE_TO_DEPLOY_CONTRACT)
41 | }
42 | }
43 |
44 | export default deployContractAsync
45 |
--------------------------------------------------------------------------------
/src/utils/getContractAddress.js:
--------------------------------------------------------------------------------
1 | import ethUtil from 'ethereumjs-util'
2 |
3 | const getContractAddress = (account, nonce) =>
4 | ethUtil.bufferToHex(ethUtil.generateAddress(account, nonce))
5 |
6 | export default getContractAddress
7 |
--------------------------------------------------------------------------------
/src/utils/httpRequest.js:
--------------------------------------------------------------------------------
1 | import * as errorConstants from '../constants/error'
2 |
3 | /**
4 | * Helper method for sending an http requests.
5 | * @param {string} verb - HTTP verb to be used in request. E.g. GET, POST, PUT.
6 | * @param {string} uri - The uri to send the request to.
7 | * @param {string} body - json string of the body.
8 | * @returns {Promise} request promise that resolves to the HTTP response.
9 | */
10 | const httpRequest = (verb, uri, body = null) => {
11 | const httpRequest = new XMLHttpRequest()
12 | return new Promise((resolve, reject) => {
13 | try {
14 | httpRequest.open(verb, uri, true)
15 | if (body) {
16 | httpRequest.setRequestHeader(
17 | 'Content-Type',
18 | 'application/json;charset=UTF-8'
19 | )
20 | }
21 | httpRequest.onreadystatechange = () => {
22 | if (httpRequest.readyState === 4) {
23 | let body = null
24 | try {
25 | body = JSON.parse(httpRequest.responseText)
26 | // eslint-disable-next-line no-unused-vars
27 | } catch (err) {}
28 | resolve({
29 | body: body,
30 | status: httpRequest.status
31 | })
32 | }
33 | }
34 | httpRequest.send(body)
35 | } catch (err) {
36 | reject(errorConstants.REQUEST_FAILED(err))
37 | }
38 | })
39 | }
40 |
41 | export default httpRequest
42 |
--------------------------------------------------------------------------------
/src/utils/isRequired.js:
--------------------------------------------------------------------------------
1 | import { MISSING_PARAMETERS } from '../constants/error'
2 |
3 | /**
4 | * Used as the default parameter for an arguemnt that is considered required. It will
5 | * throw an error if the argument is not supplied by the user.
6 | * @param {string} name - The name of the missing argument.
7 | */
8 | const isRequired = name => {
9 | throw new Error(MISSING_PARAMETERS(name))
10 | }
11 |
12 | export default isRequired
13 |
--------------------------------------------------------------------------------
/tests/helpers/asyncMockResponse.js:
--------------------------------------------------------------------------------
1 | const asyncMockResponse = response =>
2 | new Promise(resolve => {
3 | setTimeout(() => {
4 | resolve(response)
5 | }, 200)
6 | })
7 |
8 | export default asyncMockResponse
9 |
--------------------------------------------------------------------------------
/tests/helpers/delaySecond.js:
--------------------------------------------------------------------------------
1 | const delaySecond = (seconds = 1) =>
2 | new Promise(resolve => {
3 | setTimeout(() => {
4 | resolve(true)
5 | }, 1000 * seconds)
6 | })
7 |
8 | export default delaySecond
9 |
--------------------------------------------------------------------------------
/tests/helpers/setUpContracts.js:
--------------------------------------------------------------------------------
1 | import BlockHashRNG from '../../src/contracts/implementations/RNG/BlockHashRNG'
2 | import MiniMePinakion from '../../src/contracts/implementations/PNK/MiniMePinakion'
3 | import TokenFactory from '../../src/contracts/implementations/PNK/TokenFactory'
4 | import KlerosPOC from '../../src/contracts/implementations/arbitrator/KlerosPOC'
5 | import MultipleArbitrableTransaction from '../../src/contracts/implementations/arbitrable/MultipleArbitrableTransaction'
6 |
7 | const setUpContracts = async (
8 | provider,
9 | klerosPOCParams,
10 | arbitrableContractParams
11 | ) => {
12 | // initialize RNG
13 | const rngContract = await BlockHashRNG.deploy(
14 | klerosPOCParams.account,
15 | provider
16 | )
17 | // minime token
18 | const tokenFactory = await TokenFactory.deploy(
19 | klerosPOCParams.account,
20 | provider
21 | )
22 | const pnkContract = await MiniMePinakion.deploy(
23 | klerosPOCParams.account,
24 | provider,
25 | tokenFactory.address
26 | )
27 |
28 | // initialize KlerosPOC
29 | const klerosCourt = await KlerosPOC.deploy(
30 | rngContract.address,
31 | pnkContract.address,
32 | klerosPOCParams.timesPerPeriod,
33 | klerosPOCParams.account,
34 | klerosPOCParams.value,
35 | provider
36 | )
37 | const pinakionPOC = new MiniMePinakion(provider, pnkContract.address)
38 | // transfer ownership
39 | await pinakionPOC.changeController(
40 | klerosCourt.address,
41 | klerosPOCParams.account
42 | )
43 |
44 | const contractArbitrableTransaction = await MultipleArbitrableTransaction.deploy(
45 | arbitrableContractParams.partyA,
46 | provider
47 | )
48 |
49 | return [
50 | klerosCourt.address,
51 | contractArbitrableTransaction.address,
52 | rngContract.address,
53 | pnkContract.address
54 | ]
55 | }
56 |
57 | export default setUpContracts
58 |
--------------------------------------------------------------------------------
/tests/helpers/waitNotifications.js:
--------------------------------------------------------------------------------
1 | const waitNotifications = (initialAmount = undefined, notificationCallback) => {
2 | let amount
3 | let currentAmount = 0
4 | let notificationList = []
5 | let resolver
6 | let promise = new Promise(resolve => {
7 | resolver = resolve
8 | })
9 | let callback = notification => {
10 | notificationCallback(notification)
11 | notificationList.push(notification)
12 | currentAmount += 1
13 | if (typeof amount !== 'undefined' && currentAmount >= amount)
14 | resolver(notificationList)
15 | }
16 | let setAmount = n => {
17 | amount = n
18 | if (currentAmount >= amount) resolver(notificationList)
19 | }
20 | if (typeof initialAmount !== 'undefined') setAmount(initialAmount)
21 |
22 | return { promise, callback, setAmount }
23 | }
24 |
25 | export default waitNotifications
26 |
--------------------------------------------------------------------------------
/tests/integration/EventListener.test.js:
--------------------------------------------------------------------------------
1 | import Web3 from 'web3'
2 |
3 | import KlerosPOC from '../../src/contracts/implementations/arbitrator/KlerosPOC'
4 | import EventListener from '../../src/utils/EventListener'
5 | import Kleros from '../../src/kleros'
6 | import * as ethConstants from '../../src/constants/eth'
7 | import setUpContracts from '../helpers/setUpContracts'
8 | import waitNotifications from '../helpers/waitNotifications'
9 | import delaySecond from '../helpers/delaySecond'
10 |
11 | describe('Event Listener', () => {
12 | let partyA
13 | let partyB
14 | let other
15 | let web3
16 | let eventCallback
17 | let eventLogs = []
18 | let klerosPOCData
19 | let arbitrableContractData
20 | let provider
21 |
22 | beforeAll(async () => {
23 | // use testRPC
24 | provider = await new Web3.providers.HttpProvider(
25 | ethConstants.LOCALHOST_ETH_PROVIDER
26 | )
27 |
28 | web3 = await new Web3(provider)
29 |
30 | partyA = web3.eth.accounts[1]
31 | partyB = web3.eth.accounts[2]
32 | other = web3.eth.accounts[5]
33 |
34 | klerosPOCData = {
35 | timesPerPeriod: [1, 1, 1, 1, 1],
36 | account: other,
37 | value: 0
38 | }
39 |
40 | arbitrableContractData = {
41 | partyA,
42 | partyB,
43 | value: 1,
44 | timeout: 1,
45 | extraData: '',
46 | title: 'test title',
47 | description: 'test description',
48 | email: 'test@test.test',
49 | metaEvidenceUri: 'https://test-meta-evidence.com'
50 | }
51 |
52 | eventCallback = log => {
53 | eventLogs.push(log)
54 | }
55 | })
56 |
57 | it(
58 | 'registers handler for event and successfully fires callback on log',
59 | async () => {
60 | const [
61 | klerosPOCAddress,
62 | arbitrableContractAddress,
63 | rngAddress,
64 | pnkAddress
65 | ] = await setUpContracts(provider, klerosPOCData, arbitrableContractData)
66 |
67 | expect(klerosPOCAddress).toBeDefined()
68 | expect(arbitrableContractAddress).toBeDefined()
69 | expect(rngAddress).toBeDefined()
70 | expect(pnkAddress).toBeDefined()
71 |
72 | const KlerosPOCInstance = new KlerosPOC(provider, klerosPOCAddress)
73 |
74 | const EventListenerInstance = new EventListener([KlerosPOCInstance])
75 | // set up callback
76 | let { promise: waitPromise, callback: waitCallback } = waitNotifications(
77 | 1,
78 | eventCallback
79 | )
80 | // add event handler
81 | const eventName = 'NewPeriod'
82 | EventListenerInstance.addEventHandler(
83 | KlerosPOCInstance,
84 | eventName,
85 | waitCallback
86 | )
87 | await EventListenerInstance.watchForEvents()
88 |
89 | await delaySecond()
90 | KlerosPOCInstance.passPeriod(other)
91 | // we will wait for 2 seconds for promise to resolve or else throw
92 | let throwError = true
93 | setTimeout(() => {
94 | if (throwError) {
95 | EventListenerInstance.stopWatchingForEvents(KlerosPOCInstance)
96 | throw new Error('Callback Promise did not resolve')
97 | }
98 | }, 1000 * 2)
99 |
100 | await waitPromise
101 | throwError = false
102 |
103 | expect(eventLogs.length).toEqual(1)
104 | const log = eventLogs[0]
105 | expect(log.event).toEqual(eventName)
106 |
107 | EventListenerInstance.stopWatchingForEvents(KlerosPOCInstance)
108 | },
109 | 50000
110 | )
111 | it('can stop and remove contract instance', async () => {
112 | const [
113 | klerosPOCAddress,
114 | arbitrableContractAddress,
115 | rngAddress,
116 | pnkAddress
117 | ] = await setUpContracts(provider, klerosPOCData, arbitrableContractData)
118 |
119 | expect(klerosPOCAddress).toBeDefined()
120 | expect(arbitrableContractAddress).toBeDefined()
121 | expect(rngAddress).toBeDefined()
122 | expect(pnkAddress).toBeDefined()
123 |
124 | const KlerosPOCInstance = new KlerosPOC(provider, klerosPOCAddress)
125 |
126 | // add conract instance with event handler
127 | const EventListenerInstance = new EventListener([KlerosPOCInstance])
128 | EventListenerInstance.addEventHandler(
129 | KlerosPOCInstance,
130 | 'FakeEvent',
131 | () => {}
132 | )
133 | await EventListenerInstance.watchForEvents()
134 |
135 | EventListenerInstance.removeContractInstance(KlerosPOCInstance)
136 |
137 | expect(EventListenerInstance.contractInstances.length).toEqual(0)
138 | expect(
139 | EventListenerInstance.contractEventHandlerMap[KlerosPOCInstance]
140 | ).toBeUndefined()
141 | })
142 | it(
143 | 'registers handler for event, stops and starts again using only second handler from kleros',
144 | async () => {
145 | const [
146 | klerosPOCAddress,
147 | arbitrableContractAddress,
148 | rngAddress,
149 | pnkAddress
150 | ] = await setUpContracts(provider, klerosPOCData, arbitrableContractData)
151 |
152 | expect(klerosPOCAddress).toBeDefined()
153 | expect(arbitrableContractAddress).toBeDefined()
154 | expect(rngAddress).toBeDefined()
155 | expect(pnkAddress).toBeDefined()
156 |
157 | const KlerosInstance = new Kleros(provider, '', klerosPOCAddress)
158 |
159 | // start event watching
160 | await KlerosInstance.watchForEvents(partyA)
161 | // stop watching for events to clear store provider events and to reset contractInstances
162 | await KlerosInstance.stopWatchingForEvents()
163 | expect(KlerosInstance.eventListener.contractInstances.length).toEqual(0)
164 | expect(
165 | KlerosInstance.eventListener.contractEventHandlerMap[
166 | KlerosInstance.arbitrator
167 | ]
168 | ).toBeUndefined()
169 | expect(
170 | KlerosInstance.eventListener.watcherInstances[KlerosInstance.arbitrator]
171 | ).toBeUndefined()
172 | // add the contract back
173 | await KlerosInstance.eventListener.addContractImplementation(
174 | KlerosInstance.arbitrator
175 | )
176 | // set up callback
177 | let { promise: waitPromise, callback: waitCallback } = waitNotifications(
178 | 1,
179 | eventCallback
180 | )
181 | // add event handler
182 | const eventName = 'NewPeriod'
183 | KlerosInstance.eventListener.addEventHandler(
184 | KlerosInstance.arbitrator,
185 | eventName,
186 | waitCallback
187 | )
188 | // start watching for events again
189 | await KlerosInstance.eventListener.watchForEvents()
190 |
191 | await delaySecond()
192 | await KlerosInstance.arbitrator.passPeriod(other)
193 | // we will wait for 2 seconds for promise to resolve or else throw
194 | let throwError = true
195 | setTimeout(() => {
196 | if (throwError) {
197 | KlerosInstance.eventListener.stopWatchingForEvents(
198 | KlerosInstance.arbitrator
199 | )
200 | throw new Error('Callback Promise did not resolve')
201 | }
202 | }, 1000 * 2)
203 |
204 | await waitPromise
205 | throwError = false
206 |
207 | expect(
208 | eventLogs.filter(
209 | event =>
210 | event.address === KlerosInstance.arbitrator.getContractAddress()
211 | ).length
212 | ).toEqual(1)
213 |
214 | KlerosInstance.eventListener.stopWatchingForEvents(
215 | KlerosInstance.arbitrator
216 | )
217 | },
218 | 50000
219 | )
220 | })
221 |
--------------------------------------------------------------------------------
/tests/integration/contracts.test.js:
--------------------------------------------------------------------------------
1 | import Web3 from 'web3'
2 |
3 | import KlerosPOC from '../../src/contracts/implementations/arbitrator/KlerosPOC'
4 | import MultipleArbitrableTransaction from '../../src/contracts/implementations/arbitrable/MultipleArbitrableTransaction'
5 | import * as ethConstants from '../../src/constants/eth'
6 | import * as errorConstants from '../../src/constants/error'
7 | import setUpContracts from '../helpers/setUpContracts'
8 | import delaySecond from '../helpers/delaySecond'
9 |
10 | describe('Contracts', () => {
11 | let partyA
12 | let partyB
13 | let jurorContract1
14 | let jurorContract2
15 | let other
16 | let web3
17 | let klerosPOCData
18 | let arbitrableContractData
19 | let provider
20 |
21 | beforeAll(async () => {
22 | // use testRPC
23 | provider = await new Web3.providers.HttpProvider(
24 | ethConstants.LOCALHOST_ETH_PROVIDER
25 | )
26 |
27 | web3 = await new Web3(provider)
28 |
29 | partyA = web3.eth.accounts[0]
30 | partyB = web3.eth.accounts[1]
31 | other = web3.eth.accounts[2]
32 |
33 | // these accounts are not used
34 | jurorContract1 = web3.eth.accounts[11]
35 | jurorContract2 = web3.eth.accounts[12]
36 |
37 | klerosPOCData = {
38 | timesPerPeriod: [1, 1, 1, 1, 1], // activation, draw, vote, appeal, execution (seconds)
39 | account: other,
40 | value: 0
41 | }
42 |
43 | arbitrableContractData = {
44 | partyA,
45 | partyB,
46 | value: 0,
47 | timeout: 1,
48 | extraData: '',
49 | metaEvidenceUri: 'https://jsonplaceholder.typicode.com/posts/42', // assume this api is available and renders the content below
50 | metaEvidence: {
51 | userId: 5,
52 | id: 42,
53 | title: "commodi ullam sint et excepturi error explicabo praesentium voluptas",
54 | body: "odio fugit voluptatum ducimus earum autem est incidunt voluptatem\nodit reiciendis aliquam sunt sequi nulla dolorem\nnon facere repellendus voluptates quia\nratione harum vitae ut"
55 | }
56 | }
57 | })
58 |
59 | describe('KlerosPOC', async () => {
60 | it('deploys arbitrator with contractInstance', async () => {
61 | const newKlerosPOC = await KlerosPOC.deploy(
62 | '', // rngAddress param
63 | '', // pnkAddress param
64 | klerosPOCData.timesPerPeriod,
65 | klerosPOCData.account,
66 | klerosPOCData.value,
67 | provider
68 | )
69 |
70 | expect(newKlerosPOC.address).toBeTruthy()
71 |
72 | // Check that we can bootstrap with address
73 | const newKlerosInstance = new KlerosPOC(provider, newKlerosPOC.address)
74 | const contractInstance = await newKlerosInstance.loadContract()
75 | // this should load contract. Called in all KlerosPOC methods
76 | expect(contractInstance).toBeTruthy()
77 | expect(contractInstance.address).toEqual(newKlerosPOC.address)
78 | })
79 | it('initializing contract with bad address fails', async () => {
80 | const newKlerosPOC = new KlerosPOC(provider, 0x0) // bad address param
81 | try {
82 | // Check that we can bootstrap with address
83 | await newKlerosPOC.loadContract()
84 | } catch (err) {
85 | expect(err.message).toEqual(errorConstants.CONTRACT_INSTANCE_NOT_SET)
86 | }
87 | })
88 | it('setContractInstance throws with undefined parameters', async () => {
89 | const newKlerosPOC = await KlerosPOC.deploy(
90 | '', // rngAddress param
91 | '', // pnkAddress param
92 | klerosPOCData.timesPerPeriod,
93 | klerosPOCData.account,
94 | klerosPOCData.value,
95 | provider
96 | )
97 |
98 | expect(newKlerosPOC.address).toBeTruthy()
99 |
100 | // Check that we can bootstrap with address
101 | const newKlerosInstance = new KlerosPOC(provider, newKlerosPOC.address)
102 |
103 | try {
104 | await newKlerosInstance.setContractInstance()
105 | } catch (err) {
106 | expect(err.message).toEqual(errorConstants.UNABLE_TO_LOAD_CONTRACT)
107 | }
108 | })
109 | it('throws if we initialize KlerosPOC without an address', async () => {
110 | try {
111 | const _ = new KlerosPOC(provider)
112 | } catch (err) {
113 | expect(err.message).toEqual(
114 | errorConstants.MISSING_PARAMETERS('contractAddress')
115 | )
116 | }
117 | })
118 | })
119 |
120 | describe('ArbitrableTransaction', async () => {
121 | it(
122 | 'deploy a arbitrableTransaction contract',
123 | async () => {
124 | const [
125 | klerosPOCAddress,
126 | arbitrableContractAddress,
127 | rngAddress,
128 | pnkAddress
129 | ] = await setUpContracts(
130 | provider,
131 | klerosPOCData,
132 | arbitrableContractData
133 | )
134 | expect(klerosPOCAddress).toBeDefined()
135 | expect(arbitrableContractAddress).toBeDefined()
136 | expect(rngAddress).toBeDefined()
137 | expect(pnkAddress).toBeDefined()
138 |
139 | // KlerosPOC
140 | const KlerosPOCInstance = new KlerosPOC(provider, klerosPOCAddress)
141 | const klerosCourtData = await KlerosPOCInstance.getData()
142 | expect(klerosCourtData.pinakionContractAddress).toEqual(pnkAddress)
143 | expect(klerosCourtData.rngContractAddress).toEqual(rngAddress)
144 | expect(klerosCourtData.period).toEqual(0)
145 | expect(klerosCourtData.session).toEqual(1)
146 | },
147 | 10000
148 | )
149 | it(
150 | 'create a arbitrable transaction',
151 | async () => {
152 | const [
153 | klerosPOCAddress,
154 | arbitrableContractAddress
155 | ] = await setUpContracts(
156 | provider,
157 | klerosPOCData,
158 | arbitrableContractData
159 | )
160 |
161 | expect(klerosPOCAddress).toBeDefined()
162 | expect(arbitrableContractAddress).toBeDefined()
163 | // arbitrable contract
164 | const ArbitrableTransactionInstance = new MultipleArbitrableTransaction(
165 | provider,
166 | arbitrableContractAddress
167 | )
168 | await ArbitrableTransactionInstance.createArbitrableTransaction(
169 | arbitrableContractData.partyA,
170 | klerosPOCAddress,
171 | arbitrableContractData.partyB,
172 | arbitrableContractData.value,
173 | arbitrableContractData.timeout,
174 | arbitrableContractData.extraData,
175 | arbitrableContractData.metaEvidenceUri
176 | )
177 | const transactionArbitrable0 = await ArbitrableTransactionInstance.getData(
178 | 0
179 | )
180 |
181 | expect(transactionArbitrable0.seller).toEqual(arbitrableContractData.partyB)
182 | expect(transactionArbitrable0.buyer).toEqual(arbitrableContractData.partyA)
183 | expect(transactionArbitrable0.amount).toEqual(arbitrableContractData.value)
184 | expect(transactionArbitrable0.timeout).toEqual(arbitrableContractData.timeout)
185 | expect(transactionArbitrable0.disputeId).toEqual(0)
186 | expect(transactionArbitrable0.arbitrator).toEqual(klerosPOCAddress)
187 | expect(transactionArbitrable0.arbitratorExtraData).toEqual('0x')
188 | expect(transactionArbitrable0.sellerFee).toEqual(0)
189 | expect(transactionArbitrable0.buyerFee).toEqual(0)
190 | expect(transactionArbitrable0.lastInteraction).toBeDefined()
191 | expect(transactionArbitrable0.status).toEqual(0)
192 | expect(transactionArbitrable0.metaEvidence).toEqual(arbitrableContractData.metaEvidence)
193 | },
194 | 10000
195 | )
196 | it(
197 | 'Arbitrable Contract where partyA pays partyB',
198 | async () => {
199 | const [
200 | klerosPOCAddress,
201 | arbitrableContractAddress
202 | ] = await setUpContracts(
203 | provider,
204 | klerosPOCData,
205 | arbitrableContractData
206 | )
207 |
208 | expect(klerosPOCAddress).toBeDefined()
209 | expect(arbitrableContractAddress).toBeDefined()
210 |
211 | const ArbitrableTransactionInstance = new MultipleArbitrableTransaction(
212 | provider,
213 | arbitrableContractAddress
214 | )
215 |
216 | // create a arbitrable transaction
217 | await ArbitrableTransactionInstance.createArbitrableTransaction(
218 | arbitrableContractData.partyA,
219 | klerosPOCAddress,
220 | arbitrableContractData.partyB,
221 | arbitrableContractData.value,
222 | arbitrableContractData.timeout,
223 | arbitrableContractData.extraData,
224 | arbitrableContractData.metaEvidenceUri
225 | )
226 |
227 | // buyer pays the seller
228 | const transactionArbitrable0 = await ArbitrableTransactionInstance.pay(
229 | arbitrableContractData.partyA,
230 | 0,
231 | arbitrableContractData.value
232 | )
233 |
234 | expect(transactionArbitrable0.tx).toEqual(
235 | expect.stringMatching(/^0x[a-f0-9]{64}$/)
236 | ) // tx hash
237 | },
238 | 50000
239 | )
240 | it(
241 | 'dispute with a timeout call by the buyer',
242 | async () => {
243 | const [
244 | klerosPOCAddress,
245 | arbitrableContractAddress
246 | ] = await setUpContracts(
247 | provider,
248 | klerosPOCData,
249 | arbitrableContractData
250 | )
251 | expect(klerosPOCAddress).toBeDefined()
252 | expect(arbitrableContractAddress).toBeDefined()
253 |
254 | const ArbitrableTransactionInstance = new MultipleArbitrableTransaction(
255 | provider,
256 | arbitrableContractAddress
257 | )
258 |
259 | // create a arbitrable transaction
260 | await ArbitrableTransactionInstance.createArbitrableTransaction(
261 | arbitrableContractData.partyA,
262 | klerosPOCAddress,
263 | arbitrableContractData.partyB,
264 | arbitrableContractData.value,
265 | arbitrableContractData.timeout,
266 | arbitrableContractData.extraData,
267 | arbitrableContractData.metaEvidenceUri
268 | )
269 |
270 | const KlerosInstance = new KlerosPOC(provider, klerosPOCAddress)
271 | // return a bigint with the default value : 10000 wei fees in ether
272 | const arbitrationCost = await KlerosInstance.getArbitrationCost(
273 | arbitrableContractData.extraData
274 | )
275 |
276 | // buyer A pays fee
277 | const raiseDisputeByBuyerTxObj = await ArbitrableTransactionInstance.payArbitrationFeeByBuyer(
278 | arbitrableContractData.partyA,
279 | 0,
280 | arbitrationCost
281 | )
282 |
283 | expect(raiseDisputeByBuyerTxObj.tx).toEqual(
284 | expect.stringMatching(/^0x[a-f0-9]{64}$/)
285 | ) // tx hash
286 |
287 | await delaySecond(2)
288 | // call timeout by the buyer
289 | const txHashTimeOutByBuyer = await ArbitrableTransactionInstance.callTimeOutBuyer(
290 | arbitrableContractData.partyA,
291 | 0
292 | )
293 | expect(txHashTimeOutByBuyer.tx).toEqual(
294 | expect.stringMatching(/^0x[a-f0-9]{64}$/)
295 | ) // tx hash
296 | },
297 | 50000
298 | )
299 | it(
300 | 'dispute with an appeal call by the buyer',
301 | async () => {
302 | const [
303 | klerosPOCAddress,
304 | arbitrableContractAddress
305 | ] = await setUpContracts(
306 | provider,
307 | klerosPOCData,
308 | arbitrableContractData
309 | )
310 | await expect(klerosPOCAddress).toBeDefined()
311 | await expect(arbitrableContractAddress).toBeDefined()
312 |
313 | const ArbitrableTransactionInstance = new MultipleArbitrableTransaction(
314 | provider,
315 | arbitrableContractAddress
316 | )
317 |
318 | // create a arbitrable transaction
319 | await ArbitrableTransactionInstance.createArbitrableTransaction(
320 | arbitrableContractData.partyA,
321 | klerosPOCAddress,
322 | arbitrableContractData.partyB,
323 | arbitrableContractData.value,
324 | arbitrableContractData.timeout,
325 | arbitrableContractData.extraData,
326 | arbitrableContractData.metaEvidenceUri
327 | )
328 |
329 | const KlerosPOCInstance = await new KlerosPOC(provider, klerosPOCAddress)
330 |
331 | // // ****** Juror side (activate token) ****** //
332 |
333 | // jurors buy PNK
334 | const pnkAmount = '1000000000000000000'
335 | const buyPNKJurors = await Promise.all([
336 | await KlerosPOCInstance.buyPNK(pnkAmount, jurorContract1),
337 | await KlerosPOCInstance.buyPNK(pnkAmount, jurorContract2)
338 | ])
339 |
340 | const newBalance = await KlerosPOCInstance.getPNKBalance(jurorContract1)
341 |
342 | expect(newBalance.tokenBalance.toString()).toEqual(pnkAmount)
343 |
344 | // activate PNK jurors
345 | if (buyPNKJurors) {
346 | await KlerosPOCInstance.activatePNK(pnkAmount, jurorContract1)
347 | await KlerosPOCInstance.activatePNK(pnkAmount, jurorContract2)
348 | }
349 |
350 | // ****** Parties side (raise dispute) ****** //
351 |
352 | const KlerosInstance = new KlerosPOC(provider, klerosPOCAddress)
353 | // return a bigint with the default value : 10000 wei fees in ether
354 | const arbitrationCost = await KlerosInstance.getArbitrationCost(
355 | arbitrableContractData.extraData
356 | )
357 |
358 | // buyer A pays fee
359 | const raiseDisputeByBuyerTxObj = await ArbitrableTransactionInstance.payArbitrationFeeByBuyer(
360 | arbitrableContractData.partyA,
361 | 0,
362 | arbitrationCost
363 | )
364 |
365 | expect(raiseDisputeByBuyerTxObj.tx).toEqual(
366 | expect.stringMatching(/^0x[a-f0-9]{64}$/)
367 | ) // tx hash
368 |
369 | // seller pays fee
370 | const raiseDisputeBySellerTxObj = await ArbitrableTransactionInstance.payArbitrationFeeBySeller(
371 | arbitrableContractData.partyB,
372 | 0,
373 | arbitrationCost
374 | )
375 |
376 | expect(raiseDisputeBySellerTxObj.tx).toEqual(
377 | expect.stringMatching(/^0x[a-f0-9]{64}$/)
378 | ) // tx hash
379 |
380 | // ****** Juror side (pass period) ****** //
381 |
382 | let newPeriod
383 | // pass state so jurors are selected
384 | for (let i = 1; i < 3; i++) {
385 | // NOTE we need to make another block before we can generate the random number. Should not be an issue on main nets where avg block time < period length
386 | if (i === 2)
387 | web3.eth.sendTransaction({
388 | from: partyA,
389 | to: partyB,
390 | value: 10000,
391 | data: '0x'
392 | })
393 | await delaySecond()
394 | await KlerosPOCInstance.passPeriod(other)
395 |
396 | newPeriod = await KlerosPOCInstance.getPeriod()
397 | expect(newPeriod).toEqual(i)
398 | }
399 |
400 | let drawA = []
401 | let drawB = []
402 | for (let i = 1; i <= 3; i++) {
403 | if (
404 | await KlerosPOCInstance.isJurorDrawnForDispute(0, i, jurorContract1)
405 | ) {
406 | drawA.push(i)
407 | } else {
408 | drawB.push(i)
409 | }
410 | }
411 |
412 | const rulingJuror1 = 1 // vote for partyA
413 | await KlerosPOCInstance.submitVotes(
414 | 0,
415 | rulingJuror1,
416 | drawA,
417 | jurorContract1
418 | )
419 |
420 | const rulingJuror2 = 2 // vote for partyB
421 | await KlerosPOCInstance.submitVotes(
422 | 0,
423 | rulingJuror2,
424 | drawB,
425 | jurorContract2
426 | )
427 | await delaySecond()
428 | await KlerosPOCInstance.passPeriod(other)
429 |
430 | const currentRuling = await KlerosPOCInstance.currentRulingForDispute(0, 0)
431 | expect(currentRuling.toString()).toBeTruthy() // make sure the ruling exists
432 |
433 | const appealCost = await KlerosPOCInstance.getAppealCost(
434 | 0,
435 | arbitrableContractData.extraData
436 | )
437 |
438 | // raise appeal party A
439 | const raiseAppealByPartyATxObj = await ArbitrableTransactionInstance.appeal(
440 | partyA,
441 | 0,
442 | arbitrableContractData.extraData,
443 | appealCost
444 | )
445 | expect(raiseAppealByPartyATxObj.tx).toEqual(
446 | expect.stringMatching(/^0x[a-f0-9]{64}$/)
447 | ) // tx hash
448 |
449 | const dispute = await KlerosPOCInstance.getDispute(0)
450 | expect(dispute.numberOfAppeals).toEqual(1)
451 | },
452 | 50000
453 | )
454 | })
455 | })
456 |
--------------------------------------------------------------------------------
/tests/integration/disputeResolution.test.js:
--------------------------------------------------------------------------------
1 | import Web3 from 'web3'
2 |
3 | import KlerosPOC from '../../src/contracts/implementations/arbitrator/KlerosPOC'
4 | import MultipleArbitrableTransaction from '../../src/contracts/implementations/arbitrable/MultipleArbitrableTransaction'
5 | import Notifications from '../../src/resources/Notifications'
6 | import * as ethConstants from '../../src/constants/eth'
7 | import * as notificationConstants from '../../src/constants/notification'
8 | import setUpContracts from '../helpers/setUpContracts'
9 | import delaySecond from '../helpers/delaySecond'
10 |
11 | describe('Dispute Resolution', () => {
12 | let partyA
13 | let partyB
14 | let juror1
15 | let juror2
16 | let other
17 | let web3
18 | let klerosPOCData
19 | let arbitrableContractData
20 | let provider
21 |
22 | beforeAll(async () => {
23 | // use testRPC
24 | provider = await new Web3.providers.HttpProvider(
25 | ethConstants.LOCALHOST_ETH_PROVIDER
26 | )
27 |
28 | web3 = await new Web3(provider)
29 |
30 | partyA = web3.eth.accounts[6]
31 | partyB = web3.eth.accounts[7]
32 | juror1 = web3.eth.accounts[8]
33 | juror2 = web3.eth.accounts[9]
34 | other = web3.eth.accounts[10]
35 |
36 | klerosPOCData = {
37 | timesPerPeriod: [1, 1, 1, 1, 1],
38 | account: other,
39 | value: 0
40 | }
41 |
42 | arbitrableContractData = {
43 | partyA,
44 | partyB,
45 | value: '1000000000000000000',
46 | timeout: 1,
47 | extraData: '',
48 | metaEvidenceUri: 'https://my-meta-evidence.ipfs.io'
49 | }
50 | })
51 |
52 | it(
53 | 'KlerosPOC full dispute resolution flow',
54 | async () => {
55 | const [
56 | klerosPOCAddress,
57 | arbitrableContractAddress,
58 | rngAddress,
59 | pnkAddress
60 | ] = await setUpContracts(provider, klerosPOCData, arbitrableContractData)
61 | expect(klerosPOCAddress).toBeDefined()
62 | expect(arbitrableContractAddress).toBeDefined()
63 | expect(rngAddress).toBeDefined()
64 | expect(pnkAddress).toBeDefined()
65 |
66 | const KlerosPOCInstance = new KlerosPOC(provider, klerosPOCAddress)
67 | // arbitrable contract
68 | const ArbitrableTransactionInstance = new MultipleArbitrableTransaction(
69 | provider,
70 | arbitrableContractAddress
71 | )
72 | // Notifications instance for testing stateful notifications
73 | const NotificationsInstance = new Notifications(
74 | KlerosPOCInstance,
75 | ArbitrableTransactionInstance
76 | )
77 | // stateful notifications juror1
78 | let juror1StatefullNotifications = await NotificationsInstance.getStatefulNotifications(
79 | juror1,
80 | true
81 | )
82 | expect(juror1StatefullNotifications.length).toEqual(1)
83 | expect(juror1StatefullNotifications[0].notificationType).toEqual(
84 | notificationConstants.TYPE.CAN_ACTIVATE
85 | )
86 | // juror1 should have no balance to start with
87 | const initialBalance = await KlerosPOCInstance.getPNKBalance(juror1)
88 | expect(initialBalance.tokenBalance.toString()).toEqual('0')
89 | // buy 1 PNK juror1
90 | const amount = web3.toWei(web3.toBigNumber(1), 'ether')
91 | await KlerosPOCInstance.buyPNK(amount, juror1)
92 | const newBalance = await KlerosPOCInstance.getPNKBalance(juror1)
93 |
94 | expect(newBalance.tokenBalance.toString()).toEqual(amount.toString())
95 | // buy PNK for juror2
96 | await KlerosPOCInstance.buyPNK(amount, juror2)
97 |
98 | // activate PNK juror1
99 | const activatedTokenAmount = amount.div(2)
100 | const balance = await KlerosPOCInstance.activatePNK(
101 | activatedTokenAmount,
102 | juror1
103 | )
104 | expect(balance.tokenBalance.toString()).toEqual(amount.toString())
105 | expect(balance.activatedTokens.toString()).toEqual(
106 | activatedTokenAmount.toString()
107 | )
108 |
109 | // stateful notifications juror1
110 | juror1StatefullNotifications = await NotificationsInstance.getStatefulNotifications(
111 | juror1,
112 | true
113 | )
114 | expect(juror1StatefullNotifications.length).toEqual(0)
115 | // activate PNK juror2
116 | await KlerosPOCInstance.activatePNK(activatedTokenAmount, juror2)
117 |
118 | // load klerosPOC
119 | const klerosPOCInstance = await KlerosPOCInstance.loadContract()
120 |
121 | const juror1Data = await klerosPOCInstance.jurors(juror1)
122 | expect(juror1Data[2].toNumber()).toEqual(
123 | (await klerosPOCInstance.session()).toNumber()
124 | )
125 | expect(juror1Data[4].minus(juror1Data[3]).toString()).toEqual(
126 | activatedTokenAmount.toString()
127 | )
128 | // return a bigint
129 | // FIXME use arbitrableTransaction
130 | await ArbitrableTransactionInstance.createArbitrableTransaction(
131 | arbitrableContractData.partyA,
132 | klerosPOCAddress,
133 | arbitrableContractData.partyB,
134 | arbitrableContractData.value,
135 | arbitrableContractData.timeout,
136 | arbitrableContractData.extraData,
137 | arbitrableContractData.metaEvidenceUri
138 | )
139 | // return a bigint with the default value : 10000 wei fees in ether
140 | const arbitrationCost = await KlerosPOCInstance.getArbitrationCost(
141 | arbitrableContractData.extraData
142 | )
143 |
144 | // buyer pays arbitration fee
145 | const raiseDisputeByBuyerTxObj = await ArbitrableTransactionInstance.payArbitrationFeeByBuyer(
146 | arbitrableContractData.partyA,
147 | 0,
148 | arbitrationCost
149 | )
150 |
151 | expect(raiseDisputeByBuyerTxObj.tx).toEqual(
152 | expect.stringMatching(/^0x[a-f0-9]{64}$/)
153 | ) // tx hash
154 |
155 | // seller pays fee to raise a dispute
156 | const raiseDisputeBySellerTxObj = await ArbitrableTransactionInstance.payArbitrationFeeBySeller(
157 | arbitrableContractData.partyB,
158 | 0,
159 | arbitrationCost
160 | )
161 |
162 | expect(raiseDisputeBySellerTxObj.tx).toEqual(
163 | expect.stringMatching(/^0x[a-f0-9]{64}$/)
164 | ) // tx hash
165 |
166 | const dispute = await KlerosPOCInstance.getDispute(0, true)
167 | expect(dispute.arbitrableContractAddress).toEqual(
168 | arbitrableContractAddress
169 | )
170 | expect(dispute.firstSession).toEqual(
171 | (await klerosPOCInstance.session()).toNumber()
172 | )
173 | expect(dispute.numberOfAppeals).toEqual(0)
174 | expect(dispute.voteCounters).toEqual(
175 | new Array(dispute.numberOfAppeals + 1).fill(
176 | new Array(dispute.rulingChoices + 1).fill(0)
177 | )
178 | )
179 |
180 | // add an evidence for the buyer
181 | const txHashAddEvidence = await ArbitrableTransactionInstance.submitEvidence(
182 | arbitrableContractData.partyA,
183 | 0, // arbitrable transaction id
184 | 'https://my-evidence.ipfs.io' // evidence url
185 | )
186 | expect(txHashAddEvidence).toEqual(
187 | expect.stringMatching(/^0x[a-f0-9]{64}$/)
188 | ) // tx hash
189 |
190 | // check initial state of contract
191 | // FIXME var must be more explicit
192 | const initialState = await KlerosPOCInstance.getData()
193 | expect(initialState.session).toEqual(1)
194 | expect(initialState.period).toEqual(0)
195 |
196 | let newPeriod
197 | // pass state so jurors are selected
198 | for (let i = 1; i < 3; i++) {
199 | // NOTE we need to make another block before we can generate the random number. Should not be an issue on main nets where avg block time < period length
200 | if (i === 2)
201 | await web3.eth.sendTransaction({
202 | from: partyA,
203 | to: partyB,
204 | value: 10000,
205 | data: '0x'
206 | })
207 | await delaySecond()
208 | await KlerosPOCInstance.passPeriod(other)
209 |
210 | newPeriod = await KlerosPOCInstance.getPeriod()
211 | expect(newPeriod).toEqual(i)
212 | }
213 |
214 | // check deadline timestamp
215 | const deadline = await KlerosPOCInstance.getDisputeDeadlineTimestamp(1)
216 | expect(deadline).toBeTruthy()
217 |
218 | // check deadline timestamp
219 | const noAppeal = await KlerosPOCInstance.getAppealRuledAtTimestamp(1)
220 | expect(noAppeal).toBeFalsy()
221 |
222 | let drawA = []
223 | let drawB = []
224 | for (let i = 1; i <= 3; i++) {
225 | if (await KlerosPOCInstance.isJurorDrawnForDispute(0, i, juror1)) {
226 | drawA.push(i)
227 | } else if (
228 | await KlerosPOCInstance.isJurorDrawnForDispute(0, i, juror2)
229 | ) {
230 | drawB.push(i)
231 | }
232 | }
233 | expect(drawA.length + drawB.length).toEqual(3)
234 | const disputesForJuror1 = await KlerosPOCInstance.getDisputesForJuror(
235 | juror1
236 | )
237 | const disputesForJuror2 = await KlerosPOCInstance.getDisputesForJuror(
238 | juror2
239 | )
240 | expect(
241 | disputesForJuror1.length > 0 || disputesForJuror2.length > 0
242 | ).toBeTruthy()
243 |
244 | const disputeForJuror =
245 | disputesForJuror1.length > 0
246 | ? disputesForJuror1[0]
247 | : disputesForJuror2[0]
248 | expect(disputeForJuror.arbitrableContractAddress).toEqual(
249 | arbitrableContractAddress
250 | )
251 |
252 | const jurorForNotifications =
253 | drawA.length > drawB.length ? juror1 : juror2
254 | // stateful notifications juror1
255 | let jurorStatefullNotifications = await NotificationsInstance.getStatefulNotifications(
256 | jurorForNotifications,
257 | true
258 | )
259 | expect(jurorStatefullNotifications.length).toEqual(1)
260 | expect(jurorStatefullNotifications[0].notificationType).toEqual(
261 | notificationConstants.TYPE.CAN_VOTE
262 | )
263 |
264 | // submit rulings
265 | const rulingJuror1 = 1
266 | await KlerosPOCInstance.submitVotes(0, rulingJuror1, drawA, juror1)
267 | const rulingJuror2 = 2
268 | await KlerosPOCInstance.submitVotes(0, rulingJuror2, drawB, juror2)
269 | const winningRuling =
270 | drawA.length > drawB.length ? rulingJuror1 : rulingJuror2
271 |
272 | await delaySecond()
273 | await KlerosPOCInstance.passPeriod(other)
274 |
275 | const currentRuling = await klerosPOCInstance.currentRuling(0)
276 | expect(`${currentRuling}`).toEqual(`${winningRuling}`)
277 |
278 | await delaySecond()
279 | await KlerosPOCInstance.passPeriod(other)
280 |
281 | // check ruled at timestamp
282 | const ruledAt = await KlerosPOCInstance.getAppealRuledAtTimestamp(1)
283 | expect(ruledAt).toBeTruthy()
284 |
285 | // stateful notifications
286 | jurorStatefullNotifications = await NotificationsInstance.getStatefulNotifications(
287 | jurorForNotifications,
288 | true
289 | )
290 | expect(jurorStatefullNotifications.length).toEqual(1)
291 | expect(jurorStatefullNotifications[0].notificationType).toEqual(
292 | notificationConstants.TYPE.CAN_REPARTITION
293 | )
294 | // balances before ruling is executed
295 | const partyABalance = web3.eth.getBalance(partyA)
296 | const partyBBalance = web3.eth.getBalance(partyB)
297 | // repartition tokens
298 | await KlerosPOCInstance.repartitionJurorTokens(0, other)
299 |
300 | // stateful notifications
301 | jurorStatefullNotifications = await NotificationsInstance.getStatefulNotifications(
302 | jurorForNotifications,
303 | true
304 | )
305 | expect(jurorStatefullNotifications.length).toEqual(1)
306 | expect(jurorStatefullNotifications[0].notificationType).toEqual(
307 | notificationConstants.TYPE.CAN_EXECUTE
308 | )
309 | // execute ruling
310 | await KlerosPOCInstance.executeRuling(0, other)
311 |
312 | juror1StatefullNotifications = await NotificationsInstance.getStatefulNotifications(
313 | juror1,
314 | true
315 | )
316 | expect(juror1StatefullNotifications.length).toEqual(0)
317 | let partyAStatefullNotifications = await NotificationsInstance.getStatefulNotifications(
318 | partyA,
319 | false
320 | )
321 | expect(partyAStatefullNotifications.length).toEqual(0)
322 | // balances after ruling
323 | // partyA wins so they should recieve their arbitration fee as well as the value locked in contract
324 | if (winningRuling === rulingJuror1) {
325 | expect(
326 | web3.eth
327 | .getBalance(partyA)
328 | .minus(partyABalance)
329 | .toString()
330 | ).toEqual(
331 | arbitrationCost
332 | .plus(web3.toBigNumber(arbitrableContractData.value))
333 | .toString()
334 | )
335 | // partyB lost so their balance should remain the same
336 | expect(web3.eth.getBalance(partyB).toString()).toEqual(
337 | partyBBalance.toString()
338 | )
339 | } else {
340 | expect(
341 | web3.eth
342 | .getBalance(partyB)
343 | .minus(partyBBalance)
344 | .toString()
345 | ).toEqual(
346 | arbitrationCost
347 | .plus(web3.toBigNumber(arbitrableContractData.value))
348 | .toString()
349 | )
350 | // partyB lost so their balance should remain the same
351 | expect(web3.eth.getBalance(partyA).toString()).toEqual(
352 | partyABalance.toString()
353 | )
354 | }
355 | const netPNK = await KlerosPOCInstance.getNetTokensForDispute(0, partyA)
356 |
357 | const updatedTransactionArbitrable0Data = await ArbitrableTransactionInstance.getData(0)
358 | expect(updatedTransactionArbitrable0Data.status).toEqual(4) // Resolved status expected
359 | },
360 | 100000
361 | )
362 | })
363 |
--------------------------------------------------------------------------------
/tests/integration/kleros.test.js:
--------------------------------------------------------------------------------
1 | import Web3 from 'web3'
2 |
3 | import Kleros from '../../src/kleros'
4 | import * as ethConstants from '../../src/constants/eth'
5 |
6 | describe('Kleros', () => {
7 | it('can be created', () => {
8 | const mockStoreUri = ''
9 | const ethProvider = new Web3.providers.HttpProvider(
10 | ethConstants.LOCALHOST_ETH_PROVIDER
11 | )
12 | const mockArbitrator = '0x0'
13 | const mockArbitrable = '0x1'
14 |
15 | const klerosInstance = new Kleros(
16 | ethProvider,
17 | mockStoreUri,
18 | mockArbitrator,
19 | mockArbitrable
20 | )
21 |
22 | expect(klerosInstance.arbitrator).toBeTruthy()
23 | expect(klerosInstance.arbitrable).toBeTruthy()
24 | expect(klerosInstance.disputes).toBeTruthy()
25 | expect(klerosInstance.notifications).toBeTruthy()
26 |
27 | expect(klerosInstance.arbitrator.getContractAddress()).toEqual(
28 | mockArbitrator
29 | )
30 | expect(klerosInstance.arbitrable.getContractAddress()).toEqual(
31 | mockArbitrable
32 | )
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/tests/unit/contracts/abstractions/Arbitrator.test.js:
--------------------------------------------------------------------------------
1 | import ArbitratorApi from '../../../../src/contracts/abstractions/Arbitrator'
2 | import _asyncMockResponse from '../../../helpers/asyncMockResponse'
3 |
4 | describe('Arbitrator', () => {
5 | let arbitratorAddress = '0xDcB2db3E3fA7a6cba5dFE964408099d860246D7Z'
6 | let account = '0x'
7 | let arbitratorInstance
8 |
9 | beforeEach(async () => {
10 | arbitratorInstance = new ArbitratorApi({}, {})
11 | })
12 |
13 | describe('getDisputesForUser', async () => {
14 | it('has wrong period', async () => {
15 | const mockGetDisputesForUser = jest.fn()
16 | const mockShouldNotCall = jest.fn()
17 | const mockDispute = {
18 | arbitratorAddress: arbitratorAddress,
19 | disputeID: '1'
20 | }
21 | const mockStoreProvider = {
22 | getDisputes: mockGetDisputesForUser.mockReturnValue(
23 | _asyncMockResponse([mockDispute])
24 | ),
25 | newUserProfile: mockShouldNotCall
26 | }
27 |
28 | arbitratorInstance.setStoreProviderInstance(mockStoreProvider)
29 |
30 | const mockArbitrator = {
31 | getPeriod: jest.fn().mockReturnValue(_asyncMockResponse(0)),
32 | getSession: jest.fn().mockReturnValue(_asyncMockResponse(1)),
33 | getDispute: jest.fn().mockReturnValue(_asyncMockResponse(mockDispute)),
34 | getContractAddress: jest.fn().mockReturnValue(arbitratorAddress)
35 | }
36 | arbitratorInstance._contractImplementation = mockArbitrator
37 |
38 | const disputes = await arbitratorInstance.getDisputesForUser(
39 | account,
40 | false
41 | )
42 |
43 | expect(disputes.length).toBe(1)
44 | expect(disputes[0]).toEqual(mockDispute)
45 | expect(mockGetDisputesForUser.mock.calls.length).toBe(1)
46 | expect(mockShouldNotCall.mock.calls.length).toBe(0)
47 | })
48 |
49 | it('has different arbitrator', async () => {
50 | const mockGetDisputesForUser = jest.fn()
51 | const mockShouldNotCall = jest.fn()
52 | const mockDispute = {
53 | arbitratorAddress: arbitratorAddress,
54 | disputeID: '1'
55 | }
56 | const mockStoreProvider = {
57 | getDisputes: mockGetDisputesForUser.mockReturnValue(
58 | _asyncMockResponse([mockDispute])
59 | ),
60 | newUserProfile: mockShouldNotCall
61 | }
62 |
63 | arbitratorInstance.setStoreProviderInstance(mockStoreProvider)
64 |
65 | const mockArbitrator = {
66 | getPeriod: jest.fn().mockReturnValue(_asyncMockResponse(0)),
67 | getSession: jest.fn().mockReturnValue(_asyncMockResponse(1)),
68 | getDispute: jest.fn().mockReturnValue(_asyncMockResponse(mockDispute)),
69 | getContractAddress: jest.fn().mockReturnValue(arbitratorAddress + 'x')
70 | }
71 | arbitratorInstance._contractImplementation = mockArbitrator
72 |
73 | const disputes = await arbitratorInstance.getDisputesForUser(
74 | account,
75 | false
76 | )
77 |
78 | expect(disputes.length).toBe(0)
79 | expect(mockGetDisputesForUser.mock.calls.length).toBe(1)
80 | expect(mockShouldNotCall.mock.calls.length).toBe(0)
81 | })
82 |
83 | it('has wrong session. already updated store', async () => {
84 | const mockGetDisputesForUser = jest.fn()
85 | const mockSetUpUserProfile = jest.fn()
86 | const mockShouldNotCall = jest.fn()
87 | const mockDispute = {
88 | arbitratorAddress: arbitratorAddress,
89 | disputeID: '1'
90 | }
91 | const mockStoreProvider = {
92 | getDisputes: mockGetDisputesForUser.mockReturnValue(
93 | _asyncMockResponse([mockDispute])
94 | ),
95 | newUserProfile: mockSetUpUserProfile.mockReturnValue(
96 | _asyncMockResponse({
97 | session: 1
98 | })
99 | )
100 | }
101 |
102 | arbitratorInstance.setStoreProviderInstance(mockStoreProvider)
103 |
104 | const mockArbitrator = {
105 | getPeriod: jest.fn().mockReturnValue(_asyncMockResponse(2)),
106 | getSession: jest.fn().mockReturnValue(_asyncMockResponse(1)),
107 | getDispute: jest.fn().mockReturnValue(_asyncMockResponse(mockDispute)),
108 | getContractAddress: jest.fn().mockReturnValue(arbitratorAddress)
109 | }
110 | arbitratorInstance._contractImplementation = mockArbitrator
111 |
112 | const disputes = await arbitratorInstance.getDisputesForUser(
113 | account,
114 | false
115 | )
116 |
117 | expect(disputes.length).toBe(1)
118 | expect(disputes[0]).toEqual(mockDispute)
119 | expect(mockGetDisputesForUser.mock.calls.length).toBe(1)
120 | expect(mockSetUpUserProfile.mock.calls.length).toBe(1)
121 | expect(mockShouldNotCall.mock.calls.length).toBe(0)
122 | })
123 |
124 | it('has new disputes', async () => {
125 | const mockGetDisputesForUser = jest.fn()
126 | const mockSetUpUserProfile = jest.fn()
127 | const mockGetDisputesForJuror = jest.fn()
128 | const mockUpdateDisputeProfile = jest.fn()
129 | const mockUpdateSession = jest.fn()
130 | const mockAddNewDrawsDisputeProfile = jest.fn()
131 | const mockDispute = {
132 | arbitratorAddress: arbitratorAddress,
133 | disputeID: '1',
134 | appealDraws: [[1]],
135 | numberOfAppeals: 0
136 | }
137 | const mockStoreProvider = {
138 | getDisputes: mockGetDisputesForUser.mockReturnValue(
139 | _asyncMockResponse([mockDispute])
140 | ),
141 | newUserProfile: mockSetUpUserProfile.mockReturnValue(
142 | _asyncMockResponse({
143 | session: 1
144 | })
145 | ),
146 | updateDisputeProfile: mockUpdateDisputeProfile,
147 | updateUserSession: mockUpdateSession,
148 | addNewDrawsDisputeProfile: mockAddNewDrawsDisputeProfile
149 | }
150 |
151 | arbitratorInstance.setStoreProviderInstance(mockStoreProvider)
152 |
153 | const mockArbitrator = {
154 | getPeriod: jest.fn().mockReturnValue(_asyncMockResponse(2)),
155 | getSession: jest.fn().mockReturnValue(_asyncMockResponse(2)),
156 | getDispute: jest.fn().mockReturnValue(_asyncMockResponse(mockDispute)),
157 | getDisputesForJuror: mockGetDisputesForJuror.mockReturnValue(
158 | _asyncMockResponse([mockDispute])
159 | ),
160 | getDisputeCreationEvent: jest.fn().mockReturnValue({ blockNumber: 1 }),
161 | getContractAddress: jest.fn().mockReturnValue(arbitratorAddress)
162 | }
163 | arbitratorInstance._contractImplementation = mockArbitrator
164 |
165 | const disputes = await arbitratorInstance.getDisputesForUser(
166 | account,
167 | false
168 | )
169 |
170 | expect(disputes.length).toBe(1)
171 | expect(disputes[0]).toEqual(mockDispute)
172 | expect(mockGetDisputesForUser.mock.calls.length).toBe(1)
173 |
174 | expect(mockSetUpUserProfile.mock.calls.length).toBe(1)
175 | expect(mockSetUpUserProfile.mock.calls[0][0]).toBe(account)
176 |
177 | expect(mockGetDisputesForJuror.mock.calls.length).toBe(1)
178 | expect(mockGetDisputesForJuror.mock.calls[0][0]).toBe(account)
179 |
180 | expect(mockUpdateDisputeProfile.mock.calls.length).toBe(1)
181 | expect(mockUpdateDisputeProfile.mock.calls[0][0]).toBe(account)
182 | expect(mockUpdateDisputeProfile.mock.calls[0][1]).toBe(arbitratorAddress)
183 | expect(mockUpdateDisputeProfile.mock.calls[0][2]).toBe(
184 | mockDispute.disputeID
185 | )
186 | expect(mockUpdateDisputeProfile.mock.calls[0][3]).toEqual({
187 | blockNumber: 1
188 | })
189 | expect(mockAddNewDrawsDisputeProfile.mock.calls.length).toBe(1)
190 | expect(mockUpdateSession.mock.calls.length).toBe(1)
191 | })
192 | })
193 | })
194 |
--------------------------------------------------------------------------------
/tests/unit/resources/Disputes.test.js:
--------------------------------------------------------------------------------
1 | import Web3 from 'web3'
2 |
3 | import DisputesApi from '../../../src/resources/Disputes'
4 | import _asyncMockResponse from '../../helpers/asyncMockResponse'
5 |
6 | describe('Disputes', () => {
7 | let mockArbitratorWrapper = {}
8 | let mockArbitrableContractWrapper = {}
9 | let arbitratorAddress = '0xDcB2db3E3fA7a6cba5dFE964408099d860246D7Z'
10 | let arbitrableConctractAddress = '0xEcB2db3E3fA7a6cba5dFE964408099d860246D7Z'
11 | let account = '0x'
12 | let disputesInstance
13 | let web3 = new Web3()
14 |
15 | beforeEach(async () => {
16 | disputesInstance = new DisputesApi(
17 | mockArbitratorWrapper,
18 | mockArbitrableContractWrapper,
19 | {},
20 | {}
21 | )
22 |
23 | mockArbitratorWrapper = {
24 | address: arbitratorAddress
25 | }
26 |
27 | mockArbitrableContractWrapper = {
28 | address: arbitrableConctractAddress
29 | }
30 |
31 | disputesInstance.setArbitrator = mockArbitratorWrapper
32 | disputesInstance.setArbitrable = mockArbitrableContractWrapper
33 | })
34 |
35 | describe('getDataForDispute', async () => {
36 | it('gets data for new dispute (no store data)', async () => {
37 | const disputeID = 0
38 | const arbitrableContractAddress = '0xfakeaddress'
39 | const session = 1
40 | const period = 0
41 | const numberOfAppeals = 0
42 | const rulingChoices = [0, 1]
43 | const initialNumberJurors = 3
44 | const arbitrationFeePerJuror = web3.toBigNumber(web3.toWei(0.15, 'ether'))
45 | const voteCounters = []
46 | const partyA = '0x0'
47 | const partyB = '0x1'
48 | const appealDeadlines = 1
49 | const appealRuledAt = 2
50 | const appealCreatedAt = 3
51 |
52 | const mockArbitratorGetDispute = jest.fn().mockReturnValue(
53 | _asyncMockResponse({
54 | arbitratorAddress,
55 | disputeID,
56 | arbitrableContractAddress,
57 | firstSession: session,
58 | numberOfAppeals,
59 | rulingChoices,
60 | initialNumberJurors,
61 | arbitrationFeePerJuror,
62 | state: 0,
63 | voteCounters,
64 | status: 0
65 | })
66 | )
67 | const mockArbitrator = {
68 | getVoteForJuror: jest.fn().mockReturnValue([0, 1]),
69 | getDispute: mockArbitratorGetDispute,
70 | getPeriod: jest.fn().mockReturnValue(period),
71 | getSession: jest.fn().mockReturnValue(session),
72 | currentRulingForDispute: jest.fn().mockReturnValue(0),
73 | canRuleDispute: jest.fn().mockReturnValue(false),
74 | getContractAddress: jest.fn().mockReturnValue(arbitratorAddress),
75 | getDisputeCreationEvent: jest.fn().mockReturnValue({ blockNumber: 1 }),
76 | getNetTokensForDispute: jest.fn().mockReturnValue(0),
77 | getAppealRuledAtTimestamp: jest.fn().mockReturnValue(appealRuledAt),
78 | getDisputeDeadlineTimestamp: jest.fn().mockReturnValue(appealDeadlines),
79 | getAppealCreationTimestamp: jest.fn().mockReturnValue(appealCreatedAt),
80 | _getTimestampForBlock: jest.fn().mockReturnValue(appealCreatedAt / 1000)
81 | }
82 | disputesInstance._ArbitratorInstance = mockArbitrator
83 |
84 | const mockArbitrableContractData = {
85 | metaEvidence: {
86 | title: 'test'
87 | }
88 | }
89 | const mockArbitrableContract = {
90 | getParties: jest.fn().mockReturnValue({ partyA, partyB }),
91 | getMetaEvidence: jest
92 | .fn()
93 | .mockReturnValue(mockArbitrableContractData.metaEvidence),
94 | getEvidence: jest.fn().mockReturnValue([]),
95 | setContractInstance: jest.fn()
96 | }
97 | disputesInstance._ArbitrableInstance = mockArbitrableContract
98 |
99 | const mockContract = {
100 | description: 'testdesc',
101 | email: 'testemail@test.com'
102 | }
103 | const mockStoreProvider = {
104 | getContractByAddress: jest.fn().mockReturnValue(mockContract)
105 | }
106 | disputesInstance.setStoreProviderInstance(mockStoreProvider)
107 |
108 | const disputeData = await disputesInstance.getDataForDispute(
109 | disputeID,
110 | account
111 | )
112 |
113 | expect(disputeData).toBeTruthy()
114 | expect(disputeData.arbitrableContractAddress).toEqual(
115 | arbitrableContractAddress
116 | )
117 | expect(disputeData.arbitratorAddress).toEqual(arbitratorAddress)
118 | expect(disputeData.parties.partyA).toEqual(partyA)
119 | expect(disputeData.parties.partyB).toEqual(partyB)
120 | expect(disputeData.disputeID).toEqual(disputeID)
121 | expect(disputeData.firstSession).toEqual(session)
122 | expect(disputeData.lastSession).toEqual(session)
123 | expect(disputeData.numberOfAppeals).toEqual(numberOfAppeals)
124 | expect(disputeData.disputeState).toEqual(0)
125 | expect(disputeData.disputeStatus).toEqual(0)
126 | expect(disputeData.appealJuror.length).toBe(1)
127 | expect(disputeData.appealRulings.length).toBe(1)
128 | expect(disputeData.evidence).toEqual([])
129 | expect(disputeData.netPNK).toEqual(0)
130 | expect(disputeData.metaEvidence).toEqual(
131 | mockArbitrableContractData.metaEvidence
132 | )
133 | })
134 | it('gets data active dispute', async () => {
135 | const disputeID = 0
136 | const arbitrableContractAddress = '0xfakeaddress'
137 | const session = 1
138 | const period = 3
139 | const numberOfAppeals = 0
140 | const rulingChoices = [0, 1]
141 | const initialNumberJurors = 3
142 | const arbitrationFeePerJuror = web3.toBigNumber(web3.toWei(0.15, 'ether'))
143 | const voteCounters = [[2, 4]]
144 | const partyA = '0x0'
145 | const partyB = '0x1'
146 | const appealDeadlines = 1
147 | const appealRuledAt = null
148 | const appealCreatedAt = 3
149 |
150 | const mockArbitratorGetDispute = jest.fn().mockReturnValue(
151 | _asyncMockResponse({
152 | arbitratorAddress,
153 | disputeID,
154 | arbitrableContractAddress,
155 | firstSession: session,
156 | numberOfAppeals,
157 | rulingChoices,
158 | initialNumberJurors,
159 | arbitrationFeePerJuror,
160 | state: 0,
161 | voteCounters,
162 | status: 0
163 | })
164 | )
165 | const mockArbitrator = {
166 | getVoteForJuror: jest.fn().mockReturnValue([0, 1]),
167 | getDispute: mockArbitratorGetDispute,
168 | getPeriod: jest.fn().mockReturnValue(period),
169 | getSession: jest.fn().mockReturnValue(session),
170 | currentRulingForDispute: jest.fn().mockReturnValue(1),
171 | canRuleDispute: jest.fn().mockReturnValue(true),
172 | getContractAddress: jest.fn().mockReturnValue(arbitratorAddress),
173 | getDisputeCreationEvent: jest.fn().mockReturnValue({ blockNumber: 1 }),
174 | getNetTokensForDispute: jest.fn().mockReturnValue(0),
175 | getAppealRuledAtTimestamp: jest.fn().mockReturnValue(appealRuledAt),
176 | getDisputeDeadlineTimestamp: jest.fn().mockReturnValue(appealDeadlines),
177 | getAppealCreationTimestamp: jest.fn().mockReturnValue(appealCreatedAt),
178 | _getTimestampForBlock: jest.fn().mockReturnValue(appealCreatedAt / 1000)
179 | }
180 | disputesInstance._ArbitratorInstance = mockArbitrator
181 |
182 | const mockArbitrableContractData = {
183 | metaEvidence: {
184 | title: 'test'
185 | }
186 | }
187 | const mockArbitrableContract = {
188 | getParties: jest.fn().mockReturnValue({ partyA, partyB }),
189 | getMetaEvidence: jest
190 | .fn()
191 | .mockReturnValue(mockArbitrableContractData.metaEvidence),
192 | getEvidence: jest.fn().mockReturnValue([]),
193 | setContractInstance: jest.fn()
194 | }
195 | disputesInstance._ArbitrableInstance = mockArbitrableContract
196 |
197 | const mockContract = {
198 | description: 'testdesc',
199 | email: 'testemail@test.com'
200 | }
201 | const mockUserData = {
202 | appealDraws: [[1, 2]]
203 | }
204 | const mockStoreProvider = {
205 | getContractByAddress: jest.fn().mockReturnValue(mockContract),
206 | getDispute: jest.fn().mockReturnValue(mockUserData)
207 | }
208 | disputesInstance.setStoreProviderInstance(mockStoreProvider)
209 |
210 | const disputeData = await disputesInstance.getDataForDispute(
211 | disputeID,
212 | account
213 | )
214 |
215 | expect(disputeData).toBeTruthy()
216 | expect(disputeData.arbitrableContractAddress).toEqual(
217 | arbitrableContractAddress
218 | )
219 | expect(disputeData.arbitratorAddress).toEqual(arbitratorAddress)
220 | expect(disputeData.parties.partyA).toEqual(partyA)
221 | expect(disputeData.parties.partyB).toEqual(partyB)
222 | expect(disputeData.disputeID).toEqual(disputeID)
223 | expect(disputeData.firstSession).toEqual(session)
224 | expect(disputeData.lastSession).toEqual(session)
225 | expect(disputeData.numberOfAppeals).toEqual(numberOfAppeals)
226 | expect(disputeData.disputeState).toEqual(0)
227 | expect(disputeData.disputeStatus).toEqual(0)
228 |
229 | expect(disputeData.appealJuror.length).toBe(1)
230 | const jurorData = disputeData.appealJuror[0]
231 | expect(jurorData.fee).toEqual(
232 | arbitrationFeePerJuror.mul(
233 | mockUserData.appealDraws[numberOfAppeals].length
234 | )
235 | )
236 | expect(jurorData.draws).toEqual(mockUserData.appealDraws[numberOfAppeals])
237 | expect(jurorData.canRule).toBeTruthy()
238 |
239 | expect(disputeData.appealRulings.length).toBe(1)
240 | const appealData = disputeData.appealRulings[0]
241 | expect(appealData.voteCounter).toEqual(voteCounters[numberOfAppeals])
242 | expect(appealData.ruledAt).toBeFalsy()
243 | expect(appealData.deadline).toEqual(appealDeadlines)
244 | expect(appealData.ruling).toEqual(2)
245 | expect(appealData.canRepartition).toBeFalsy()
246 | expect(appealData.canExecute).toBeFalsy()
247 |
248 | expect(disputeData.evidence).toEqual([])
249 | expect(disputeData.netPNK).toEqual(0)
250 | expect(disputeData.metaEvidence).toEqual(
251 | mockArbitrableContractData.metaEvidence
252 | )
253 | })
254 | it('gets data ruled on dispute -- can repartition', async () => {
255 | const disputeID = 0
256 | const arbitrableContractAddress = '0xfakeaddress'
257 | const session = 1
258 | const period = 4
259 | const numberOfAppeals = 0
260 | const rulingChoices = [0, 1]
261 | const initialNumberJurors = 3
262 | const arbitrationFeePerJuror = web3.toBigNumber(web3.toWei(0.15, 'ether'))
263 | const voteCounters = [[4, 5]]
264 | const partyA = '0x0'
265 | const partyB = '0x1'
266 | const appealDeadlines = 1
267 | const appealRuledAt = 2
268 | const appealCreatedAt = 3
269 |
270 | const mockArbitratorGetDispute = jest.fn().mockReturnValue(
271 | _asyncMockResponse({
272 | arbitratorAddress,
273 | disputeID,
274 | arbitrableContractAddress,
275 | firstSession: session,
276 | numberOfAppeals,
277 | rulingChoices,
278 | initialNumberJurors,
279 | arbitrationFeePerJuror,
280 | state: 0,
281 | voteCounters,
282 | status: 3
283 | })
284 | )
285 | const mockArbitrator = {
286 | getVoteForJuror: jest.fn().mockReturnValue([0, 1]),
287 | getDispute: mockArbitratorGetDispute,
288 | getPeriod: jest.fn().mockReturnValue(period),
289 | getSession: jest.fn().mockReturnValue(session),
290 | currentRulingForDispute: jest.fn().mockReturnValue(1),
291 | canRuleDispute: jest.fn().mockReturnValue(false),
292 | getContractAddress: jest.fn().mockReturnValue(arbitratorAddress),
293 | getDisputeCreationEvent: jest.fn().mockReturnValue({ blockNumber: 1 }),
294 | getNetTokensForDispute: jest.fn().mockReturnValue(0),
295 | getAppealRuledAtTimestamp: jest.fn().mockReturnValue(appealRuledAt),
296 | getDisputeDeadlineTimestamp: jest.fn().mockReturnValue(appealDeadlines),
297 | getAppealCreationTimestamp: jest.fn().mockReturnValue(appealCreatedAt),
298 | _getTimestampForBlock: jest.fn().mockReturnValue(appealCreatedAt / 1000)
299 | }
300 | disputesInstance._ArbitratorInstance = mockArbitrator
301 |
302 | const mockArbitrableContractData = {
303 | metaEvidence: {
304 | title: 'test'
305 | }
306 | }
307 | const mockArbitrableContract = {
308 | getParties: jest.fn().mockReturnValue({ partyA, partyB }),
309 | getMetaEvidence: jest
310 | .fn()
311 | .mockReturnValue(mockArbitrableContractData.metaEvidence),
312 | getEvidence: jest.fn().mockReturnValue([]),
313 | setContractInstance: jest.fn()
314 | }
315 | disputesInstance._ArbitrableInstance = mockArbitrableContract
316 |
317 | const mockContract = {
318 | description: 'testdesc',
319 | email: 'testemail@test.com'
320 | }
321 | const mockUserData = {
322 | appealDraws: [[1, 2]],
323 | appealCreatedAt: [123],
324 | appealDeadlines: [456],
325 | appealRuledAt: [789],
326 | netPNK: 2
327 | }
328 | const mockStoreProvider = {
329 | getContractByAddress: jest.fn().mockReturnValue(mockContract),
330 | getDispute: jest.fn().mockReturnValue(mockUserData)
331 | }
332 | disputesInstance.setStoreProviderInstance(mockStoreProvider)
333 |
334 | const disputeData = await disputesInstance.getDataForDispute(
335 | disputeID,
336 | account
337 | )
338 |
339 | expect(disputeData).toBeTruthy()
340 | expect(disputeData.arbitrableContractAddress).toEqual(
341 | arbitrableContractAddress
342 | )
343 | expect(disputeData.arbitratorAddress).toEqual(arbitratorAddress)
344 | expect(disputeData.parties.partyA).toEqual(partyA)
345 | expect(disputeData.parties.partyB).toEqual(partyB)
346 | expect(disputeData.disputeID).toEqual(disputeID)
347 | expect(disputeData.firstSession).toEqual(session)
348 | expect(disputeData.lastSession).toEqual(session)
349 | expect(disputeData.numberOfAppeals).toEqual(numberOfAppeals)
350 | expect(disputeData.disputeState).toEqual(0)
351 | expect(disputeData.disputeStatus).toEqual(3)
352 |
353 | expect(disputeData.appealJuror.length).toBe(1)
354 | const jurorData = disputeData.appealJuror[0]
355 | expect(jurorData.fee).toEqual(
356 | arbitrationFeePerJuror.mul(
357 | mockUserData.appealDraws[numberOfAppeals].length
358 | )
359 | )
360 | expect(jurorData.draws).toEqual(mockUserData.appealDraws[numberOfAppeals])
361 | expect(jurorData.canRule).toBeFalsy()
362 |
363 | expect(disputeData.appealRulings.length).toBe(1)
364 | const appealData = disputeData.appealRulings[0]
365 | expect(appealData.voteCounter).toEqual(voteCounters[numberOfAppeals])
366 | expect(appealData.ruling).toEqual(2)
367 | expect(appealData.canRepartition).toBeTruthy()
368 | expect(appealData.canExecute).toBeFalsy()
369 | expect(disputeData.evidence).toEqual([])
370 | })
371 | it('gets data ruled on dispute -- can execute', async () => {
372 | const disputeID = 0
373 | const arbitrableContractAddress = '0xfakeaddress'
374 | const session = 1
375 | const period = 4
376 | const numberOfAppeals = 0
377 | const rulingChoices = [0, 1]
378 | const initialNumberJurors = 3
379 | const arbitrationFeePerJuror = web3.toBigNumber(web3.toWei(0.15, 'ether'))
380 | const voteCounters = [[4, 5]]
381 | const partyA = '0x0'
382 | const partyB = '0x1'
383 | const appealDeadlines = 1
384 | const appealRuledAt = 2
385 | const appealCreatedAt = 3
386 |
387 | const mockArbitratorGetDispute = jest.fn().mockReturnValue(
388 | _asyncMockResponse({
389 | arbitratorAddress,
390 | disputeID,
391 | arbitrableContractAddress,
392 | firstSession: session,
393 | numberOfAppeals,
394 | rulingChoices,
395 | initialNumberJurors,
396 | arbitrationFeePerJuror,
397 | state: 2,
398 | voteCounters,
399 | status: 3
400 | })
401 | )
402 | const mockArbitrator = {
403 | getVoteForJuror: jest.fn().mockReturnValue([0, 1]),
404 | getDispute: mockArbitratorGetDispute,
405 | getPeriod: jest.fn().mockReturnValue(period),
406 | getSession: jest.fn().mockReturnValue(session),
407 | currentRulingForDispute: jest.fn().mockReturnValue(1),
408 | canRuleDispute: jest.fn().mockReturnValue(false),
409 | getContractAddress: jest.fn().mockReturnValue(arbitratorAddress),
410 | getDisputeCreationEvent: jest.fn().mockReturnValue({ blockNumber: 1 }),
411 | getNetTokensForDispute: jest.fn().mockReturnValue(0),
412 | getAppealRuledAtTimestamp: jest.fn().mockReturnValue(appealRuledAt),
413 | getDisputeDeadlineTimestamp: jest.fn().mockReturnValue(appealDeadlines),
414 | getAppealCreationTimestamp: jest.fn().mockReturnValue(appealCreatedAt),
415 | _getTimestampForBlock: jest.fn().mockReturnValue(appealCreatedAt / 1000)
416 | }
417 | disputesInstance._ArbitratorInstance = mockArbitrator
418 |
419 | const mockArbitrableContractData = {
420 | metaEvidence: {
421 | title: 'test'
422 | }
423 | }
424 | const mockArbitrableContract = {
425 | getParties: jest.fn().mockReturnValue({ partyA, partyB }),
426 | getMetaEvidence: jest
427 | .fn()
428 | .mockReturnValue(mockArbitrableContractData.metaEvidence),
429 | getEvidence: jest.fn().mockReturnValue([]),
430 | setContractInstance: jest.fn()
431 | }
432 | disputesInstance._ArbitrableInstance = mockArbitrableContract
433 |
434 | const mockContract = {
435 | description: 'testdesc',
436 | email: 'testemail@test.com'
437 | }
438 | const mockUserData = {
439 | appealDraws: [[1, 2]],
440 | appealCreatedAt: [123],
441 | appealDeadlines: [456],
442 | appealRuledAt: [789],
443 | lastBlock: 1
444 | }
445 | const mockStoreProvider = {
446 | getContractByAddress: jest.fn().mockReturnValue(mockContract),
447 | getDispute: jest.fn().mockReturnValue(mockUserData)
448 | }
449 | disputesInstance.setStoreProviderInstance(mockStoreProvider)
450 |
451 | const disputeData = await disputesInstance.getDataForDispute(
452 | disputeID,
453 | account
454 | )
455 |
456 | expect(disputeData).toBeTruthy()
457 | expect(disputeData.arbitrableContractAddress).toEqual(
458 | arbitrableContractAddress
459 | )
460 | expect(disputeData.arbitratorAddress).toEqual(arbitratorAddress)
461 | expect(disputeData.disputeID).toEqual(disputeID)
462 | expect(disputeData.firstSession).toEqual(session)
463 | expect(disputeData.lastSession).toEqual(session)
464 | expect(disputeData.numberOfAppeals).toEqual(numberOfAppeals)
465 | expect(disputeData.disputeState).toEqual(2)
466 | expect(disputeData.disputeStatus).toEqual(3)
467 |
468 | expect(disputeData.appealJuror.length).toBe(1)
469 | const jurorData = disputeData.appealJuror[0]
470 | expect(jurorData.fee).toEqual(
471 | arbitrationFeePerJuror.mul(
472 | mockUserData.appealDraws[numberOfAppeals].length
473 | )
474 | )
475 | expect(jurorData.draws).toEqual(mockUserData.appealDraws[numberOfAppeals])
476 | expect(jurorData.canRule).toBeFalsy()
477 |
478 | expect(disputeData.appealRulings.length).toBe(1)
479 | const appealData = disputeData.appealRulings[0]
480 | expect(appealData.voteCounter).toEqual(voteCounters[numberOfAppeals])
481 | expect(appealData.ruling).toEqual(2)
482 | expect(appealData.canRepartition).toBeFalsy()
483 | expect(appealData.canExecute).toBeTruthy()
484 | expect(disputeData.evidence).toEqual([])
485 | expect(disputeData.metaEvidence).toEqual(
486 | mockArbitrableContractData.metaEvidence
487 | )
488 | })
489 | })
490 | })
491 |
--------------------------------------------------------------------------------
/tests/unit/utils/PromiseQueue.test.js:
--------------------------------------------------------------------------------
1 | import PromiseQueue from '../../../src/utils/PromiseQueue'
2 | import delaySecond from '../../helpers/delaySecond'
3 |
4 | describe('PromiseQueue', () => {
5 | let promiseQueue
6 |
7 | beforeEach(() => {
8 | promiseQueue = new PromiseQueue()
9 | })
10 |
11 | it('queue keeps promises in order', async () => {
12 | const resolvedValues = []
13 |
14 | const promiseA = () =>
15 | new Promise(resolve => {
16 | setTimeout(() => {
17 | resolvedValues.push(1)
18 | resolve()
19 | }, 500)
20 | })
21 |
22 | const promiseB = () =>
23 | new Promise(resolve => {
24 | setTimeout(() => {
25 | resolvedValues.push(2)
26 | resolve()
27 | }, 100)
28 | })
29 |
30 | promiseQueue.push(promiseA)
31 | promiseQueue.push(promiseB)
32 |
33 | await delaySecond(1)
34 | expect(resolvedValues).toEqual([1, 2])
35 | })
36 |
37 | it('get return value from promise in queue', async () => {
38 | const resolvedValues = []
39 |
40 | const promiseA = () =>
41 | new Promise(resolve => {
42 | setTimeout(() => {
43 | resolvedValues.push(1)
44 | resolve(1)
45 | }, 100)
46 | })
47 |
48 | const promiseB = () =>
49 | new Promise(resolve => {
50 | setTimeout(() => {
51 | resolvedValues.push(2)
52 | resolve(2)
53 | }, 500)
54 | })
55 |
56 | const promiseC = () =>
57 | new Promise(resolve => {
58 | setTimeout(() => {
59 | resolvedValues.push(3)
60 | resolve(3)
61 | }, 100)
62 | })
63 |
64 | promiseQueue.push(promiseA)
65 | const result = await promiseQueue.fetch(promiseB)
66 | promiseQueue.push(promiseC)
67 | await delaySecond(2)
68 | expect(result).toEqual(2)
69 | expect(resolvedValues).toEqual([1, 2, 3])
70 | })
71 | })
72 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path')
2 |
3 | module.exports = {
4 | entry: './src/index.js',
5 | output: {
6 | path: resolve(__dirname, 'umd/'),
7 | filename: 'kleros-api.js',
8 | libraryTarget: 'umd',
9 | umdNamedDefine: true,
10 | library: 'KlerosAPI'
11 | },
12 |
13 | devtool: 'source-map',
14 |
15 | module: {
16 | rules: [
17 | {
18 | test: /\.js$/,
19 | loader: 'babel-loader',
20 | exclude: /node_modules/
21 | }
22 | ]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------