on master help sign in
Merge pull request #351 from urbit/m/senate-mvp
a9df1dcFang
package-lock.json+4−6
@@ -1,6 +1,6 @@
1{
1{
2 "name": "bridge",
2 "name": "bridge",
3− "version": "1.4.1",
3+ "version": "2.0.0",
4 "lockfileVersion": 1,
4 "lockfileVersion": 1,
5 "requires": true,
5 "requires": true,
6 "dependencies": {
6 "dependencies": {
@@ -2591,13 +2591,11 @@
2591 }
2591 }
2592 },
2592 },
2593 "azimuth-js": {
2593 "azimuth-js": {
2594− "version": "0.15.1",
2594+ "version": "file:../constitution-js",
2595− "resolved": "https://registry.npmjs.org/azimuth-js/-/azimuth-js-0.15.1.tgz",
2596− "integrity": "sha512-EEjDPlv4LQcgF5xhRQg2o81IrcVCu+3K87AI/NVRl37rcQyTCrCzjfeafOTqUQ8PvQXk+wf0Vrtrc+80yJju4g==",
2597 "requires": {
2595 "requires": {
2598− "azimuth-solidity": "1.2.0",
2596+ "azimuth-solidity": "1.2.1",
2599 "ethereumjs-util": "^5.2.0",
2597 "ethereumjs-util": "^5.2.0",
2600− "urbit-ob": "^4.0.1"
2598+ "urbit-ob": "4.1.4"
2601 }
2599 }
2602 },
2600 },
2603 "azimuth-solidity": {
2601 "azimuth-solidity": {
package.json+1−1
@@ -19,7 +19,7 @@
19 "@ledgerhq/hw-transport-u2f": "^4.61.0",
19 "@ledgerhq/hw-transport-u2f": "^4.61.0",
20 "@welldone-software/why-did-you-render": "^3.2.3",
20 "@welldone-software/why-did-you-render": "^3.2.3",
21 "async-retry": "^1.2.3",
21 "async-retry": "^1.2.3",
22− "azimuth-js": "^0.15.1",
22+ "azimuth-js": "^0.17.0",
23 "azimuth-solidity": "^1.2.0",
23 "azimuth-solidity": "^1.2.0",
24 "bip32": "^1.0.2",
24 "bip32": "^1.0.2",
25 "bip39": "^2.5.0",
25 "bip39": "^2.5.0",
src/lib/router.js+2
@@ -1,6 +1,7 @@
1import Disclaimer from 'views/Disclaimer';
1import Disclaimer from 'views/Disclaimer';
2import Login from 'views/Login.js';
2import Login from 'views/Login.js';
3import Admin from 'views/Admin';
3import Admin from 'views/Admin';
4+import Senate from 'views/Senate';
4import CreateGalaxy from 'views/CreateGalaxy';
5import CreateGalaxy from 'views/CreateGalaxy';
5import IssueChild from 'views/IssueChild';
6import IssueChild from 'views/IssueChild';
6
7
@@ -25,6 +26,7 @@ export const ROUTES = {
25 [ROUTE_NAMES.ISSUE_CHILD]: IssueChild,
26 [ROUTE_NAMES.ISSUE_CHILD]: IssueChild,
26 [ROUTE_NAMES.INVITE]: Invite,
27 [ROUTE_NAMES.INVITE]: Invite,
27 [ROUTE_NAMES.ADMIN]: Admin,
28 [ROUTE_NAMES.ADMIN]: Admin,
29+ [ROUTE_NAMES.SENATE]: Senate,
28 // TODO: nest this route under the Party.js router
30 // TODO: nest this route under the Party.js router
29 [ROUTE_NAMES.PARTY_SET_POOL_SIZE]: PartySetPoolSize,
31 [ROUTE_NAMES.PARTY_SET_POOL_SIZE]: PartySetPoolSize,
30 [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
32 [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
src/views/Point.js+22
@@ -35,6 +35,7 @@ export default function Point() {
35 isActiveOwner,
35 isActiveOwner,
36 canManage,
36 canManage,
37 canSpawn,
37 canSpawn,
38+ canVote,
38 } = useCurrentPermissions();
39 } = useCurrentPermissions();
39
40
40 // fetch the invites for the current cursor
41 // fetch the invites for the current cursor
@@ -42,6 +43,8 @@ export default function Point() {
42
43
43 const goAdmin = useCallback(() => push(names.ADMIN), [push, names]);
44 const goAdmin = useCallback(() => push(names.ADMIN), [push, names]);
44
45
46+ const goSenate = useCallback(() => push(names.SENATE), [push, names]);
47+
45 const goInvite = useCallback(() => push(names.INVITE), [push, names]);
48 const goInvite = useCallback(() => push(names.INVITE), [push, names]);
46
49
47 const goPartiesSetPoolSize = useCallback(
50 const goPartiesSetPoolSize = useCallback(
@@ -90,6 +93,24 @@ export default function Point() {
90 }
93 }
91 })();
94 })();
92
95
96+ const senateButton = (() => {
97+ if (azimuth.getPointSize(point) !== azimuth.PointSize.Galaxy) {
98+ return null;
99+ }
100+ return (
101+ <>
102+ <Grid.Item
103+ full
104+ as={ForwardButton}
105+ disabled={!canVote}
106+ onClick={goSenate}>
107+ Senate
108+ </Grid.Item>
109+ <Grid.Divider />
110+ </>
111+ );
112+ })();
113+
93 // sync the current cursor
114 // sync the current cursor
94 useSyncOwnedPoints([point]);
115 useSyncOwnedPoints([point]);
95
116
@@ -126,6 +147,7 @@ export default function Point() {
126 <Grid.Divider />
147 <Grid.Divider />
127 </>
148 </>
128 )}
149 )}
150+ {senateButton}
129 <Grid.Item full as={BootUrbitOSButton} />
151 <Grid.Item full as={BootUrbitOSButton} />
130 <Grid.Divider />
152 <Grid.Divider />
131 <Grid.Item
153 <Grid.Item
src/views/Senate.jsnew file+252
@@ -0,0 +1,252 @@
1+import React, { useCallback, useMemo, useState } from 'react';
2+import cn from 'classnames';
3+import { Grid, Text, Input, P, LinkButton } from 'indigo-react';
4+import * as azimuth from 'azimuth-js';
5+import ob from 'urbit-ob';
6+
7+import { useNetwork } from 'store/network';
8+import { usePointCache } from 'store/pointCache';
9+import { usePointCursor } from 'store/pointCursor';
10+
11+import * as need from 'lib/need';
12+import useEthereumTransaction from 'lib/useEthereumTransaction';
13+import { GAS_LIMITS } from 'lib/constants';
14+import patp2dec from 'lib/patp2dec';
15+import { getSpawnCandidate } from 'lib/child';
16+import { useLocalRouter } from 'lib/LocalRouter';
17+import useConstant from 'lib/useConstant';
18+
19+import { keccak256 } from 'lib/wallet';
20+
21+import ViewHeader from 'components/ViewHeader';
22+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
23+import View from 'components/View';
24+import { PointInput, AddressInput } from 'form/Inputs';
25+import {
26+ composeValidator,
27+ buildPointValidator,
28+ buildAddressValidator,
29+ hasErrors,
30+} from 'form/validators';
31+import BridgeForm from 'form/BridgeForm';
32+import FormError from 'form/FormError';
33+import CopiableAddress from 'components/CopiableAddress';
34+import convertToInt from 'lib/convertToInt';
35+
36+function useCastDocumentVote() {
37+ const { contracts } = useNetwork();
38+ const { pointCursor } = usePointCursor();
39+ const { syncDetails } = usePointCache();
40+
41+ const _contracts = need.contracts(contracts);
42+ const _point = need.point(pointCursor);
43+
44+ return useEthereumTransaction(
45+ useCallback(
46+ (hash, accept) => {
47+ return azimuth.ecliptic.castDocumentVote(
48+ _contracts,
49+ _point,
50+ hash,
51+ accept
52+ );
53+ },
54+ [_contracts, _point]
55+ ),
56+ useCallback(() => null, []), //TODO update polls[hash].hasVoted ?
57+ GAS_LIMITS.DEFAULT //TODO update once we have a decent sample size
58+ );
59+}
60+
61+export default function Senate() {
62+ const { pop } = useLocalRouter();
63+ const { contracts } = useNetwork();
64+ const { pointCursor } = usePointCursor();
65+
66+ const _contracts = need.contracts(contracts);
67+ const _point = convertToInt(need.point(pointCursor), 10);
68+
69+ const {
70+ construct,
71+ unconstruct,
72+ inputsLocked,
73+ completed,
74+ bind,
75+ } = useCastDocumentVote();
76+
77+ const [documentHash, setDocumentHash] = useState('0x...');
78+ const [majorities, setMajorities] = useState([]);
79+ const [proposals, setProposals] = useState([]);
80+ const [polls, setPolls] = useState({});
81+ const [loading, setLoading] = useState(true);
82+ const [votingOn, setVotingOn] = useState({});
83+
84+ //TODO update every second?
85+ const now = Math.round(new Date().getTime() / 1000);
86+
87+ useMemo(async () => {
88+ let proposals = await azimuth.polls.getDocumentProposals(_contracts);
89+ let majorities = await azimuth.polls.getDocumentMajorities(_contracts);
90+ let polls = {};
91+ for (let doc of proposals) {
92+ let poll = await azimuth.polls.getDocumentPoll(_contracts, doc);
93+ poll.endTime = polls.start + poll.duration;
94+ poll.hasVoted = await azimuth.polls.hasVotedOnDocumentPoll(
95+ _contracts,
96+ _point,
97+ doc
98+ );
99+ polls[doc] = poll;
100+ }
101+ setMajorities(majorities);
102+ setProposals(proposals);
103+ setPolls(polls);
104+ setLoading(false);
105+ return;
106+ }, [_contracts]);
107+
108+ const [open, closed] = useMemo(() => {
109+ let open = [];
110+ let closed = [];
111+ if (loading) return [open, closed];
112+ for (let doc of proposals) {
113+ if (now > polls[doc].endTime) {
114+ closed.push(doc);
115+ } else {
116+ open.push(doc);
117+ }
118+ }
119+ return [open, closed];
120+ }, [loading, proposals, polls, now]);
121+
122+ const doVote = useCallback(
123+ (hash, accept) => {
124+ construct(hash, accept);
125+ setVotingOn({ hash, accept });
126+ },
127+ [construct, setVotingOn]
128+ );
129+
130+ const didVote = useCallback(() => {
131+ polls[votingOn].hasVoted = true;
132+ setPolls(polls);
133+ setVotingOn({});
134+ }, [polls, setPolls, votingOn]);
135+
136+ const majorityList = useMemo(() => {
137+ return majorities.map(doc => {
138+ return (
139+ <Grid.Item full as={Text}>
140+ <a
141+ target="_blank"
142+ rel="noopener noreferrer"
143+ href={`https://github.com/urbit/azimuth/blob/master/proposals/${doc}.txt`}>
144+ <code>{doc}</code>↗
145+ </a>
146+ </Grid.Item>
147+ );
148+ });
149+ }, [majorities]);
150+
151+ const openList = useMemo(() => {
152+ return open.map(doc => {
153+ const hasVoted = polls[doc].hasVoted;
154+ return (
155+ <>
156+ <Grid.Item full as={Text}>
157+ <P>
158+ <code>{doc}</code>
159+ </P>
160+ <P>
161+ {hasVoted ? (
162+ 'Your vote has been cast.'
163+ ) : (
164+ <>
165+ <LinkButton onClick={() => doVote(doc, true)}>
166+ {votingOn.hash === doc && votingOn.accept ? (
167+ <b>support</b>
168+ ) : (
169+ <>support</>
170+ )}
171+ </LinkButton>
172+ {' / '}
173+ <LinkButton onClick={() => doVote(doc, false)}>
174+ {votingOn.hash === doc && !votingOn.accept ? (
175+ <b>reject</b>
176+ ) : (
177+ <>reject</>
178+ )}
179+ </LinkButton>
180+ </>
181+ )}
182+ </P>
183+ </Grid.Item>
184+ <Grid.Divider />
185+ </>
186+ );
187+ });
188+ }, [open, polls, votingOn]);
189+
190+ const onDocumentChange = useCallback(({ valid, values }) => {
191+ if (values.document) {
192+ const hash = '0x' + keccak256(values.document.trim()).toString('hex');
193+ setDocumentHash(hash);
194+ } else {
195+ setDocumentHash('0x...');
196+ }
197+ }, []);
198+
199+ return (
200+ <View pop={pop} inset>
201+ <Grid>
202+ <Grid.Item full as={ViewHeader}>
203+ Senate: Document Proposals
204+ </Grid.Item>
205+
206+ <Grid.Item full as={Grid}>
207+ <Grid.Item full as={P}>
208+ Accepted documents:
209+ </Grid.Item>
210+ {majorityList}
211+ </Grid.Item>
212+
213+ <Grid.Divider />
214+
215+ <Grid.Item full as={Grid}>
216+ <Grid.Item full as={P}>
217+ Open document polls:
218+ </Grid.Item>
219+ {openList}
220+ </Grid.Item>
221+
222+ <BridgeForm onValues={onDocumentChange}>
223+ {({ onSubmit, values }) => (
224+ // <>
225+ <Grid.Item
226+ full
227+ as={InlineEthereumTransaction}
228+ {...bind}
229+ onReturn={didVote}
230+ />
231+
232+ // <Grid.Divider />
233+ //
234+ // <Grid.Item
235+ // full
236+ // as={Input}
237+ // type="textarea"
238+ // placeholder="Input some text to find its hash..."
239+ // name="document"
240+ // className="mt4"
241+ // />
242+ //
243+ // <Grid.Item full as={CopiableAddress}>
244+ // {documentHash}
245+ // </Grid.Item>
246+ // </>
247+ )}
248+ </BridgeForm>
249+ </Grid>
250+ </View>
251+ );
252+}