on master help sign in
inviter: create deterministic tickets
commit673ee7db3924165349afff1bdd18f89c9010f0b5 authorLiam Fitzgerald <liam@urizen.co> dateWed 8 Apr 2020, 11:07 (7 years ago) parent86298d7
5 files changed+187−26
src/lib/useInviter.js+8−6 src/lib/walletgen.js+8−1 src/store/lib/useInvitesStore.js+2−1 src/store/wallet.js+1−1 src/views/Invite/Cohort.js+168−17
src/lib/useInviter.js+8−6
@@ -35,10 +35,11 @@ const STATUS = {
3535
3636const useInviter = () => {
3737 const { contracts, web3, networkType } = useNetwork();
38− const { wallet, walletType, walletHdPath } = useWallet();
38+ const { wallet, walletType, walletHdPath, authToken } = useWallet();
3939 const { pointCursor } = usePointCursor();
4040 const { syncInvites } = usePointCache();
4141 const { gasPrice } = useGasPrice();
42+
4243 const point = need.point(pointCursor);
4344
4445 const [needFunds, setNeedFunds] = useState(null);
@@ -65,7 +66,8 @@ const useInviter = () => {
6566 const _contracts = contracts.getOrElse(null);
6667 const _web3 = web3.getOrElse(null);
6768 const _wallet = wallet.getOrElse(null);
68− if (!_contracts || !_web3 || !_wallet) {
69+ const _authToken = authToken.getOrElse(null);
70+ if (!_contracts || !_web3 || !_wallet || !_authToken) {
6971 // not using need because we want a custom error
7072 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
7173 }
@@ -104,10 +106,10 @@ const useInviter = () => {
104106 try {
105107 const planet = planets[i];
106108
107− const { ticket, owner } = await wg.generateTemporaryTicketAndWallet(
108− MIN_PLANET
109− // we're always giving planets, so generate a ticket of the correct size
110− );
109+ const {
110+ ticket,
111+ owner,
112+ } = await wg.generateTemporaryDeterministicWallet(planet, _authToken);
111113
112114 const inviteTx = azimuth.delegatedSending.sendPoint(
113115 _contracts,
src/lib/walletgen.js+8−1
@@ -56,7 +56,7 @@ export const makeDeterministicTicket = (point, seed) => {
5656 Buffer.from(point.toString()),
5757 Buffer.from('invites'),
5858 ]);
59− const entropy = shas(seed, pointSalt);
59+ const entropy = shas(Buffer.from(seed, 'hex'), pointSalt);
6060
6161 const buf = entropy.slice(0, bytes);
6262 const patq = ob.hex2patq(buf.toString('hex'));
@@ -97,3 +97,10 @@ export const generateTemporaryTicketAndWallet = async point => {
9797
9898 return { ticket, owner };
9999};
100+
101+export const generateTemporaryDeterministicWallet = async (point, seed) => {
102+ const ticket = makeDeterministicTicket(point, seed);
103+ const owner = await generateOwnershipWallet(ZOD, ticket);
104+
105+ return { ticket, owner };
106+};
src/store/lib/useInvitesStore.js+2−1
@@ -51,7 +51,8 @@ export default function useInvitesStore() {
5151 active: !!active,
5252 }))
5353 .partition('active')
54− .map(arr => arr.map(x => x.point));
54+ .map(arr => arr.map(x => x.point))
55+ .value();
5556
5657 addToInvitesCache({
5758 [point]: {
src/store/wallet.js+1−1
@@ -73,7 +73,7 @@ function _useWallet(initialWallet = Nothing(), initialMnemonic = Nothing()) {
7373 web3: _web3,
7474 });
7575
76− setAuthToken(token);
76+ setAuthToken(Just(token));
7777 })();
7878 }, [wallet, walletType, walletHdPath, web3]);
7979
src/views/Invite/Cohort.js+168−17
@@ -1,11 +1,12 @@
11import React, { useMemo, useEffect, useCallback, useState } from 'react';
22import { Grid, Flex, Button } from 'indigo-react';
33import * as ob from 'urbit-ob';
4−import { Just } from 'folktale/maybe';
4+import { Just, Nothing } from 'folktale/maybe';
55import cn from 'classnames';
66
77import { usePointCursor } from 'store/pointCursor';
88import { usePointCache } from 'store/pointCache';
9+import { useWallet } from 'store/wallet';
910
1011import View from 'components/View';
1112import Tabs from 'components/Tabs';
@@ -19,10 +20,13 @@ import Crumbs from 'components/Crumbs';
1920import { useLocalRouter } from 'lib/LocalRouter';
2021import useInvites from 'lib/useInvites';
2122import * as need from 'lib/need';
23+import * as wg from 'lib/walletgen';
2224
2325import Inviter from 'views/Invite/Inviter';
2426
2527import { ReactComponent as SearchIcon } from 'assets/search.svg';
28+import CopyButton from 'components/CopyButton';
29+import { useSyncForeignPoints } from 'lib/useSyncPoints';
2630
2731function SearchInput({ className, value, onChange }) {
2832 return (
@@ -42,7 +46,7 @@ function SearchInput({ className, value, onChange }) {
4246 );
4347}
4448
45−function CohortMember({ point, pending = false, className }) {
49+function CohortMember({ point, pending = false, className, ...rest }) {
4650 const { sentInvites } = useInvites(point);
4751 const patp = useMemo(() => ob.patp(point), [point]);
4852 const colors = pending ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
@@ -54,7 +58,7 @@ function CohortMember({ point, pending = false, className }) {
5458 );
5559
5660 return (
57− <Flex justify="between" className={cn('b1 b-gray2', className)}>
61+ <Flex justify="between" className={cn('b1 b-gray2', className)} {...rest}>
5862 <Flex.Item className="w9 h9">
5963 <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
6064 </Flex.Item>
@@ -73,7 +77,13 @@ function CohortMember({ point, pending = false, className }) {
7377 </Flex>
7478 );
7579}
76−function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
80+function CohortList({
81+ acceptedPoints,
82+ pendingPoints,
83+ className,
84+ onlyPending,
85+ onSelectInvite,
86+}) {
7787 const { syncInvites } = usePointCache();
7888
7989 const [query, setQuery] = useState('');
@@ -98,7 +108,12 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
98108 }),
99109 [query]
100110 );
101−
111+ const selectInvite = useCallback(
112+ point => () => {
113+ onSelectInvite(point);
114+ },
115+ [onSelectInvite]
116+ );
102117 const _pendingPoints = filterPoints(pendingPoints);
103118 const _acceptedPoints = filterPoints(acceptedPoints);
104119
@@ -115,6 +130,7 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
115130 half={(idx % 2) + 1}
116131 as={CohortMember}
117132 point={p}
133+ onClick={selectInvite(p)}
118134 />
119135 ))}
120136 {_pendingPoints.map((p, idx) => (
@@ -124,6 +140,7 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
124140 as={CohortMember}
125141 point={p}
126142 pending
143+ onClick={selectInvite(p)}
127144 />
128145 ))}
129146 </>
@@ -139,12 +156,122 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
139156 );
140157}
141158
159+function hideIf(hider, hidden) {
160+ return hider ? '●●●●●●●' : hidden;
161+}
162+
163+function CohortMemberExpanded({ point, className, ...rest }) {
164+ const { sentInvites, acceptedInvites } = useInvites(point);
165+ const { getDetails } = usePointCache();
166+ const { authToken } = useWallet();
167+ const details = getDetails(point).getOrElse({});
168+ const { active } = details;
169+ const patp = useMemo(() => ob.patp(point), [point]);
170+ const colors = !active ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
171+
172+ const [code, setCode] = useState(Nothing());
173+ const [codeVisible, setCodeVisible] = useState(false);
174+
175+ useEffect(() => {
176+ const fetchWallet = async () => {
177+ const _authToken = authToken.getOrElse(null);
178+ if (!_authToken || active) {
179+ return;
180+ }
181+ const ticket = await wg.makeDeterministicTicket(point, _authToken);
182+ setCode(Just(ticket));
183+ };
184+ fetchWallet();
185+ }, [authToken, point, active]);
186+
187+ const DetailText = useCallback(
188+ () =>
189+ !active ? 'Pending' : <> {matchBlinky(sentInvites)} points invited </>,
190+ [active, sentInvites]
191+ );
192+
193+ return (
194+ <Grid justify="between" className={cn('b1 b-gray2', className)} {...rest}>
195+ <Grid.Item rows={[1, 4]} cols={[1, 4]} className="h10 w10">
196+ <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
197+ </Grid.Item>
198+
199+ <Grid.Item cols={[4, 13]} className="mono mt-auto ">
200+ {patp}
201+ </Grid.Item>
202+
203+ {!active && (
204+ <>
205+ <Grid.Item
206+ cols={[4, 13]}
207+ className="mt-auto mb1 f6 gray4"
208+ as={Flex}
209+ col>
210+ Invite Code
211+ </Grid.Item>
212+ <Grid.Item
213+ cols={[4, 10]}
214+ className="mb-auto mt1 f6 gray4"
215+ as={Flex}
216+ col
217+ onClick={() => setCodeVisible(r => !r)}>
218+ {hideIf(!codeVisible, matchBlinky(code))}
219+ </Grid.Item>
220+ <Grid.Item
221+ className="mh-auto f6"
222+ cols={[10, 13]}
223+ as={CopyButton}
224+ text={code.getOrElse('')}
225+ />
226+ </>
227+ )}
228+
229+ {active && (
230+ <>
231+ <Grid.Item
232+ cols={[4, 13]}
233+ className="mt-auto mb1 f6 gray4"
234+ as={Flex}
235+ col>
236+ {matchBlinky(sentInvites)} invites sent
237+ </Grid.Item>
238+ <Grid.Item
239+ cols={[4, 13]}
240+ className="mb-auto mt1 f6 gray4"
241+ as={Flex}
242+ col>
243+ {matchBlinky(acceptedInvites)} invites accepted
244+ </Grid.Item>
245+ </>
246+ )}
247+
248+ {/* <Flex.Item>Invite Code</Flex.Item> */}
249+ {/* <Flex.Item>{matchBlinky(code)}</Flex.Item> */}
250+ {/* <span className="pointer underline">Get Invite code </span> */}
251+ {/* <br /> */}
252+ {/* {resend && <span>{matchBlinky(code)}</span>} */}
253+ {/* </Grid.Item> */}
254+ {/* {resend && ( */}
255+ {/* <Grid.Item cols={[5, 13]} rows={[2, 3]} className="mv-auto"></Grid.Item> */}
256+ {/* )} */}
257+ {/* <Grid.Item justify="evenly" as={Flex} col className="mono f6 ph4"> */}
258+ {/* <Flex.Item>{patp}</Flex.Item> */}
259+ {/* <Flex.Item className="gray4"> */}
260+ {/* <DetailText /> */}
261+ {/* </Flex.Item> */}
262+ {/* </Grid.Item> */}
263+ </Grid>
264+ );
265+}
266+
142267export default function InviteCohort() {
143268 const { pop } = useLocalRouter();
144269
145270 const { pointCursor } = usePointCursor();
146271 const point = need.point(pointCursor);
147272
273+ const { syncDetails } = usePointCache();
274+
148275 const {
149276 acceptedInvites,
150277 acceptedPoints,
@@ -164,6 +291,17 @@ export default function InviteCohort() {
164291 .map(a => sentInvites.getOrElse(0) + a)
165292 .getOrElse(0);
166293
294+ const [selectedInvite, _setSelectedInvite] = useState();
295+
296+ useEffect(() => {
297+ Promise.all(_acceptedPoints.map(i => syncDetails(i)));
298+ }, [_acceptedPoints, syncDetails]);
299+
300+ const setSelectedInvite = useCallback(
301+ p => _setSelectedInvite(old => (old === p ? null : p)),
302+ []
303+ );
304+
167305 return (
168306 <View pop={pop} inset>
169307 <Grid gap={3}>
@@ -209,18 +347,31 @@ export default function InviteCohort() {
209347 )}
210348 {showInviteForm && <Inviter />}
211349
212− <Grid.Item
213− full
214− center
215− as={Tabs}
216− views={VIEWS}
217− options={OPTIONS}
218− currentTab={tab}
219− onTabChange={setTab}
220− acceptedPoints={_acceptedPoints}
221− pendingPoints={_pendingPoints}
222− onlyPending={tab === NAMES.PENDING}
223− />
350+ {true && (
351+ <Grid.Item
352+ full
353+ center
354+ as={Tabs}
355+ views={VIEWS}
356+ options={OPTIONS}
357+ currentTab={tab}
358+ onTabChange={setTab}
359+ acceptedPoints={_acceptedPoints}
360+ pendingPoints={_pendingPoints}
361+ onlyPending={tab === NAMES.PENDING}
362+ onSelectInvite={setSelectedInvite}
363+ />
364+ )}
365+ {selectedInvite && (
366+ <Grid.Item
367+ full
368+ center
369+ as={CohortMemberExpanded}
370+ pending
371+ point={selectedInvite}
372+ onBack={() => setSelectedInvite(null)}
373+ />
374+ )}
224375 </Grid>
225376 </View>
226377 );