on master help sign in
senate: support voting on upgrade proposals
2063508fang
src/lib/routeNames.js+2
@@ -19,6 +19,8 @@ export const ROUTE_NAMES = {
19 SIGIL_GENERATOR: Symbol('SIGIL_GENERATOR'),
19 SIGIL_GENERATOR: Symbol('SIGIL_GENERATOR'),
20 CREATE_GALAXY: Symbol('CREATE_GALAXY'),
20 CREATE_GALAXY: Symbol('CREATE_GALAXY'),
21 SENATE: Symbol('SENATE'),
21 SENATE: Symbol('SENATE'),
22+ DOCUMENTS: Symbol('DOCUMENTS'),
23+ UPGRADES: Symbol('UPGRADES'),
22 ISSUE_CHILD: Symbol('ISSUE_CHILD'),
24 ISSUE_CHILD: Symbol('ISSUE_CHILD'),
23 PARTY_SET_POOL_SIZE: Symbol('PARTY_SET_POOL_SIZE'),
25 PARTY_SET_POOL_SIZE: Symbol('PARTY_SET_POOL_SIZE'),
24 INVITE_COHORT: Symbol('INVITE_COHORT'),
26 INVITE_COHORT: Symbol('INVITE_COHORT'),
src/lib/router.js+4
@@ -2,6 +2,8 @@ import Disclaimer from 'views/Disclaimer';
2import Login from 'views/Login.js';
2import Login from 'views/Login.js';
3import Other from 'views/Login/Other.js';
3import Other from 'views/Login/Other.js';
4import Senate from 'views/Senate';
4import Senate from 'views/Senate';
5+import Documents from 'views/Senate/Documents';
6+import Upgrades from 'views/Senate/Upgrades';
5import CreateGalaxy from 'views/CreateGalaxy';
7import CreateGalaxy from 'views/CreateGalaxy';
6import IssueChild from 'views/IssueChild';
8import IssueChild from 'views/IssueChild';
7import StarRelease from 'views/Release';
9import StarRelease from 'views/Release';
@@ -35,6 +37,8 @@ export const ROUTES = {
35 [ROUTE_NAMES.CREATE_GALAXY]: CreateGalaxy,
37 [ROUTE_NAMES.CREATE_GALAXY]: CreateGalaxy,
36 [ROUTE_NAMES.ISSUE_CHILD]: IssueChild,
38 [ROUTE_NAMES.ISSUE_CHILD]: IssueChild,
37 [ROUTE_NAMES.SENATE]: Senate,
39 [ROUTE_NAMES.SENATE]: Senate,
40+ [ROUTE_NAMES.DOCUMENTS]: Documents,
41+ [ROUTE_NAMES.UPGRADES]: Upgrades,
38 // TODO: nest this route under the Party.js router
42 // TODO: nest this route under the Party.js router
39 [ROUTE_NAMES.PARTY_SET_POOL_SIZE]: PartySetPoolSize,
43 [ROUTE_NAMES.PARTY_SET_POOL_SIZE]: PartySetPoolSize,
40 [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
44 [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
src/views/Senate.js+25−204
@@ -1,219 +1,40 @@
1−import React, { useCallback, useMemo, useState } from 'react';
1+import React, { useCallback, useState } from 'react';
2−import { Grid, Text, P, LinkButton } from 'indigo-react';
2+import { Grid } from 'indigo-react';
3−import * as azimuth from 'azimuth-js';
3+import { azimuth } from 'azimuth-js';
4
4
5−import { useNetwork } from 'store/network';
6−import { usePointCursor } from 'store/pointCursor';
7−
8−import * as need from 'lib/need';
9−import useEthereumTransaction from 'lib/useEthereumTransaction';
10−import { GAS_LIMITS } from 'lib/constants';
11−import { useLocalRouter } from 'lib/LocalRouter';
12−
13−import ViewHeader from 'components/ViewHeader';
14−import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
15import View from 'components/View';
5import View from 'components/View';
16−import BridgeForm from 'form/BridgeForm';
6+import ViewHeader from 'components/ViewHeader';
17−import convertToInt from 'lib/convertToInt';
7+import { ForwardButton } from 'components/Buttons';
18−
19−function useCastDocumentVote() {
20− const { contracts } = useNetwork();
21− const { pointCursor } = usePointCursor();
22−
23− const _contracts = need.contracts(contracts);
24− const _point = need.point(pointCursor);
25−
26− return useEthereumTransaction(
27− useCallback(
28− (hash, accept) => {
29− return azimuth.ecliptic.castDocumentVote(
30− _contracts,
31− _point,
32− hash,
33− accept
34− );
35− },
36− [_contracts, _point]
37− ),
38− useCallback(() => null, []), //TODO update polls[hash].hasVoted ?
39− GAS_LIMITS.DEFAULT //TODO update once we have a decent sample size
40− );
41−}
42−
43−function renderDocumentHash(doc) {
44− return doc.slice(0, 34) + '\n ' + doc.slice(34);
45−}
46−
47−export default function Senate() {
48− const { pop } = useLocalRouter();
49− const { contracts } = useNetwork();
50− const { pointCursor } = usePointCursor();
51−
52− const _contracts = need.contracts(contracts);
53− const _point = convertToInt(need.point(pointCursor), 10);
54−
55− const { construct, bind } = useCastDocumentVote();
56−
57− // const [documentHash, setDocumentHash] = useState('0x...');
58− const [majorities, setMajorities] = useState([]);
59− const [proposals, setProposals] = useState([]);
60− const [polls, setPolls] = useState({});
61− const [loading, setLoading] = useState(true);
62− const [votingOn, setVotingOn] = useState({});
63−
64− //TODO update every second?
65− const now = Math.round(new Date().getTime() / 1000);
66−
67− useMemo(async () => {
68− let proposals = await azimuth.polls.getDocumentProposals(_contracts);
69− let majorities = await azimuth.polls.getDocumentMajorities(_contracts);
70− let polls = {};
71− for (let doc of proposals) {
72− let poll = await azimuth.polls.getDocumentPoll(_contracts, doc);
73− poll.endTime = convertToInt(poll.start) + convertToInt(poll.duration);
74− poll.hasVoted = await azimuth.polls.hasVotedOnDocumentPoll(
75− _contracts,
76− _point,
77− doc
78− );
79− polls[doc] = poll;
80− }
81− setMajorities(majorities);
82− setProposals(proposals);
83− setPolls(polls);
84− setLoading(false);
85− return;
86− }, [_contracts, _point]);
87−
88− // eslint-disable-next-line
89− const [open, closed] = useMemo(() => {
90− let open = [];
91− let closed = [];
92− if (loading) return [open, closed];
93− for (let doc of proposals) {
94− if (now > polls[doc].endTime) {
95− closed.push(doc);
96− } else {
97− open.push(doc);
98− }
99− }
100− return [open, closed];
101− }, [loading, proposals, polls, now]);
102
8
103− const doVote = useCallback(
9+import { useLocalRouter } from 'lib/LocalRouter';
104− (hash, accept) => {
105− construct(hash, accept);
106− setVotingOn({ hash, accept });
107− },
108− [construct, setVotingOn]
109− );
110
10
111− const didVote = useCallback(() => {
11+export default function Point() {
112− polls[votingOn].hasVoted = true;
12+ const { pop, push, names } = useLocalRouter();
113− setPolls(polls);
114− setVotingOn({});
115− }, [polls, setPolls, votingOn]);
116
13
117− const majorityList = useMemo(() => {
14+ //TODO if we moved the poll fetching logic into a cache/store,
118− return majorities.map(doc => {
15+ // we could be cute here and display "x ongoing" under the buttons.
119− return (
120− <Grid.Item full className="mv2" as={P}>
121− <a
122− target="_blank"
123− rel="noopener noreferrer"
124− href={`https://raw.githubusercontent.com/urbit/azimuth/master/proposals/${doc}.txt`}
125− class="mb4 mono wrap pre">
126− {renderDocumentHash(doc)}↗
127− </a>
128− </Grid.Item>
129− );
130− });
131− }, [majorities]);
132
16
133− const openList = useMemo(() => {
17+ const goDocuments = useCallback(() => push(names.DOCUMENTS), [push, names]);
134− return open.map(doc => {
135− const hasVoted = polls[doc].hasVoted;
136− return (
137− <>
138− <Grid.Item full className="mv2" as={Text}>
139− <P className="m0 mono wrap pre">{renderDocumentHash(doc)}</P>
140− <P className="mt0">
141− {hasVoted ? (
142− 'Your vote has been cast.'
143− ) : (
144− <>
145− <LinkButton onClick={() => doVote(doc, true)}>
146− {votingOn.hash === doc && votingOn.accept ? (
147− <b>support</b>
148− ) : (
149− <>support</>
150− )}
151− </LinkButton>
152− {' / '}
153− <LinkButton onClick={() => doVote(doc, false)}>
154− {votingOn.hash === doc && !votingOn.accept ? (
155− <b>reject</b>
156− ) : (
157− <>reject</>
158− )}
159− </LinkButton>
160− </>
161− )}
162− </P>
163− </Grid.Item>
164− <Grid.Divider />
165− </>
166− );
167− });
168− }, [open, polls, votingOn, doVote]);
169
18
170− const onDocumentChange = useCallback(({ valid, values }) => {
19+ const goUpgrades = useCallback(() => push(names.UPGRADES), [push, names]);
171− // if (values.document) {
172− // const hash = '0x' + keccak256(values.document.trim()).toString('hex');
173− // setDocumentHash(hash);
174− // } else {
175− // setDocumentHash('0x...');
176− // }
177− }, []);
178
20
179 return (
21 return (
180 <View pop={pop} inset>
22 <View pop={pop} inset>
181− <Grid>
182− <Grid.Item full as={ViewHeader}>
23+ <Grid.Item full as={ViewHeader}>
24+ Senate: Proposals
25+ </Grid.Item>
26+ <Grid className="pt2">
27+ <Grid.Item
28+ full
29+ as={ForwardButton}
30+ className="mt1"
31+ onClick={goDocuments}>
183− Senate: Document Proposals
32+ Document Proposals
184 </Grid.Item>
33 </Grid.Item>
185−
34+ <Grid.Divider />
186− <Grid.Item full as={Grid}>
35+ <Grid.Item full as={ForwardButton} className="mt1" onClick={goUpgrades}>
187− <Grid.Item full as={P}>
36+ Upgrade Proposals
188− Accepted documents:
189− </Grid.Item>
190− {majorityList}
191 </Grid.Item>
37 </Grid.Item>
192−
193− {openList.length > 0 && (
194− <>
195− <Grid.Divider className="mv4" />
196−
197− <Grid.Item full as={Grid}>
198− <Grid.Item full as={P}>
199− Open document polls:
200− </Grid.Item>
201− {openList}
202− </Grid.Item>
203−
204− <BridgeForm onValues={onDocumentChange}>
205− {({ onSubmit, values }) => (
206− // <>
207− <Grid.Item
208− full
209− as={InlineEthereumTransaction}
210− {...bind}
211− onReturn={didVote}
212− />
213− )}
214− </BridgeForm>
215− </>
216− )}
217 </Grid>
38 </Grid>
218 </View>
39 </View>
219 );
40 );
src/views/Senate/Documents.jsnew file+220
@@ -0,0 +1,220 @@
1+import React, { useCallback, useMemo, useState } from 'react';
2+import { Grid, Text, P, LinkButton } from 'indigo-react';
3+import * as azimuth from 'azimuth-js';
4+
5+import { useNetwork } from 'store/network';
6+import { usePointCursor } from 'store/pointCursor';
7+
8+import * as need from 'lib/need';
9+import useEthereumTransaction from 'lib/useEthereumTransaction';
10+import { GAS_LIMITS } from 'lib/constants';
11+import { useLocalRouter } from 'lib/LocalRouter';
12+
13+import ViewHeader from 'components/ViewHeader';
14+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
15+import View from 'components/View';
16+import BridgeForm from 'form/BridgeForm';
17+import convertToInt from 'lib/convertToInt';
18+
19+function useCastDocumentVote() {
20+ const { contracts } = useNetwork();
21+ const { pointCursor } = usePointCursor();
22+
23+ const _contracts = need.contracts(contracts);
24+ const _point = need.point(pointCursor);
25+
26+ return useEthereumTransaction(
27+ useCallback(
28+ (hash, accept) => {
29+ return azimuth.ecliptic.castDocumentVote(
30+ _contracts,
31+ _point,
32+ hash,
33+ accept
34+ );
35+ },
36+ [_contracts, _point]
37+ ),
38+ useCallback(() => null, []), //TODO update polls[hash].hasVoted ?
39+ GAS_LIMITS.DEFAULT //TODO update once we have a decent sample size
40+ );
41+}
42+
43+function renderDocumentHash(doc) {
44+ return doc.slice(0, 34) + '\n ' + doc.slice(34);
45+}
46+
47+export default function Documents() {
48+ const { pop } = useLocalRouter();
49+ const { contracts } = useNetwork();
50+ const { pointCursor } = usePointCursor();
51+
52+ const _contracts = need.contracts(contracts);
53+ const _point = convertToInt(need.point(pointCursor), 10);
54+
55+ const { construct, bind } = useCastDocumentVote();
56+
57+ // const [documentHash, setDocumentHash] = useState('0x...');
58+ const [majorities, setMajorities] = useState([]);
59+ const [proposals, setProposals] = useState([]);
60+ const [polls, setPolls] = useState({});
61+ const [loading, setLoading] = useState(true);
62+ const [votingOn, setVotingOn] = useState({});
63+
64+ //TODO update every second?
65+ const now = Math.round(new Date().getTime() / 1000);
66+
67+ useMemo(async () => {
68+ let proposals = await azimuth.polls.getDocumentProposals(_contracts);
69+ let majorities = await azimuth.polls.getDocumentMajorities(_contracts);
70+ let polls = {};
71+ for (let doc of proposals) {
72+ let poll = await azimuth.polls.getDocumentPoll(_contracts, doc);
73+ poll.endTime = convertToInt(poll.start) + convertToInt(poll.duration);
74+ poll.hasVoted = await azimuth.polls.hasVotedOnDocumentPoll(
75+ _contracts,
76+ _point,
77+ doc
78+ );
79+ polls[doc] = poll;
80+ }
81+ setMajorities(majorities);
82+ setProposals(proposals);
83+ setPolls(polls);
84+ setLoading(false);
85+ return;
86+ }, [_contracts, _point]);
87+
88+ // eslint-disable-next-line
89+ const [open, closed] = useMemo(() => {
90+ let open = [];
91+ let closed = [];
92+ if (loading) return [open, closed];
93+ for (let doc of proposals) {
94+ if (now > polls[doc].endTime) {
95+ closed.push(doc);
96+ } else {
97+ open.push(doc);
98+ }
99+ }
100+ return [open, closed];
101+ }, [loading, proposals, polls, now]);
102+
103+ const doVote = useCallback(
104+ (hash, accept) => {
105+ construct(hash, accept);
106+ setVotingOn({ hash, accept });
107+ },
108+ [construct, setVotingOn]
109+ );
110+
111+ const didVote = useCallback(() => {
112+ polls[votingOn].hasVoted = true;
113+ setPolls(polls);
114+ setVotingOn({});
115+ }, [polls, setPolls, votingOn]);
116+
117+ const majorityList = useMemo(() => {
118+ return majorities.map(doc => {
119+ return (
120+ <Grid.Item full className="mv2" as={P}>
121+ <a
122+ target="_blank"
123+ rel="noopener noreferrer"
124+ href={`https://raw.githubusercontent.com/urbit/azimuth/master/proposals/${doc}.txt`}
125+ class="mb4 mono wrap pre">
126+ {renderDocumentHash(doc)}↗
127+ </a>
128+ </Grid.Item>
129+ );
130+ });
131+ }, [majorities]);
132+
133+ const openList = useMemo(() => {
134+ return open.map(doc => {
135+ const hasVoted = polls[doc].hasVoted;
136+ return (
137+ <>
138+ <Grid.Item full className="mv2" as={Text}>
139+ <P className="m0 mono wrap pre">{renderDocumentHash(doc)}</P>
140+ <P className="mt0">
141+ {hasVoted ? (
142+ 'Your vote has been cast.'
143+ ) : (
144+ <>
145+ <LinkButton onClick={() => doVote(doc, true)}>
146+ {votingOn.hash === doc && votingOn.accept ? (
147+ <b>support</b>
148+ ) : (
149+ <>support</>
150+ )}
151+ </LinkButton>
152+ {' / '}
153+ <LinkButton onClick={() => doVote(doc, false)}>
154+ {votingOn.hash === doc && !votingOn.accept ? (
155+ <b>reject</b>
156+ ) : (
157+ <>reject</>
158+ )}
159+ </LinkButton>
160+ </>
161+ )}
162+ </P>
163+ </Grid.Item>
164+ <Grid.Divider />
165+ </>
166+ );
167+ });
168+ }, [open, polls, votingOn, doVote]);
169+
170+ const onDocumentChange = useCallback(({ valid, values }) => {
171+ // if (values.document) {
172+ // const hash = '0x' + keccak256(values.document.trim()).toString('hex');
173+ // setDocumentHash(hash);
174+ // } else {
175+ // setDocumentHash('0x...');
176+ // }
177+ }, []);
178+
179+ return (
180+ <View pop={pop} inset>
181+ <Grid>
182+ <Grid.Item full as={ViewHeader}>
183+ Senate: Document Proposals
184+ </Grid.Item>
185+
186+ <Grid.Item full as={Grid}>
187+ <Grid.Item full as={P}>
188+ Accepted documents:
189+ </Grid.Item>
190+ {majorityList}
191+ </Grid.Item>
192+
193+ {openList.length > 0 && (
194+ <>
195+ <Grid.Divider className="mv4" />
196+
197+ <Grid.Item full as={Grid}>
198+ <Grid.Item full as={P}>
199+ Open document polls:
200+ </Grid.Item>
201+ {openList}
202+ </Grid.Item>
203+
204+ <BridgeForm onValues={onDocumentChange}>
205+ {({ onSubmit, values }) => (
206+ // <>
207+ <Grid.Item
208+ full
209+ as={InlineEthereumTransaction}
210+ {...bind}
211+ onReturn={didVote}
212+ />
213+ )}
214+ </BridgeForm>
215+ </>
216+ )}
217+ </Grid>
218+ </View>
219+ );
220+}
src/views/Senate/Upgrades.jsnew file+195
@@ -0,0 +1,195 @@
1+import React, { useCallback, useMemo, useState } from 'react';
2+import { Grid, Text, P, LinkButton } from 'indigo-react';
3+import * as azimuth from 'azimuth-js';
4+
5+import { useNetwork } from 'store/network';
6+import { usePointCursor } from 'store/pointCursor';
7+
8+import * as need from 'lib/need';
9+import useEthereumTransaction from 'lib/useEthereumTransaction';
10+import { GAS_LIMITS } from 'lib/constants';
11+import { useLocalRouter } from 'lib/LocalRouter';
12+
13+import ViewHeader from 'components/ViewHeader';
14+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
15+import View from 'components/View';
16+import BridgeForm from 'form/BridgeForm';
17+import convertToInt from 'lib/convertToInt';
18+
19+function useCastUpgradeVote() {
20+ const { contracts } = useNetwork();
21+ const { pointCursor } = usePointCursor();
22+
23+ const _contracts = need.contracts(contracts);
24+ const _point = need.point(pointCursor);
25+
26+ return useEthereumTransaction(
27+ useCallback(
28+ (address, accept) => {
29+ return azimuth.ecliptic.castUpgradeVote(
30+ _contracts,
31+ _point,
32+ address,
33+ accept
34+ );
35+ },
36+ [_contracts, _point]
37+ ),
38+ useCallback(() => null, []), //TODO update polls[address].hasVoted ?
39+ GAS_LIMITS.DEFAULT //TODO update once we have a decent sample size
40+ );
41+}
42+
43+export default function Upgrades() {
44+ const { pop } = useLocalRouter();
45+ const { contracts } = useNetwork();
46+ const { pointCursor } = usePointCursor();
47+
48+ const _contracts = need.contracts(contracts);
49+ const _point = convertToInt(need.point(pointCursor), 10);
50+
51+ const { construct, bind } = useCastUpgradeVote();
52+
53+ const [proposals, setProposals] = useState([]);
54+ const [polls, setPolls] = useState({});
55+ const [loading, setLoading] = useState(true);
56+ const [votingOn, setVotingOn] = useState({});
57+
58+ //TODO update every second?
59+ const now = Math.round(new Date().getTime() / 1000);
60+
61+ useMemo(async () => {
62+ let proposals = await azimuth.polls.getUpgradeProposals(_contracts);
63+ let polls = {};
64+ for (let address of proposals) {
65+ let poll = await azimuth.polls.getUpgradePoll(_contracts, address);
66+ poll.endTime = convertToInt(poll.start) + convertToInt(poll.duration);
67+ poll.hasVoted = await azimuth.polls.hasVotedOnUpgradePoll(
68+ _contracts,
69+ _point,
70+ address
71+ );
72+ polls[address] = poll;
73+ }
74+ setProposals(proposals);
75+ setPolls(polls);
76+ setLoading(false);
77+ return;
78+ }, [_contracts, _point]);
79+
80+ // eslint-disable-next-line
81+ const [open, closed] = useMemo(() => {
82+ let open = [];
83+ let closed = [];
84+ if (loading) return [open, closed];
85+ for (let address of proposals) {
86+ if (now > polls[address].endTime) {
87+ closed.push(address);
88+ } else {
89+ open.push(address);
90+ }
91+ }
92+ return [open, closed];
93+ }, [loading, proposals, polls, now]);
94+
95+ const doVote = useCallback(
96+ (address, accept) => {
97+ construct(address, accept);
98+ setVotingOn({ address, accept });
99+ },
100+ [construct, setVotingOn]
101+ );
102+
103+ const didVote = useCallback(() => {
104+ polls[votingOn].hasVoted = true;
105+ setPolls(polls);
106+ setVotingOn({});
107+ }, [polls, setPolls, votingOn]);
108+
109+ const openList = useMemo(() => {
110+ return open.map(address => {
111+ const hasVoted = polls[address].hasVoted;
112+ return (
113+ <>
114+ <Grid.Item full className="mv2" as={Text}>
115+ <a
116+ target="_blank"
117+ rel="noopener noreferrer"
118+ href={`https://etherscan.io/address/${address}#code`}
119+ class="mb4 mono wrap pre">
120+ {address}↗
121+ </a>
122+ <P className="mt0">
123+ {hasVoted ? (
124+ 'Your vote has been cast.'
125+ ) : (
126+ <>
127+ <LinkButton onClick={() => doVote(address, true)}>
128+ {votingOn.address === address && votingOn.accept ? (
129+ <b>support</b>
130+ ) : (
131+ <>support</>
132+ )}
133+ </LinkButton>
134+ {' / '}
135+ <LinkButton onClick={() => doVote(address, false)}>
136+ {votingOn.address === address && !votingOn.accept ? (
137+ <b>reject</b>
138+ ) : (
139+ <>reject</>
140+ )}
141+ </LinkButton>
142+ </>
143+ )}
144+ </P>
145+ </Grid.Item>
146+ <Grid.Divider />
147+ </>
148+ );
149+ });
150+ }, [open, polls, votingOn, doVote]);
151+
152+ return (
153+ <View pop={pop} inset>
154+ <Grid>
155+ <Grid.Item full as={ViewHeader}>
156+ Senate: Upgrade Proposals
157+ </Grid.Item>
158+
159+ <Grid.Item full as={Grid}>
160+ <Grid.Item full as={P}>
161+ Current Ecliptic:
162+ </Grid.Item>
163+ <Grid.Item full className="mv2 mb4 mono wrap pre" as={P}>
164+ {_contracts.ecliptic.address}
165+ </Grid.Item>
166+ </Grid.Item>
167+
168+ {openList.length > 0 && (
169+ <>
170+ <Grid.Divider className="mv4" />
171+
172+ <Grid.Item full as={Grid}>
173+ <Grid.Item full as={P}>
174+ Open upgrade polls:
175+ </Grid.Item>
176+ {openList}
177+ </Grid.Item>
178+
179+ <BridgeForm>
180+ {({ onSubmit, values }) => (
181+ // <>
182+ <Grid.Item
183+ full
184+ as={InlineEthereumTransaction}
185+ {...bind}
186+ onReturn={didVote}
187+ />
188+ )}
189+ </BridgeForm>
190+ </>
191+ )}
192+ </Grid>
193+ </View>
194+ );
195+}