├── src
├── redux
│ ├── actions
│ │ ├── .gitkeep
│ │ ├── alert-actions.js
│ │ ├── abi
│ │ │ ├── positionRegistryAbi.js
│ │ │ └── etherSignalAbi.js
│ │ ├── utils
│ │ │ └── getSignalPerBlock.js
│ │ ├── connection-actions.js
│ │ └── position-actions.js
│ └── reducers
│ │ ├── index.js
│ │ ├── alert-reducer.js
│ │ ├── connection-reducer.js
│ │ └── position-reducer.js
├── styles
│ ├── atoms
│ │ ├── .gitkeep
│ │ └── LoadingAnimation.css
│ ├── ecosystems
│ │ ├── .gitkeep
│ │ ├── Alerts.css
│ │ └── PositionSubmitter.css
│ ├── environments
│ │ ├── .gitkeep
│ │ ├── Home.css
│ │ └── Frame.css
│ ├── molecules
│ │ ├── .gitkeep
│ │ └── PositionListItem.css
│ └── organisms
│ │ ├── .gitkeep
│ │ ├── NetworkStatus.css
│ │ ├── Alert.css
│ │ ├── AccountSelector.css
│ │ ├── PositionFilter.css
│ │ └── PositionPagination.css
├── components
│ ├── atoms
│ │ ├── .gitkeep
│ │ └── LoadingAnimation.js
│ ├── molecules
│ │ ├── .gitkeep
│ │ ├── PositionDepositInput.js
│ │ └── PositionListItem.js
│ ├── organisms
│ │ ├── .gitkeep
│ │ ├── Alert.js
│ │ ├── PositionList.js
│ │ ├── AccountSelector.js
│ │ ├── PositionPagination.js
│ │ ├── SignalChart.js
│ │ ├── PositionDepositModal.js
│ │ ├── NetworkStatus.js
│ │ ├── PositionSubmitterForm.js
│ │ └── PositionFilter.js
│ ├── ecosystems
│ │ ├── .gitkeep
│ │ ├── Alerts.js
│ │ ├── PositionSubmitter.js
│ │ ├── PositionSubmitterModal.js
│ │ └── Positions.js
│ └── environments
│ │ ├── .gitkeep
│ │ ├── About.js
│ │ ├── Home.js
│ │ ├── Frame.js
│ │ └── CliQuickstart.js
├── images
│ ├── ajax.gif
│ └── logo.svg
├── index.css
└── index.js
├── config
├── flow
│ ├── css.js.flow
│ └── file.js.flow
├── babel.dev.js
├── babel.prod.js
├── eslint.js
├── webpack.config.dev.js
└── webpack.config.prod.js
├── .gitignore
├── favicon.ico
├── .eslintrc.json
├── cli
├── test1.js
├── readmev2.txt
└── ethersignal2.js
├── README.md
├── index.html
├── scripts
├── openChrome.applescript
├── build.js
└── start.js
├── contracts
└── ethersignal2.sol
└── package.json
/src/redux/actions/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/styles/atoms/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/atoms/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/molecules/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/organisms/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/styles/ecosystems/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/styles/environments/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/styles/environments/Home.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/styles/molecules/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/styles/organisms/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/config/flow/css.js.flow:
--------------------------------------------------------------------------------
1 | // @flow
2 |
--------------------------------------------------------------------------------
/src/components/ecosystems/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/environments/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_STORE
2 | build
3 | .npm-debug.log
4 | node_modules
5 |
--------------------------------------------------------------------------------
/config/flow/file.js.flow:
--------------------------------------------------------------------------------
1 | // @flow
2 | declare export default string;
3 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vulcanize/ethersignal/HEAD/favicon.ico
--------------------------------------------------------------------------------
/src/images/ajax.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vulcanize/ethersignal/HEAD/src/images/ajax.gif
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/src/styles/atoms/LoadingAnimation.css:
--------------------------------------------------------------------------------
1 | .loading-animation {
2 | text-align: center;
3 | }
4 |
--------------------------------------------------------------------------------
/src/styles/organisms/NetworkStatus.css:
--------------------------------------------------------------------------------
1 | samp.network-status {
2 | font-size: .8em;
3 | color: #666;
4 | }
5 |
--------------------------------------------------------------------------------
/src/styles/organisms/Alert.css:
--------------------------------------------------------------------------------
1 | .alert {
2 |
3 | }
4 |
5 | .alert:last-of-type {
6 | margin-bottom: 0;
7 | }
8 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "trails/react",
3 | "parserOptions": {
4 | "sourceType": "module"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/styles/ecosystems/Alerts.css:
--------------------------------------------------------------------------------
1 | .alerts {
2 | position: fixed;
3 | bottom: 0;
4 | left: 0;
5 | width: 100%;
6 | padding: .5em;
7 | z-index: 5;
8 | }
9 |
--------------------------------------------------------------------------------
/src/styles/organisms/AccountSelector.css:
--------------------------------------------------------------------------------
1 | .account-selector {
2 | margin-bottom: .5em;
3 | }
4 |
5 | .account-selector .control-label {
6 | margin-right: .5em;
7 | }
8 |
--------------------------------------------------------------------------------
/cli/test1.js:
--------------------------------------------------------------------------------
1 | function test1_voteSpam() {
2 | for (i = 0; i < 100; i++) {
3 | ethersignal.setSignal(0, ((i % 2) == 0) ? true : false, {from: web3.eth.accounts[0], gas: 300000});
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/styles/ecosystems/PositionSubmitter.css:
--------------------------------------------------------------------------------
1 | .position-submitter {
2 | text-align: center;
3 | }
4 |
5 | .position-submitter-logo {
6 | margin-right: .125em;
7 | max-height: 60px;
8 | }
9 |
--------------------------------------------------------------------------------
/src/redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import positions from './position-reducer'
4 | import connection from './connection-reducer'
5 | import alerts from './alert-reducer'
6 |
7 | export default combineReducers({
8 | positions,
9 | connection,
10 | alerts
11 | })
12 |
--------------------------------------------------------------------------------
/src/styles/organisms/PositionFilter.css:
--------------------------------------------------------------------------------
1 | .position-filter {
2 | display: flex;
3 | align-items: center;
4 | margin: 0 -.5em;
5 | }
6 |
7 | .position-filter > div {
8 | margin: 0 .5em;
9 | }
10 |
11 | .position-filter .control-label {
12 | margin-right: .5em;
13 | }
14 |
15 | .position-filter .icon {
16 | margin-right: .5em;
17 | }
18 |
--------------------------------------------------------------------------------
/src/styles/environments/Frame.css:
--------------------------------------------------------------------------------
1 | .navbar .navbar-brand {
2 |
3 | }
4 |
5 | .app-header-logo {
6 | max-width: 17px;
7 | }
8 |
9 | .navbar .navbar-brand > img {
10 | display: inline;
11 | margin-right: .125em;
12 | }
13 |
14 | .navbar .navbar-brand a {
15 | color: #777;
16 | }
17 |
18 | .navbar .navbar-brand a:hover {
19 | color: #555;
20 | text-decoration: none;
21 | }
22 |
--------------------------------------------------------------------------------
/src/styles/organisms/PositionPagination.css:
--------------------------------------------------------------------------------
1 | .position-pagination {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | margin: 0 -1em;
6 | }
7 |
8 | .position-pagination > div {
9 | margin: 0 1em;
10 | }
11 |
12 | .position-pagination ul.pagination {
13 | margin: 0;
14 | }
15 |
16 | .position-pagination .control-label {
17 | margin-right: .5em;
18 | }
19 |
--------------------------------------------------------------------------------
/config/babel.dev.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | cacheDirectory: true,
3 | presets: [
4 | 'babel-preset-es2015',
5 | 'babel-preset-es2016',
6 | 'babel-preset-react'
7 | ].map(require.resolve),
8 | plugins: [
9 | 'babel-plugin-syntax-trailing-function-commas',
10 | 'babel-plugin-transform-class-properties',
11 | 'babel-plugin-transform-object-rest-spread'
12 | ].map(require.resolve)
13 | };
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ether Signal
2 |
3 | A React implementation of https://github.com/vulcanize/ethersignal
4 |
5 | ## Setting up development server
6 | From terminal:
7 | ```js
8 | git clone git@github.com:langateam/ethersignal.git && cd ethersignal
9 | npm install
10 | npm start
11 | ```
12 | Open localhost:8080 in MIST.
13 |
14 | ### Contract on TestNet
15 | http://testnet.etherscan.io/address/0x9e75993a7a9b9f92a1978bcc15c30cbcb967bc81#code
16 |
--------------------------------------------------------------------------------
/config/babel.prod.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | 'babel-preset-es2015',
4 | 'babel-preset-es2016',
5 | 'babel-preset-react'
6 | ].map(require.resolve),
7 | plugins: [
8 | 'babel-plugin-syntax-trailing-function-commas',
9 | 'babel-plugin-transform-class-properties',
10 | 'babel-plugin-transform-object-rest-spread',
11 | 'babel-plugin-transform-react-constant-elements'
12 | ].map(require.resolve)
13 | };
14 |
--------------------------------------------------------------------------------
/src/components/atoms/LoadingAnimation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import loading from './../../images/ajax.gif'
3 |
4 | import './../../styles/atoms/LoadingAnimation.css'
5 |
6 | class LoadingAnimation extends Component {
7 |
8 | render() {
9 | return (
10 |
8 | EtherSignal CLI
9 | Quick Start.
10 |
11 |
12 |
13 | 1. Launch a geth node if it is not already running.
14 |
geth
15 |
16 |
17 |
18 | 2. Attach via the geth command line client
19 |
geth attach
20 |
21 |
22 |
23 | 3. Load the ethersignal script.
24 |
25 | > loadScript("ethersignal2.js"){'\n'}
26 | true
27 |
28 |
29 |
30 |
31 |
32 |
33 | Now you can either signal on a position, tally the current signal levels
34 | for a position, list the registered positions, or register a position:
35 |
36 |
37 | To list the registered positions run the following:
38 |
39 | > ListPositions() {'\n'}
40 | [Positions: cut & paste the CalcSignal(); portion to see current signal levels]{'\n'}
41 | {'\n'}
42 | {'\n'}
43 | Position CalcSignal("0x953521cfe06b48d65b64ae864abb4c808312885e", 1290010);{'\n'}
44 | registered by 0x8c2741b9bebd3c27feb7bb3356f7b04652977b78{'\n'}
45 | eth deposit: 0{'\n'}
46 | Title: will this work{'\n'}
47 | Text: will this contract factory work{'\n'}
48 | {'\n'}
49 | Position CalcSignal("0xcdda0a8fe9a7a844c9d8611b2cadfe36b4bb438f", 1290020);{'\n'}
50 | registered by 0x8c2741b9bebd3c27feb7bb3356f7b04652977b78{'\n'}
51 | eth deposit: 0{'\n'}
52 | Title: title{'\n'}
53 | Text: text{'\n'}
54 | Positions filtered for being under the minDeposit of 0: 0{'\n'}
55 | true{'\n'}
56 |
57 |
58 |
59 |
60 |
61 | If you would like to filter based on the position desposit, pass a
62 | parameter to ListPositions() as follows:
63 |
64 | > ListPositions(1){'\n'}
65 | [Positions: cut & paste the CalcSignal(); portion to see current signal levels]{'\n'}
66 | Positions filtered for being under the minDeposit of 1: 2{'\n'}
67 | true{'\n'}
68 |
69 |
70 |
71 |
72 |
73 | As you can see above, in order to get the current signal levels for a position
74 | you can simply cut and paste the CalcSignal(); portion of the output from:
75 |
76 | ListPositions():{'\n'}
77 | > CalcSignal("0x953521cfe06b48d65b64ae864abb4c808312885e", 1290010);{'\n'}
78 | {'{'}{'\n'}
79 | {' '}against: 0,{'\n'}
80 | {' '}pro: 167.12471268213704{'\n'}
81 | {'}'}{'\n'}
82 |
83 |
84 |
85 |
86 |
87 | In order to register a position you can use the following contract method:
88 |
89 | > positionregistry.registerPosition("title", "text",
90 | {'{'}from: web3.eth.accounts[0], gas: 300000{'}'});
91 |
92 |
93 |
94 |
95 |
96 | If you would like to optionally submit a deposit into your position
97 | in order to distinguish it from others you can do the following (note
98 | your deposit will be returned when you withdraw the position):
99 |
100 | > web3.eth.sendTransaction({'{'}from: web3.eth.accounts[0],
101 | to:"0xcdda0a8fe9a7a844c9d8611b2cadfe36b4bb438f", value: web3.toWei(0.1, "ether"){'}'})
102 |
103 |
104 |
105 |
106 | You may withdraw you position and reclaim your deposit as follows:
107 |
> WithdrawPosition("0xcdda0a8fe9a7a844c9d8611b2cadfe36b4bb438f");
108 |
109 |
110 |
111 | In order to vote on a position, you will need to use the positions
112 | signal address. Take the following signal as an example:
113 |
114 | Position CalcSignal("0xcdda0a8fe9a7a844c9d8611b2cadfe36b4bb438f", 1290020);{'\n'}
115 | registered by 0x8c2741b9bebd3c27feb7bb3356f7b04652977b78{'\n'}
116 | eth deposit: 0{'\n'}
117 | Title: title{'\n'}
118 | Text: text{'\n'}
119 |
120 |
121 |
122 |
123 | The signal address is what is within CalcSignal(); so above it is
124 | "0xcdda0a8fe9a7a844c9d8611b2cadfe36b4bb438f". To vote simply run the
125 | following command, where true means to vote for the signal, and false
126 | would mean to vote against the signal:
127 |
> SetSignal("0xcdda0a8fe9a7a844c9d8611b2cadfe36b4bb438f", true);
128 |
129 |
130 | Enjoy.
131 |
132 |
133 | )
134 | }
135 |
136 | }
137 |
138 | CliQuickstart.propTypes = {}
139 |
140 | export default CliQuickstart
141 |
--------------------------------------------------------------------------------
/src/redux/reducers/position-reducer.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | import {
4 | FETCH_POSITIONS_REQUEST,
5 | FETCH_POSITIONS_SUCCESS,
6 | FETCH_POSITIONS_FAILURE,
7 | SHOW_NEW_POSITION_MODAL,
8 | HIDE_NEW_POSITION_MODAL,
9 | SET_NEW_POSITION_TITLE,
10 | SET_NEW_POSITION_DESCRIPTION,
11 | SET_NEW_POSITION_TITLE_VALIDATION_ERROR,
12 | SUBMIT_NEW_POSITION_SUCCESS,
13 | SUBMIT_NEW_POSITION_FAILURE,
14 | SET_POSITION_ORDER_BY,
15 | SET_POSITION_MINIMUM_VALUE_FILTER,
16 | SET_POSITION_MINIMUM_VALUE_DENOMINATION,
17 | SET_POSITION_PAGINATION_ITEMS_TO_DISPLAY,
18 | SET_POSITION_PAGINATION_CURRENT_PAGE,
19 | SET_POSITION_PAGINATION_NUMBER_OF_PAGES,
20 | DISPLAY_POSITION_DEPOSIT_MODAL,
21 | HIDE_POSITION_DEPOSIT_MODAL,
22 | SET_POSITION_DEPOSIT_VALUE,
23 | SET_POSITION_DEPOSIT_VALIDATION_ERROR,
24 | SET_POSITION_DEPOSIT_DENOMINATION
25 | } from './../actions/position-actions'
26 |
27 | const initialState = {
28 | showModal: false,
29 | fetching: false,
30 | error: '',
31 | items: [],
32 | sort: {
33 | orderBy: 'absoluteSignal',
34 | direction: 'desc'
35 | },
36 | filter: {
37 | minimumValue: '',
38 | denomination: 'Ether'
39 | },
40 | pagination: {
41 | itemsToDisplay: 5,
42 | currentPage: 1
43 | },
44 | newPosition: {
45 | title: '',
46 | description: '',
47 | titleValidationError: ''
48 | },
49 | depositModal: {
50 | showModal: false,
51 | senderAddr: '',
52 | recipientAddr: '',
53 | value: '',
54 | valueValidationError: '',
55 | denomination: 'Finney'
56 | }
57 | }
58 |
59 | export default function positionReducer(state = initialState, action) {
60 |
61 | switch (action.type) {
62 |
63 | case DISPLAY_POSITION_DEPOSIT_MODAL:
64 | return Object.assign({}, state, {
65 | depositModal: Object.assign({}, state.depositModal, {
66 | showModal: true,
67 | senderAddr: action.senderAddr,
68 | recipientAddr: action.recipientAddr
69 | })
70 | })
71 |
72 | case HIDE_POSITION_DEPOSIT_MODAL:
73 | return Object.assign({}, state, {
74 | depositModal: Object.assign({}, initialState.depositModal)
75 | })
76 |
77 | case SET_POSITION_DEPOSIT_VALUE:
78 | return Object.assign({}, state, {
79 | depositModal: Object.assign({}, state.depositModal, {
80 | value: action.value,
81 | valueValidationError: ''
82 | })
83 | })
84 |
85 | case SET_POSITION_DEPOSIT_VALIDATION_ERROR:
86 | return Object.assign({}, state, {
87 | depositModal: Object.assign({}, state.depositModal, {
88 | valueValidationError: action.error
89 | })
90 | })
91 |
92 | case SET_POSITION_DEPOSIT_DENOMINATION:
93 | return Object.assign({}, state, {
94 | depositModal: Object.assign({}, state.depositModal, {
95 | denomination: action.denomination
96 | })
97 | })
98 |
99 | case FETCH_POSITIONS_REQUEST:
100 | return Object.assign({}, state, {
101 | fetching: true,
102 | error: ''
103 | })
104 |
105 | case FETCH_POSITIONS_SUCCESS:
106 | return Object.assign({}, state, {
107 | fetching: false,
108 | error: '',
109 | items: [
110 | ...action.response
111 | ]
112 | })
113 |
114 | case FETCH_POSITIONS_FAILURE:
115 | return Object.assign({}, state, {
116 | fetching: false,
117 | error: ''
118 | })
119 |
120 | case SHOW_NEW_POSITION_MODAL:
121 | return Object.assign({}, state, {
122 | showModal: true
123 | })
124 |
125 | case SET_NEW_POSITION_TITLE:
126 | return Object.assign({}, state, {
127 | newPosition: Object.assign({}, state.newPosition, {
128 | title: action.title,
129 | titleValidationError: ''
130 | })
131 | })
132 |
133 | case SET_NEW_POSITION_DESCRIPTION:
134 | return Object.assign({}, state, {
135 | newPosition: Object.assign({}, state.newPosition, {
136 | description: action.description
137 | })
138 | })
139 |
140 | case HIDE_NEW_POSITION_MODAL:
141 | case SUBMIT_NEW_POSITION_FAILURE:
142 | case SUBMIT_NEW_POSITION_SUCCESS:
143 | return Object.assign({}, state, {
144 | showModal: false,
145 | newPosition: Object.assign({}, initialState.newPosition)
146 | })
147 |
148 | case SET_NEW_POSITION_TITLE_VALIDATION_ERROR:
149 | return Object.assign({}, state, {
150 | newPosition: Object.assign({}, state.newPosition, {
151 | titleValidationError: action.error
152 | })
153 | })
154 |
155 | case SET_POSITION_ORDER_BY:
156 | return Object.assign({}, state, {
157 | sort: Object.assign({}, state.sort, {
158 | orderBy: action.orderBy,
159 | direction: action.direction
160 | })
161 | })
162 |
163 | case SET_POSITION_MINIMUM_VALUE_FILTER:
164 | return Object.assign({}, state, {
165 | filter: Object.assign({}, state.filter, {
166 | minimumValue: action.minimumValue
167 | })
168 | })
169 |
170 | case SET_POSITION_MINIMUM_VALUE_DENOMINATION:
171 | return Object.assign({}, state, {
172 | filter: Object.assign({}, state.filter, {
173 | denomination: action.denomination
174 | })
175 | })
176 |
177 | case SET_POSITION_PAGINATION_ITEMS_TO_DISPLAY:
178 | return Object.assign({}, state, {
179 | pagination: Object.assign({}, state.pagination, {
180 | itemsToDisplay: _.toNumber(action.itemsToDisplay)
181 | })
182 | })
183 |
184 | case SET_POSITION_PAGINATION_CURRENT_PAGE:
185 | return Object.assign({}, state, {
186 | pagination: Object.assign({}, state.pagination, {
187 | currentPage: action.currentPage
188 | })
189 | })
190 |
191 | case SET_POSITION_PAGINATION_NUMBER_OF_PAGES:
192 | return Object.assign({}, state, {
193 | pagination: Object.assign({}, state.pagination, {
194 | numberOfPages: action.numberOfPages
195 | })
196 | })
197 |
198 | default:
199 | return state
200 |
201 | }
202 |
203 | }
204 |
--------------------------------------------------------------------------------
/src/redux/actions/position-actions.js:
--------------------------------------------------------------------------------
1 | import querystring from 'querystring'
2 | import fetch from 'isomorphic-fetch'
3 | import getSignalPerBlock from './utils/getSignalPerBlock'
4 | import _ from 'lodash'
5 |
6 | /*
7 | * connection to local blockchain node.
8 | */
9 |
10 | /* global Web3, web3 */
11 |
12 | import etherSignalAbi from './abi/etherSignalAbi'
13 | import positionRegistryAbi from './abi/positionRegistryAbi'
14 |
15 | if (typeof web3 !== 'undefined' && typeof Web3 !== 'undefined') {
16 | web3 = new Web3(web3.currentProvider)
17 | }
18 | else if (typeof Web3 !== 'undefined') {
19 | web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
20 | if (!web3.isConnected()) {
21 | const Web3 = require('web3')
22 | web3 = new Web3(new Web3.providers.HttpProvider('http://rpc.ethapi.org:8545'))
23 | }
24 | }
25 |
26 | import {
27 | addTimedAlert
28 | } from './alert-actions'
29 |
30 | /*
31 | * What contracts are we interested in?
32 | */
33 |
34 | const etherSignalContract = web3.eth.contract(etherSignalAbi)
35 | const positionRegistryContract = web3.eth.contract(positionRegistryAbi)
36 |
37 | const address = '0x9e75993a7a9b9f92a1978bcc15c30cbcb967bc81'
38 | const positionRegistry = positionRegistryContract.at(address)
39 |
40 | /*
41 | * Begin Redux actions
42 | */
43 |
44 | export const FETCH_POSITIONS_REQUEST = 'FETCH_POSITIONS_REQUEST'
45 | export const FETCH_POSITIONS_SUCCESS = 'FETCH_POSITIONS_SUCCESS'
46 | export const FETCH_POSITIONS_FAILURE = 'FETCH_POSITIONS_FAILURE'
47 |
48 | export function fetchPositionsRequest() {
49 | return {
50 | type: FETCH_POSITIONS_REQUEST
51 | }
52 | }
53 |
54 | export function fetchPositionsSuccess(response) {
55 | return {
56 | type: FETCH_POSITIONS_SUCCESS,
57 | response
58 | }
59 | }
60 |
61 | export function fetchPositionsFailure(error) {
62 | return {
63 | type: FETCH_POSITIONS_FAILURE,
64 | error
65 | }
66 | }
67 |
68 | /*
69 | * @name getPositions
70 | * @param {number} fromBlock
71 | * @param {number} endBlock
72 | * @description
73 | * Makes a call to the positionRegistry ABI and returns positions contained in
74 | * blocks ranging from ${fromBlock} to ${endBlock}
75 | */
76 |
77 | function getPositions(fromBlock, endBlock) {
78 | return new Promise((resolve, reject) => {
79 | positionRegistry.LogPosition({}, {fromBlock, endBlock})
80 | .get((err, positions) => {
81 | if (err) reject(err)
82 | resolve(positions)
83 | })
84 | })
85 | }
86 |
87 | /*
88 | * @name getPositionDeposit
89 | * @param {object} position
90 | * A position returned by #getPositions
91 | * @description
92 | * Calculates the current deposit (or amplitude) held at the signal address for
93 | * a given position
94 | */
95 |
96 | function getPositionDeposit(position) {
97 | return new Promise((resolve, reject) => {
98 | web3.eth.getBlock(position.blockNumber, (err, block) => {
99 | web3.eth.getBalance(position.args.sigAddr, (err, balance) => {
100 | const deposit = Number(web3.fromWei(balance, 'finney'))
101 | resolve(Object.assign({}, position, {block, deposit}))
102 | })
103 | })
104 | })
105 | }
106 |
107 | /*
108 | * @name getPositionVoteMaps
109 | * @description
110 | * Accepts a position object and collects signal transactions starting from
111 | * the positions block number.
112 | * @returns
113 | * Position object with proMap and againstMap properties, which describe which
114 | * accounts voted for and against the position.
115 | */
116 |
117 | function getPositionVoteMaps(position) {
118 |
119 | const address = position.args.sigAddr
120 | const etherSignal = etherSignalContract.at(address)
121 |
122 | return new Promise((resolve, reject) => {
123 |
124 | etherSignal.LogSignal({}, {fromBlock: position.blockNumber})
125 | .get((error, signals) => {
126 |
127 | if (error) {
128 | reject(error)
129 | }
130 |
131 | const proMap = {}
132 | const againstMap = {}
133 |
134 | signals.forEach(signal => {
135 | if (signal.args.pro) {
136 | proMap[signal.args.addr] = 1
137 | againstMap[signal.args.addr] = 0
138 | }
139 | else {
140 | proMap[signal.args.addr] = 0
141 | againstMap[signal.args.addr] = 1
142 | }
143 | })
144 |
145 | resolve(Object.assign({}, position, {proMap, againstMap}))
146 |
147 | })
148 | })
149 | }
150 |
151 | /*
152 | * @name calculateCurrentSignal
153 | * @param {object} position
154 | * A position object with properties proMap and againstMap, which are provided by
155 | * #getPositionVoteMaps
156 | * @description
157 | * Iterates over addresses in proMap and againstMap and calculates their current
158 | * account balance.
159 | */
160 |
161 | function calculateCurrentSignal(position) {
162 |
163 | position.totalPro = 0
164 | position.totalAgainst = 0
165 | position.isMine = false
166 | position.iHaveSignalled = false
167 | position.myVote
168 |
169 | return Promise.all(
170 | _.map(position.proMap, (key, address) => {
171 | return new Promise((resolve, reject) => {
172 | web3.eth.getBalance(address, (err, balance) => {
173 |
174 | balance = web3.fromWei(balance)
175 |
176 | position.proMap[address] = position.proMap[address] * balance
177 | position.againstMap[address] = position.againstMap[address] * balance
178 |
179 | position.totalPro += parseFloat(position.proMap[address])
180 | position.totalAgainst += parseFloat(position.againstMap[address])
181 |
182 | web3.eth.accounts.find(account => {
183 | if (address === account) {
184 | position.iHaveSignalled = true
185 | if (position.proMap[address]) {
186 | position.myVote = 'pro'
187 | }
188 | else if (position.againstMap[address]) {
189 | position.myVote = 'against'
190 | }
191 | }
192 | })
193 |
194 | })
195 |
196 | resolve()
197 |
198 | })
199 |
200 | })
201 | )
202 | .then(() => {
203 |
204 | for (const index in web3.eth.accounts) {
205 | if (web3.eth.accounts[index] === position.args.regAddr) {
206 | position.isMine = true
207 | }
208 | }
209 |
210 | return position
211 |
212 | })
213 |
214 | }
215 |
216 | /*
217 | * @name #formatPosition
218 | * @param {object} position
219 | * @description
220 | * Formats a position object after all the operations responsible for adding
221 | * data have been completed.
222 | */
223 |
224 | function formatPosition(position) {
225 | return {
226 | title: position.args.title,
227 | desc: position.args.text,
228 | regAddr: position.args.regAddr,
229 | pro: Math.round(position.totalPro),
230 | against: Math.round(position.totalAgainst),
231 | absoluteSignal: position.totalPro + Math.abs(position.totalAgainst),
232 | sigAddr: position.args.sigAddr,
233 | deposit: position.deposit,
234 | creationDate: position.block.timestamp,
235 | iHaveSignalled: position.iHaveSignalled,
236 | isMine: position.isMine,
237 | myVote: position.myVote,
238 | history: position.history
239 | }
240 | }
241 |
242 | export function fetchPositions(fromBlock = 1200000, endBlock) {
243 | return dispatch => {
244 | dispatch(fetchPositionsRequest())
245 | getPositions(fromBlock, endBlock)
246 | .then(positions => {
247 | return Promise.all(positions.map(position => getPositionDeposit(position)))
248 | })
249 | .then(positions => {
250 | return Promise.all(positions.map(position => getPositionVoteMaps(position)))
251 | })
252 | .then(positions => {
253 | return Promise.all(positions.map(position => calculateCurrentSignal(position)))
254 | })
255 | .then(positions => {
256 | return Promise.all(positions.map(position => fetchHistoricalSignal(position)))
257 | })
258 | .then(positions => {
259 | return positions.map(position => formatPosition(position))
260 | })
261 | .then(positions => {
262 | dispatch(fetchPositionsSuccess(positions))
263 | })
264 | .catch(error => {
265 | dispatch(fetchPositionsFailure(error))
266 | })
267 | }
268 | }
269 |
270 | export const VOTE_ON_POSITION_REQUEST = 'VOTE_ON_POSITION_REQUEST'
271 | export const VOTE_ON_POSITION_SUCCESS = 'VOTE_ON_POSITION_SUCCESS'
272 | export const VOTE_ON_POSITION_FAILURE = 'VOTE_ON_POSITION_FAILURE'
273 |
274 | export function voteOnPositionRequest() {
275 | return {
276 | type: VOTE_ON_POSITION_REQUEST
277 | }
278 | }
279 |
280 | export function voteOnPositionSuccess(response) {
281 | return {
282 | type: VOTE_ON_POSITION_SUCCESS,
283 | response
284 | }
285 | }
286 |
287 | export function voteOnPositionFailure(error) {
288 | return {
289 | type: VOTE_ON_POSITION_FAILURE,
290 | error
291 | }
292 | }
293 |
294 | // Casts a a vote for against a given position for all accounts that are active.
295 | export function voteOnPosition(positionSignalAddress, vote) {
296 |
297 | // If vote is true, it is a vote in favor of the given position.
298 | // Else, it is a vote against the position.
299 | const etherSignal = etherSignalContract.at(positionSignalAddress)
300 |
301 | return dispatch => {
302 | Promise.all(
303 | web3.eth.accounts.map(account => {
304 | return new Promise((resolve, reject) => {
305 | try {
306 | resolve(etherSignal.setSignal(vote, {from: account}))
307 | }
308 | catch (err) {
309 | reject(err)
310 | }
311 | })
312 | })
313 | )
314 | .then(response => {
315 | dispatch(addTimedAlert('Your vote was submitted!', 'success'))
316 | dispatch(voteOnPositionSuccess(response))
317 | })
318 | .catch(error => {
319 | dispatch(addTimedAlert(error.message, 'danger'))
320 | dispatch(voteOnPositionFailure(error))
321 | })
322 | }
323 |
324 | }
325 |
326 | export const SHOW_NEW_POSITION_MODAL = 'SHOW_NEW_POSITION_MODAL'
327 | export const HIDE_NEW_POSITION_MODAL = 'HIDE_NEW_POSITION_MODAL'
328 |
329 | export function showNewPositionModal() {
330 | return {
331 | type: SHOW_NEW_POSITION_MODAL
332 | }
333 | }
334 |
335 | export function hideNewPositionModal() {
336 | return {
337 | type: HIDE_NEW_POSITION_MODAL
338 | }
339 | }
340 |
341 | export const SET_NEW_POSITION_TITLE = 'SET_NEW_POSITION_TITLE'
342 | export const SET_NEW_POSITION_DESCRIPTION = 'SET_NEW_POSITION_DESCRIPTION'
343 | export const SET_NEW_POSITION_TITLE_VALIDATION_ERROR = 'SET_NEW_POSITION_TITLE_VALIDATION_ERROR'
344 |
345 | export function setNewPositionTitle(title) {
346 | return {
347 | type: SET_NEW_POSITION_TITLE,
348 | title
349 | }
350 | }
351 |
352 | export function setNewPositionDescription(description) {
353 | return {
354 | type: SET_NEW_POSITION_DESCRIPTION,
355 | description
356 | }
357 | }
358 |
359 | export function setNewPositionTitleValidationError(error) {
360 | return {
361 | type: SET_NEW_POSITION_TITLE_VALIDATION_ERROR,
362 | error
363 | }
364 | }
365 |
366 | export const SUBMIT_NEW_POSITION_REQUEST = 'SUBMIT_NEW_POSITION_REQUEST'
367 | export const SUBMIT_NEW_POSITION_SUCCESS = 'SUBMIT_NEW_POSITION_SUCCESS'
368 | export const SUBMIT_NEW_POSITION_FAILURE = 'SUBMIT_NEW_POSITION_FAILURE'
369 |
370 | export function submitNewPositionRequest() {
371 | return {
372 | type: SUBMIT_NEW_POSITION_REQUEST
373 | }
374 | }
375 |
376 | export function submitNewPositionSuccess(response) {
377 | return {
378 | type: SUBMIT_NEW_POSITION_SUCCESS,
379 | response
380 | }
381 | }
382 |
383 | export function submitNewPositionFailure(error) {
384 | return {
385 | type: SUBMIT_NEW_POSITION_FAILURE,
386 | error
387 | }
388 | }
389 |
390 | export function submitNewPosition(title, description, account) {
391 |
392 | // Todo: there should be an account selector
393 | const sender = account
394 | const data = positionRegistry.registerPosition.getData(title, description)
395 |
396 | return dispatch => {
397 | dispatch(submitNewPositionRequest())
398 | web3.eth.estimateGas({from: sender, to: address, data: data}, (err, gas) => {
399 | try {
400 | positionRegistry.registerPosition.sendTransaction(
401 | title,
402 | description,
403 | {
404 | from: sender,
405 | to: address,
406 | gas: gas
407 | },
408 | (err, result) => {
409 | if (err) throw err
410 | dispatch(addTimedAlert('The position was submitted!', 'success'))
411 | dispatch(submitNewPositionSuccess(result))
412 | }
413 | )
414 | }
415 |
416 | catch (error) {
417 | dispatch(addTimedAlert(error.message, 'danger'))
418 | dispatch(submitNewPositionFailure(error))
419 | }
420 |
421 | })
422 |
423 | }
424 |
425 | }
426 |
427 | export const SET_POSITION_ORDER_BY = 'SET_POSITION_ORDER_BY'
428 | export const SET_POSITION_MINIMUM_VALUE_FILTER = 'SET_POSITION_MINIMUM_VALUE_FILTER'
429 | export const SET_POSITION_MINIMUM_VALUE_DENOMINATION = 'SET_POSITION_MINIMUM_VALUE_DENOMINATION'
430 | export const SET_POSITION_PAGINATION_ITEMS_TO_DISPLAY = 'SET_POSITION_PAGINATION_ITEMS_TO_DISPLAY'
431 | export const SET_POSITION_PAGINATION_CURRENT_PAGE = 'SET_POSITION_PAGINATION_CURRENT_PAGE'
432 | export const SET_POSITION_PAGINATION_NUMBER_OF_PAGES = 'SET_POSITION_PAGINATION_NUMBER_OF_PAGES'
433 |
434 | export function setPositionOrderBy(orderBy, direction) {
435 | return {
436 | type: SET_POSITION_ORDER_BY,
437 | orderBy,
438 | direction
439 | }
440 | }
441 |
442 | export function setPositionMinimumValueFilter(minimumValue) {
443 | return {
444 | type: SET_POSITION_MINIMUM_VALUE_FILTER,
445 | minimumValue
446 | }
447 | }
448 |
449 | export function setPositionMiniumValueDenomination(denomination) {
450 | return {
451 | type: SET_POSITION_MINIMUM_VALUE_DENOMINATION,
452 | denomination
453 | }
454 | }
455 |
456 | export function setPositionPaginationItemsToDisplay(itemsToDisplay) {
457 | return {
458 | type: SET_POSITION_PAGINATION_ITEMS_TO_DISPLAY,
459 | itemsToDisplay
460 | }
461 | }
462 |
463 | export function setPositionPaginationCurrentPage(currentPage) {
464 | return {
465 | type: SET_POSITION_PAGINATION_CURRENT_PAGE,
466 | currentPage
467 | }
468 | }
469 |
470 | export function setPositionPaginationNumberOfPages(numberOfPages) {
471 | return {
472 | type: SET_POSITION_PAGINATION_NUMBER_OF_PAGES,
473 | numberOfPages
474 | }
475 | }
476 |
477 | export const DISPLAY_POSITION_DEPOSIT_MODAL = 'DISPLAY_POSITION_DEPOSIT_MODAL'
478 | export const HIDE_POSITION_DEPOSIT_MODAL = 'HIDE_POSITION_DEPOSIT_MODAL'
479 | export const SET_POSITION_DEPOSIT_VALUE = 'SET_POSITION_DEPOSIT_VALUE'
480 | export const SET_POSITION_DEPOSIT_DENOMINATION = 'SET_POSITION_DEPOSIT_DENOMINATION'
481 | export const SET_POSITION_DEPOSIT_VALIDATION_ERROR = 'SET_POSITION_DEPOSIT_VALIDATION_ERROR'
482 | export const ADD_POSITION_DEPOSIT_REQUEST = 'ADD_POSITION_DEPOSIT_REQUEST'
483 | export const ADD_POSITION_DEPOSIT_SUCCESS = 'ADD_POSITION_DEPOSIT_SUCCESS'
484 | export const ADD_POSITION_DEPOSIT_FAILURE = 'ADD_POSITION_DEPOSIT_FAILURE'
485 |
486 | export function displayPositionDepositModal(senderAddr, recipientAddr) {
487 | return {
488 | type: DISPLAY_POSITION_DEPOSIT_MODAL,
489 | senderAddr,
490 | recipientAddr
491 | }
492 | }
493 |
494 | export function hidePositionDepositModal() {
495 | return {
496 | type: HIDE_POSITION_DEPOSIT_MODAL
497 | }
498 | }
499 |
500 | export function setPositionDepositValue(value) {
501 | return {
502 | type: SET_POSITION_DEPOSIT_VALUE,
503 | value
504 | }
505 | }
506 |
507 | export function setPositionDepositDenomination(denomination) {
508 | return {
509 | type: SET_POSITION_DEPOSIT_DENOMINATION,
510 | denomination
511 | }
512 | }
513 |
514 | export function setPositionDepositValidationError(error) {
515 | return {
516 | type: SET_POSITION_DEPOSIT_VALIDATION_ERROR,
517 | error
518 | }
519 | }
520 |
521 | export function addPositionDepositRequest() {
522 | return {
523 | type: ADD_POSITION_DEPOSIT_REQUEST
524 | }
525 | }
526 |
527 | export function addPositionDepositSuccess(response) {
528 | return {
529 | type: ADD_POSITION_DEPOSIT_REQUEST,
530 | response
531 | }
532 | }
533 |
534 | export function addPositionDepositFailure(error) {
535 | return {
536 | type: ADD_POSITION_DEPOSIT_FAILURE,
537 | error
538 | }
539 | }
540 |
541 | function denominationToWeiConverter(value, denomination) {
542 |
543 | switch (denomination) {
544 |
545 | case 'Ether':
546 | return value * Math.pow(10, 18)
547 | case 'Finney':
548 | return value * Math.pow(10, 15)
549 | case 'Wei':
550 | default:
551 | return value
552 |
553 | }
554 |
555 | }
556 |
557 | export function addPositionDeposit(value, denomination, senderAddr, recipientAddr) {
558 |
559 | return dispatch => {
560 | dispatch(addPositionDepositRequest())
561 | return new Promise((resolve, reject) => {
562 | web3.eth.sendTransaction({
563 | value: denominationToWeiConverter(value, denomination),
564 | from: senderAddr,
565 | to: recipientAddr
566 | }, (err, result) => {
567 | if (err) reject(err)
568 | resolve(result)
569 | })
570 | })
571 | .then(response => {
572 | dispatch(addTimedAlert('The deposit was submitted successfully', 'success'))
573 | dispatch(hidePositionDepositModal())
574 | dispatch(addPositionDepositSuccess(response))
575 | })
576 | .catch(error => {
577 | dispatch(addTimedAlert('The transaction was not confirmed', 'danger'))
578 | dispatch(hidePositionDepositModal())
579 | dispatch(addPositionDepositFailure(error))
580 | })
581 | }
582 |
583 | }
584 |
585 | export const GET_POSITION_SIGNAL_HISTORY_REQUEST = 'GET_POSITION_SIGNAL_HISTORY_REQUEST'
586 | export const GET_POSITION_SIGNAL_HISTORY_SUCCESS = 'GET_POSITION_SIGNAL_HISTORY_SUCCESS'
587 | export const GET_POSITION_SIGNAL_HISTORY_FAILURE = 'GET_POSITION_SIGNAL_HISTORY_FAILURE'
588 |
589 | export function getPositionSignalHistoryRequest() {
590 | return {
591 | type: GET_POSITION_SIGNAL_HISTORY_REQUEST
592 | }
593 | }
594 |
595 | export function getPositionSignalHistorySuccess(response) {
596 | return {
597 | type: GET_POSITION_SIGNAL_HISTORY_SUCCESS,
598 | response
599 | }
600 | }
601 |
602 | export function getPositionSignalHistoryFailure(error) {
603 | return {
604 | type: GET_POSITION_SIGNAL_HISTORY_FAILURE,
605 | error
606 | }
607 | }
608 |
609 | export function fetchHistoricalSignal(position, opts) {
610 |
611 | const contractAddress = position.args.sigAddr
612 |
613 | const URL = `https://ethersignal-api.herokuapp.com/transaction/${contractAddress}`
614 |
615 | if (!opts) {
616 | opts = {}
617 | }
618 |
619 | const params = {
620 | startblock: opts.startblock || 0,
621 | endblock: opts.endblock || 99999999,
622 | sort: opts.sort || 'asc'
623 | }
624 |
625 | const query = querystring.stringify(params)
626 |
627 | return fetch(`${URL}?${query}`, {
628 | method: 'GET'
629 | })
630 | .then(response => response.json())
631 | .then(response => {
632 |
633 | if (response.message === 'NOTOK') {
634 | throw 'There was an error with the testnet API.'
635 | }
636 |
637 | return Promise.all(
638 |
639 | response.result.map(transaction => {
640 |
641 | return new Promise((resolve, reject) => {
642 |
643 | web3.eth.getBalance(transaction.from, transaction.blockNumber, (err, balance) => {
644 |
645 | if (!balance) {
646 | console.log('No balance was returned for this transaction') // eslint-disable-line no-console
647 | console.log(transaction) // eslint-disable-line no-console
648 | }
649 |
650 | resolve({
651 | from: transaction.from,
652 | blockNumber: transaction.blockNumber,
653 | signal: balance,
654 | vote: transaction.input.slice(-1)
655 | })
656 |
657 | })
658 | })
659 | })
660 | )
661 | })
662 | .then(response => {
663 | return getSignalPerBlock(response)
664 | })
665 | .then(history => {
666 | return Object.assign({}, position, {history})
667 | })
668 | .catch(error => {
669 | // Recover from an error by passing an empty history
670 | return Object.assign({}, position, {history: []})
671 | })
672 |
673 | }
674 |
--------------------------------------------------------------------------------