on master help sign in
Invites working fairly well
commit40027147eeb70bf738e5f2985c3c748c72ca649e authorWill Galebach <willbach@gmail.com> dateFri 3 Sep 2021, 17:33 (5 years ago) parentbab2fa8
18 files changed+357−401
src/components/InlineEthereumTransaction.js+42−12 src/components/InlineEthereumTransaction.scss+5 src/components/L2/Dropdowns/AccountsDropdown.tsx+5−1 src/components/L2/Dropdowns/Dropdown.scss+3 src/components/L2/Headers/L2BackHeader.tsx+29−4 src/components/L2/Paginator.scss+1−1 src/components/MiniBackButton.js+4−1 src/components/MiniBackButton.scss+10 src/lib/useEthereumTransaction.js+4 src/lib/useRoller.ts+52−12 src/store/lib/useInvitesStore.js−2 src/store/storage/roller.ts+2−1 src/views/Invite/Cohort.js+164−318 src/views/Invite/Cohort.scss+11−1 src/views/IssueChild.js+2−1 src/views/Point.js+18−46 src/views/Points.js+2−1 src/views/UrbitID/Home.js+3
src/components/InlineEthereumTransaction.js+42−12
@@ -28,6 +28,8 @@ import { convertToInt } from 'lib/convertToInt';
2828import NeedFundsNotice from './NeedFundsNotice';
2929import NoticeBox from './NoticeBox';
3030
31+import './InlineEthereumTransaction.scss';
32+
3133export default function InlineEthereumTransaction({
3234 // from useEthereumTransaction.bind
3335 initializing,
@@ -101,7 +103,12 @@ export default function InlineEthereumTransaction({
101103 const renderPrimarySection = () => {
102104 if (error) {
103105 return (
104− <Grid.Item full as={RestartButton} solid onClick={() => reset()}>
106+ <Grid.Item
107+ className="eth-tx-button"
108+ full
109+ as={RestartButton}
110+ solid
111+ onClick={() => reset()}>
105112 Reset Transaction
106113 </Grid.Item>
107114 );
@@ -109,7 +116,11 @@ export default function InlineEthereumTransaction({
109116 return (
110117 <>
111118 <Grid.Divider />
112− <Grid.Item full as={RestartButton} onClick={onReturn}>
119+ <Grid.Item
120+ className="eth-tx-button"
121+ full
122+ as={RestartButton}
123+ onClick={onReturn}>
113124 Return
114125 </Grid.Item>
115126 <Grid.Divider />
@@ -134,6 +145,7 @@ export default function InlineEthereumTransaction({
134145 } else if (showBroadcastButton) {
135146 return (
136147 <Grid.Item
148+ className="eth-tx-button"
137149 full
138150 as={ForwardButton}
139151 solid
@@ -146,6 +158,7 @@ export default function InlineEthereumTransaction({
146158 } else if (showLoadingButton) {
147159 return (
148160 <Grid.Item
161+ className="eth-tx-button"
149162 full
150163 as={ProgressButton}
151164 success
@@ -157,6 +170,7 @@ export default function InlineEthereumTransaction({
157170 } else {
158171 return (
159172 <Grid.Item
173+ className="eth-tx-button"
160174 full
161175 as={GenerateButton}
162176 onClick={generateAndSign}
@@ -207,13 +221,15 @@ export default function InlineEthereumTransaction({
207221 {renderPrimarySection()}
208222
209223 {error && (
210− <Grid.Item full as={ErrorText} className="mv1">
224+ <Grid.Item
225+ className="eth-tx-button" full as={ErrorText} className="mv1">
211226 {error.message}
212227 </Grid.Item>
213228 )}
214229
215230 {needFunds && (
216231 <Grid.Item
232+ className="eth-tx-button"
217233 full
218234 as={NeedFundsNotice}
219235 className="mt3"
@@ -224,6 +240,7 @@ export default function InlineEthereumTransaction({
224240 {showConfigureInput && (
225241 <>
226242 <Grid.Item
243+ className="eth-tx-button"
227244 full
228245 as={ToggleInput}
229246 name="useAdvanced"
@@ -234,9 +251,11 @@ export default function InlineEthereumTransaction({
234251 />
235252
236253 <Condition when="useAdvanced" is={true}>
237− <Grid.Item full className="mb2"></Grid.Item>
254+ <Grid.Item
255+ className="eth-tx-button" full className="mb2"></Grid.Item>
238256 <Grid.Divider />
239257 <Grid.Item
258+ className="eth-tx-button"
240259 full
241260 as={Flex}
242261 row
@@ -255,6 +274,7 @@ export default function InlineEthereumTransaction({
255274
256275 {/* TODO(shrugs): move to indigo/RangeInput */}
257276 <Grid.Item
277+ className="eth-tx-button"
258278 full
259279 as="input"
260280 type="range"
@@ -266,6 +286,7 @@ export default function InlineEthereumTransaction({
266286 }
267287 />
268288 <Grid.Item
289+ className="eth-tx-button"
269290 full
270291 as={Flex}
271292 row
@@ -281,7 +302,8 @@ export default function InlineEthereumTransaction({
281302
282303 {showSignedTx && fakeSigned && (
283304 <>
284− <Grid.Item full as={NoticeBox} className="mt2">
305+ <Grid.Item
306+ className="eth-tx-button" full as={NoticeBox} className="mt2">
285307 Your wallet will sign the transaction upon sending it.
286308 </Grid.Item>
287309 </>
@@ -290,6 +312,7 @@ export default function InlineEthereumTransaction({
290312 {showSignedTx && !fakeSigned && (
291313 <>
292314 <Grid.Item
315+ className="eth-tx-button"
293316 full
294317 as={ToggleInput}
295318 name="viewSigned"
@@ -317,21 +340,25 @@ function SignedTransactionList({ serializedTxsHex, nonce, maxCost }) {
317340 return serializedTxsHex.map((serializedTxHex, i) => (
318341 <React.Fragment key={i}>
319342 <Grid.Divider />
320− <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
343+ <Grid.Item
344+ className="eth-tx-button" full as={Flex} justify="between" className="pv4 black f5">
321345 <Flex.Item>Nonce</Flex.Item>
322346 <Flex.Item>{nonce + i}</Flex.Item>
323347 </Grid.Item>
324348 <Grid.Divider />
325− <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
349+ <Grid.Item
350+ className="eth-tx-button" full as={Flex} justify="between" className="pv4 black f5">
326351 <Flex.Item>Transaction Cost</Flex.Item>
327352 <Flex.Item>{maxCost} ETH</Flex.Item>
328353 </Grid.Item>
329354 <Grid.Divider />
330− <Grid.Item full as={Flex} justify="between" className="mt3 mb2">
355+ <Grid.Item
356+ className="eth-tx-button" full as={Flex} justify="between" className="mt3 mb2">
331357 <Flex.Item as={H5}>Signed Transaction Hex</Flex.Item>
332358 <Flex.Item as={CopyButton} text={serializedTxHex} />
333359 </Grid.Item>
334− <Grid.Item full as="code" className="mb4 f6 mono gray4 wrap">
360+ <Grid.Item
361+ className="eth-tx-button" full as="code" className="mb4 f6 mono gray4 wrap">
335362 {serializedTxHex}
336363 </Grid.Item>
337364 <Grid.Divider />
@@ -343,18 +370,21 @@ function TransactionReceipt({ txHashes, finalCost, onClose }) {
343370 const txUrls = useExploreTxUrls(txHashes);
344371 return (
345372 <>
346− <Grid.Item full as={Flex} justify="between" className="pv2 black f5">
373+ <Grid.Item
374+ className="eth-tx-button" full as={Flex} justify="between" className="pv2 black f5">
347375 <Flex.Item>Receipt</Flex.Item>
348376 <Flex.Item onClick={onClose} className="underline pointer">
349377 Close
350378 </Flex.Item>
351379 </Grid.Item>
352− <Grid.Item full as={Flex} justify="between" className="pv2 black f5">
380+ <Grid.Item
381+ className="eth-tx-button" full as={Flex} justify="between" className="pv2 black f5">
353382 <Flex.Item>Transaction Cost</Flex.Item>
354383 <Flex.Item className="mono">{finalCost} ETH</Flex.Item>
355384 </Grid.Item>
356385 <Grid.Divider />
357− <Grid.Item full as={Flex} col className="pv4">
386+ <Grid.Item
387+ className="eth-tx-button" full as={Flex} col className="pv4">
358388 <Flex.Item as={H5}>Receipt</Flex.Item>
359389
360390 {txHashes &&
src/components/InlineEthereumTransaction.scssnew file+5
@@ -0,0 +1,5 @@
1+@import 'style/shared/variables.scss';
2+
3+.eth-tx-button {
4+ border-radius: $medium-border-radius;
5+}
src/components/L2/Dropdowns/AccountsDropdown.tsx+5−1
@@ -9,6 +9,7 @@ import { useWallet } from 'store/wallet';
99import { useHistory } from 'store/history';
1010import { usePointCursor } from 'store/pointCursor';
1111import { usePointCache } from 'store/pointCache';
12+import { clearInvitesStorage } from 'store/storage/roller';
1213import CopiableAddressWrap from 'components/copiable/CopiableAddressWrap';
1314import Dropdown from './Dropdown';
1415
@@ -39,7 +40,10 @@ const AccountsDropdown = () => {
3940 };
4041 const goToMigrate = () => push(names.MIGRATE);
4142 const goToBitcoin = () => push(names.BITCOIN);
42− const onLogout = reset;
43+ const onLogout = () => {
44+ clearInvitesStorage();
45+ reset();
46+ };
4347
4448 return (
4549 <Dropdown
src/components/L2/Dropdowns/Dropdown.scss+3
@@ -31,12 +31,15 @@
3131 top: 36px;
3232 left: -3px;
3333 border-radius: $medium-border-radius;
34+ z-index: 1;
3435
3536 .content {
3637 border-radius: $medium-border-radius;
3738 background-color: white;
3839 border: 1px solid $light-gray;
3940 width: 240px;
41+ max-height: 80vh;
42+ overflow-y: scroll;
4043 }
4144 }
4245}
src/components/L2/Headers/L2BackHeader.tsx+29−4
@@ -1,4 +1,4 @@
1−import React, { useCallback, useEffect } from 'react';
1+import React, { useCallback, useEffect, useState } from 'react';
22import { Row, Icon } from '@tlon/indigo-react';
33
44import useRoller from 'lib/useRoller';
@@ -6,15 +6,24 @@ import { useHistory } from 'store/history';
66import { useRollerStore } from 'store/roller';
77
88import './L2BackHeader.scss';
9+import { useNetwork } from 'store/network';
10+import { useWallet } from 'store/wallet';
11+import { toBN } from 'web3-utils';
12+import BN from 'bn.js';
913
1014export interface L2BackHeaderProps {
1115 back?: () => void;
16+ hideBalance?: boolean;
1217}
1318
14−const L2BackHeader = ({ back }: L2BackHeaderProps) => {
19+const L2BackHeader = ({ back, hideBalance = false }: L2BackHeaderProps) => {
1520 const { config } = useRoller();
1621 const { nextRoll, currentL2 } = useRollerStore(store => store);
1722 const { pop }: any = useHistory();
23+ const { wallet }: any = useWallet();
24+ const { web3 }: any = useNetwork();
25+
26+ const [ethBalance, setEthBalance] = useState<BN>(toBN(0));
1827
1928 const goBack = useCallback(() => {
2029 if (back) {
@@ -28,7 +37,21 @@ const L2BackHeader = ({ back }: L2BackHeaderProps) => {
2837 console.log('loaded config in L2BackHeader:', config);
2938 }, [config]);
3039
31− const ethBalance = 0.032;
40+ useEffect(() => {
41+ const getEthBalance = async () => {
42+ const _web3 = web3.getOrElse(null);
43+ const _wallet = wallet.getOrElse(null);
44+
45+ if (_web3 && _wallet) {
46+ const newBalance = toBN(await _web3.eth.getBalance(_wallet.address));
47+ setEthBalance(newBalance);
48+ }
49+ };
50+
51+ if (!currentL2) {
52+ getEthBalance();
53+ }
54+ }, [currentL2, setEthBalance, wallet, web3]);
3255
3356 return (
3457 <Row className="l2-back-header">
@@ -39,7 +62,9 @@ const L2BackHeader = ({ back }: L2BackHeaderProps) => {
3962 {nextRoll}
4063 </div>
4164 ) : (
42− <div className="eth-balance">Balance: {ethBalance} ETH</div>
65+ !hideBalance && (
66+ <div className="eth-balance">Balance: {Number(ethBalance)} ETH</div>
67+ )
4368 )}
4469 </Row>
4570 );
src/components/L2/Paginator.scss+1−1
@@ -6,9 +6,9 @@
66 justify-content: center;
77 align-items: center;
88 width: 100%;
9+ margin-top: 8px;
910
1011 .info {
11− margin-top: 8px;
1212 width: 40px;
1313 text-align: center;
1414 }
src/components/MiniBackButton.js+4−1
@@ -1,11 +1,14 @@
11import React from 'react';
22import cn from 'classnames';
3+import { Icon } from '@tlon/indigo-react';
34import { IconButton } from 'indigo-react';
45
6+import './MiniBackButton.scss';
7+
58export const MiniBackButton = ({ className, isExit = false, ...rest }) => {
69 return (
710 <IconButton className={cn('black', className)} {...rest}>
8− {'<-'}
11+ <Icon icon="ChevronWest" className="back-button" />
912 </IconButton>
1013 );
1114};
src/components/MiniBackButton.scssnew file+10
@@ -0,0 +1,10 @@
1+@import 'style/shared/variables.scss';
2+
3+svg.back-button {
4+ padding: 6px 9px;
5+ width: 14px;
6+ height: 20px;
7+ background-color: $washedGray;
8+ border-radius: $medium-border-radius;
9+ cursor: pointer;
10+}
src/lib/useEthereumTransaction.js+4
@@ -144,14 +144,17 @@ export default function useEthereumTransaction(
144144 );
145145
146146 setSignedTransactions(txns);
147+ console.log(2, txns)
147148
148149 // Per return type of `signTransaction`, tx is either a Transaction or FakeSignedTx.
149150 // We check the negation in this case, since TS cannot perform run-time check of type
150151 // FakeSignedTx, but it can check for an instance of the ethereumjs/tx class.
151152 if (txns.some(tx => !(tx instanceof Transaction))) {
152153 setState(STATE.FAKE_SIGNED);
154+ console.log(3)
153155 } else {
154156 setState(STATE.SIGNED);
157+ console.log(4)
155158 }
156159 } catch (error) {
157160 console.error('error signing txs', error);
@@ -174,6 +177,7 @@ export default function useEthereumTransaction(
174177
175178 const broadcast = useCallback(async () => {
176179 try {
180+ console.log(5)
177181 setConfirmationProgress(0.0);
178182 setError(undefined);
179183 setState(STATE.BROADCASTED);
src/lib/useRoller.ts+52−12
@@ -3,6 +3,7 @@ import * as wg from 'lib/walletgen';
33import * as need from 'lib/need';
44import * as azimuth from 'azimuth-js';
55import * as ob from 'urbit-ob';
6+import { Just, Nothing } from 'folktale/maybe';
67
78import {
89 Config,
@@ -24,9 +25,13 @@ import { usePointCursor } from 'store/pointCursor';
2425import { useNetwork } from 'store/network';
2526import { signTransactionHash } from './authToken';
2627import { Invite } from 'types/Invite';
27−import { getStoredInvites, setPendingInvites, setStoredInvites } from 'store/storage/roller';
28+import {
29+ getStoredInvites,
30+ setPendingInvites,
31+ setStoredInvites,
32+} from 'store/storage/roller';
2833import { usePointCache } from 'store/pointCache';
29−import { maybeGetResult } from 'views/Points';
34+import { hasTransferProxy, maybeGetResult } from 'views/Points';
3035
3136const hasPoint = (point: number) => (invite: Invite) => invite.planet === point;
3237
@@ -50,11 +55,12 @@ const getProxyAndNonce = (
5055 : { proxy: undefined, nonce: undefined };
5156
5257export default function useRoller() {
53− const { wallet, walletType, walletHdPath, authToken }: any = useWallet();
58+ const { wallet, authToken }: any = useWallet();
5459 const { pointCursor }: any = usePointCursor();
5560 const { web3, contracts }: any = useNetwork();
5661 const allPoints: any = usePointCache();
5762 const controlledPoints = allPoints?.controlledPoints;
63+ const getDetails = allPoints?.getDetails;
5864
5965 const {
6066 nextBatchTime,
@@ -62,6 +68,7 @@ export default function useRoller() {
6268 setNextRoll,
6369 setPendingTransactions,
6470 setInvites,
71+ currentL2,
6572 } = useRollerStore();
6673 const [config, setConfig] = useState<Config | null>(null);
6774
@@ -229,7 +236,7 @@ export default function useRoller() {
229236
230237 const stillPending = invites.pending.filter(invite => {
231238 const completed = !pendingTransactions.find(
232− ({ tx }) => tx.tx?.data?.ship === invite.planet
239+ p => p?.rawTx?.tx?.tx?.data?.ship === invite.planet
233240 );
234241
235242 if (completed) {
@@ -246,12 +253,43 @@ export default function useRoller() {
246253 });
247254 setInvites(availableInvites);
248255
249− const allSpawned = await api.getSpawned(Number(curPoint));
250− const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
251− const spawnedAndOwned = ownedPoints.filter(
252− (p: number) => allSpawned.includes(p) && p > 2000
253− );
254− console.log('SPAWNED AND OWNED', spawnedAndOwned);
256+ let possibleMissingInvites: number[] = [];
257+
258+ if (currentL2) {
259+ const allSpawned = await api.getSpawned(Number(curPoint));
260+ const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
261+ possibleMissingInvites = ownedPoints.filter(
262+ (p: number) =>
263+ allSpawned.includes(p) &&
264+ azimuth.azimuth.getPointSize(p) === azimuth.azimuth.PointSize.Planet
265+ );
266+ console.log('SPAWNED AND OWNED', possibleMissingInvites);
267+ } else {
268+ const maybeOutgoingPoints = controlledPoints.chain((points: any) =>
269+ points.matchWith({
270+ Error: () => Nothing(),
271+ Ok: (c: any) => {
272+ const points = c.value.ownedPoints.map((point: number) =>
273+ getDetails(point).chain((details: any) =>
274+ Just({ point: point, has: hasTransferProxy(details) })
275+ )
276+ );
277+ // if we have details for every point,
278+ // return the array of pending transfers.
279+ if (points.every((p: any) => Just.hasInstance(p))) {
280+ const outgoing = points
281+ .filter((p: any) => p.value.has)
282+ .map((p: any) => p.value.point);
283+ return Just(outgoing);
284+ } else {
285+ return Nothing();
286+ }
287+ },
288+ })
289+ );
290+
291+ possibleMissingInvites = maybeOutgoingPoints.getOrElse([]);
292+ }
255293
256294 // Iterate over all spawned and controlled planets
257295 // If the planet is not in available invites, generate the ticket and add it
@@ -259,8 +297,8 @@ export default function useRoller() {
259297 const _contracts = contracts.getOrElse(null);
260298
261299 if (_authToken && _contracts) {
262− for (let i = 0; i < spawnedAndOwned.length; i++) {
263− const point = spawnedAndOwned[i];
300+ for (let i = 0; i < possibleMissingInvites.length; i++) {
301+ const point = possibleMissingInvites[i];
264302
265303 if (!availableInvites.find(hasPoint(point))) {
266304 console.log('MISSING IN AVAILABLE', point);
@@ -300,6 +338,8 @@ export default function useRoller() {
300338 authToken,
301339 contracts,
302340 controlledPoints,
341+ currentL2,
342+ getDetails,
303343 ]);
304344
305345 // On load, get initial config
src/store/lib/useInvitesStore.js−2
@@ -42,8 +42,6 @@ export default function useInvitesStore() {
4242 )
4343 );
4444
45− console.log('INVITES STORE', availableInvites, invitedPoints, activity);
46−
4745 const sentInvites = invitedPoints.length;
4846 const acceptedInvites = activity.filter(i => !!i).length;
4947
src/store/storage/roller.ts+2−1
@@ -10,7 +10,6 @@ export interface StoredInvites {
1010
1111export const getStoredInvites = (point: string): StoredInvites => {
1212 const invitesString = localStorage.getItem(INVITES_KEY) || '{}';
13− console.log(invitesString)
1413 const invites = JSON.parse(invitesString);
1514
1615 console.log('STORED INVITES', invites);
@@ -34,3 +33,5 @@ export const setPendingInvites = (point: string, pendingInvites: Invite[]) => {
3433 invites.pending.push(...pendingInvites);
3534 setStoredInvites(point, invites);
3635};
36+
37+export const clearInvitesStorage = () => localStorage.removeItem(INVITES_KEY);
src/views/Invite/Cohort.js+164−318
@@ -1,348 +1,146 @@
1−import React, { useMemo, useEffect, useCallback, useState } from 'react';
2−import { Grid, Flex, Button } from 'indigo-react';
3−import * as ob from 'urbit-ob';
4−import { Just, Nothing } from 'folktale/maybe';
1+import React, { useEffect, useCallback, useState } from 'react';
2+import { Grid, Button } from 'indigo-react';
53import cn from 'classnames';
4+import * as azimuth from 'azimuth-js';
5+import { Icon, Row, StatelessTextInput } from '@tlon/indigo-react';
66
77import { usePointCursor } from 'store/pointCursor';
88import { usePointCache } from 'store/pointCache';
99import { useWallet } from 'store/wallet';
10−
11−import View from 'components/View';
12−import MaybeSigil from 'components/MaybeSigil';
13−import { matchBlinky } from 'components/Blinky';
10+import { useNetwork } from 'store/network';
11+import { useRollerStore } from 'store/roller';
1412
1513import { useLocalRouter } from 'lib/LocalRouter';
16−import useInvites from 'lib/useInvites';
1714import * as need from 'lib/need';
1815import * as wg from 'lib/walletgen';
16+import useRoller from 'lib/useRoller';
17+import { convertToInt } from 'lib/convertToInt';
18+import { generateUrl } from 'lib/utils/invite';
1919
20−import { ReactComponent as SearchIcon } from 'assets/search.svg';
21−import CopyButton from 'components/copiable/CopyButton';
22−import { useNetwork } from 'store/network';
20+import { useIssueChild } from 'views/IssueChild';
21+import View from 'components/View';
2322import L2BackHeader from 'components/L2/Headers/L2BackHeader';
2423import Window from 'components/L2/Window/Window';
2524import HeaderPane from 'components/L2/Window/HeaderPane';
26−import { Icon, Row, StatelessTextInput } from '@tlon/indigo-react';
2725import BodyPane from 'components/L2/Window/BodyPane';
28−import { useRollerStore } from 'store/roller';
29−import useRoller from 'lib/useRoller';
30−import FeeDropdown from 'components/L2/Dropdowns/FeeDropdown';
3126import LoadingOverlay from 'components/L2/LoadingOverlay';
32−import { generateUrl } from 'lib/utils/invite';
3327import CopiableWithTooltip from 'components/copiable/CopiableWithTooltip';
3428import Paginator from 'components/L2/Paginator';
29+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
3530
3631import './Cohort.scss';
37−import useInviter from 'lib/useInviter';
32+import { getStoredInvites, setStoredInvites } from 'store/storage/roller';
3833
3934const INVITES_PER_PAGE = 7;
4035const DEFAULT_NUM_INVITES = 5;
4136
42−function SearchInput({ className, value, onChange }) {
43− return (
44− <Flex align="center" full className={cn('b b-gray2 b1', className)}>
45− <Flex.Item className="p2">
46− <SearchIcon />
47− </Flex.Item>
48−
49− <Flex.Item
50− flex={1}
51− as={'input'}
52− value={value}
53− onChange={onChange}
54− className="b-none h7"
55− />
56− </Flex>
57− );
58−}
37+export default function InviteCohort() {
38+ const { pop, push, names } = useLocalRouter();
39+ const { authToken } = useWallet();
40+ const {
41+ nextRoll,
42+ currentL2,
43+ pendingTransactions,
44+ invites,
45+ } = useRollerStore();
46+ const { generateInviteCodes, getPendingTransactions } = useRoller();
47+ const { pointCursor } = usePointCursor();
48+ const { contracts } = useNetwork();
49+ const { getDetails } = usePointCache();
5950
60−function CohortMember({ point, pending = false, className, ...rest }) {
61− const { sentInvites } = useInvites(point);
62− const patp = useMemo(() => ob.patp(point), [point]);
63− const colors = pending ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
51+ const point = need.point(pointCursor);
52+ const _contracts = need.contracts(contracts);
6453
65− const DetailText = useCallback(
66− () =>
67− pending ? 'Pending' : <> {matchBlinky(sentInvites)} points invited </>,
68− [pending, sentInvites]
69− );
54+ const pointSize = azimuth.azimuth.getPointSize(point);
55+ const isParent = pointSize !== azimuth.azimuth.PointSize.Planet;
56+ const details = need.details(getDetails(point));
57+ const networkRevision = convertToInt(details.keyRevisionNumber, 10);
58+ const networkKeysNotSet = !currentL2 && isParent && networkRevision === 0;
7059
71− return (
72− <Flex justify="between" className={cn('b1 b-gray2', className)} {...rest}>
73− <Flex.Item className="w9 h9">
74− <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
75− </Flex.Item>
60+ const goUrbitOS = useCallback(() => push(names.URBIT_OS), [push, names]);
7661
77− <Flex.Item
78− flex={1}
79− justify="evenly"
80− as={Flex}
81− col
82− className="mono f6 ph4">
83− <Flex.Item>{patp}</Flex.Item>
84− <Flex.Item className="gray4">
85− <DetailText />
86− </Flex.Item>
87− </Flex.Item>
88− </Flex>
62+ const [numInvites, setNumInvites] = useState(
63+ currentL2 ? DEFAULT_NUM_INVITES : 1
8964 );
90−}
91−function CohortList({
92− acceptedPoints,
93− pendingPoints,
94− className,
95− onlyPending,
96− onSelectInvite,
97−}) {
98− const { syncInvites } = usePointCache();
65+ const [showInviteForm, setShowInviteForm] = useState(false);
66+ const [loading, setLoading] = useState(false);
67+ const [error, setError] = useState(null);
68+ const [l1Invite, setL1Invite] = useState(null);
9969
100− const [query, setQuery] = useState('');
70+ const { construct, unconstruct, completed, bind } = useIssueChild();
10171
72+ // Set up the invite spawn if on L1
10273 useEffect(() => {
103− acceptedPoints.map(syncInvites);
104− }, [acceptedPoints, syncInvites]);
105−
106− const handleChange = useCallback(
107− e => {
108− setQuery(e.target.value);
109− e.preventDefault();
110− },
111− [setQuery]
112− );
113−
114− const filterPoints = useCallback(
115− points =>
116− points.filter(p => {
117− const patp = ob.patp(p).slice(1);
118− return patp.startsWith(query);
119− }),
120− [query]
121− );
122− const selectInvite = useCallback(
123− point => () => {
124− onSelectInvite(point);
125− },
126− [onSelectInvite]
127− );
128− const _pendingPoints = filterPoints(pendingPoints);
129− const _acceptedPoints = filterPoints(acceptedPoints);
130−
131− const offset = onlyPending ? 0 : _acceptedPoints.length;
132−
133− return (
134− <Grid gap={3} className={cn('mt4', className)}>
135− <Grid.Item full as={SearchInput} value={query} onChange={handleChange} />
136− <>
137− {!onlyPending &&
138− _acceptedPoints.map((p, idx) => (
139− <Grid.Item
140− key={p}
141− half={(idx % 2) + 1}
142− as={CohortMember}
143− point={p}
144− onClick={selectInvite(p)}
145− />
146− ))}
147− {_pendingPoints.map((p, idx) => (
148− <Grid.Item
149− key={p}
150− half={((offset + idx) % 2) + 1}
151− as={CohortMember}
152− point={p}
153− pending
154− onClick={selectInvite(p)}
155− />
156− ))}
157− </>
158−
159− {_pendingPoints.length === 0 &&
160− (_acceptedPoints.length === 0 || onlyPending) && (
161− <Grid.Item full className="p4 t-center">
162− {' '}
163− No invites accepted yet.
164− </Grid.Item>
165− )}
166− </Grid>
167− );
168−}
169−
170−function hideIf(hider, hidden) {
171− return hider ? '●●●●●●●' : hidden;
172−}
74+ const _authToken = authToken.getOrElse(null);
17375
174−function CohortMemberExpanded({ point, className, ...rest }) {
175− const { sentInvites, acceptedInvites } = useInvites(point);
176− const { getDetails } = usePointCache();
177− const { authToken } = useWallet();
178− const { contracts } = useNetwork();
179− const details = getDetails(point);
180− const { active } = details.getOrElse({});
181− const patp = useMemo(() => ob.patp(point), [point]);
182− const colors = useMemo(
183− () => (!active ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF']),
184− [active]
185− );
186−
187− const [code, setCode] = useState(Nothing());
188− const [codeVisible, setCodeVisible] = useState(false);
76+ const setUpInvite = async () => {
77+ setLoading(true);
78+ const [invitePoint] = await azimuth.azimuth.getUnspawnedChildren(
79+ _contracts,
80+ point
81+ );
18982
190− useEffect(() => {
191− const fetchWallet = async () => {
192− const _authToken = authToken.getOrElse(null);
193− const _details = details.getOrElse(null);
194− const _contracts = contracts.getOrElse(null);
195− if (
196− !_authToken ||
197− !_details ||
198− _details.active ||
199− !_contracts ||
200− !codeVisible
201− ) {
202− setCode(Nothing());
203− return;
204− }
83+ // TODO: figure out how to store the ticket and invite info
20584 const { ticket, owner } = await wg.generateTemporaryDeterministicWallet(
20685 point,
20786 _authToken
20887 );
20988
210− if (owner.keys.address !== _details.transferProxy) {
211− setCode(Just(null));
212− } else {
213− setCode(Just(ticket));
214− }
89+ construct(invitePoint, owner.keys.address);
90+ setL1Invite({ ticket, planet: invitePoint });
91+ setLoading(false);
21592 };
216− fetchWallet();
217− }, [authToken, codeVisible, contracts, details, point]);
218−
219− useEffect(() => {
220− setCodeVisible(false);
221− }, [point]);
222−
223− return (
224− <Grid justify="between" className={cn('b1 b-gray2', className)} {...rest}>
225− <Grid.Item rows={[1, 4]} cols={[1, 4]} className="h10 w10">
226− <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
227− </Grid.Item>
228−
229− <Grid.Item cols={[4, 13]} className="mono mt-auto ">
230− {patp}
231− </Grid.Item>
232−
233− {!active && (
234− <>
235− <Grid.Item
236− cols={[4, 13]}
237− className="mt-auto mb1 f6 gray4"
238− as={Flex}
239− col>
240− Invite Code
241− </Grid.Item>
242− <Grid.Item cols={[4, 10]} className="mb-auto mt1 f6 gray4">
243− {(code.getOrElse(true) === null &&
244− 'This invite code cannot be recovered') ||
245− hideIf(!codeVisible, matchBlinky(code))}
246− </Grid.Item>
247− {!codeVisible && (
248− <Grid.Item
249− cols={[10, 13]}
250− className="mh-auto f6 underline pointer"
251− onClick={() => setCodeVisible(true)}>
252− Show
253− </Grid.Item>
254− )}
255− {codeVisible && code.getOrElse('') !== null && (
256− <Grid.Item
257− className="mh-auto f6 t-right"
258− cols={[10, 13]}
259− as={CopyButton}
260− text={code.getOrElse('')}
261− />
262− )}
263− </>
264− )}
265−
266− {active && (
267− <>
268− <Grid.Item
269− cols={[4, 13]}
270− className="mt-auto mb1 f6 gray4"
271− as={Flex}
272− col>
273− {matchBlinky(sentInvites)} invites sent
274− </Grid.Item>
275− <Grid.Item
276− cols={[4, 13]}
277− className="mb-auto mt1 f6 gray4"
278− as={Flex}
279− col>
280− {matchBlinky(acceptedInvites)} invites accepted
281− </Grid.Item>
282− </>
283− )}
284− </Grid>
285− );
286−}
28793
288−export default function InviteCohort() {
289− const { pop } = useLocalRouter();
290− const {
291− nextRoll,
94+ if (!currentL2 && _contracts && point && _authToken && showInviteForm) {
95+ setUpInvite();
96+ } else {
97+ unconstruct();
98+ }
99+ }, [
292100 currentL2,
293− pendingTransactions,
294− invites,
295− } = useRollerStore();
296− const { generateInvites } = useInviter();
297− const { generateInviteCodes, getPendingTransactions } = useRoller();
298−
299− const { pointCursor } = usePointCursor();
300− const point = need.point(pointCursor);
101+ point,
102+ _contracts,
103+ authToken,
104+ construct,
105+ showInviteForm,
106+ unconstruct,
107+ ]);
301108
302− const [page, setPage] = useState(0);
109+ const l1Complete = useCallback(() => {
110+ const { available, pending, claimed } = getStoredInvites(point);
111+ setStoredInvites(point, {
112+ available: [...available, l1Invite],
113+ pending,
114+ claimed,
115+ });
116+ setShowInviteForm(false);
117+ }, [l1Invite, point]);
303118
304− const { pendingPoints } = useInvites(point);
119+ useEffect(() => (completed ? l1Complete() : null), [completed, l1Complete]);
305120
306− const displayedInvites = currentL2 ? invites : pendingPoints.getOrElse([]);
121+ const [page, setPage] = useState(0);
307122
308− const invitesToDisplay = displayedInvites.slice(
123+ const invitesToDisplay = invites.slice(
309124 page * INVITES_PER_PAGE,
310125 (page + 1) * INVITES_PER_PAGE
311126 );
312127
313128 const hasPending = Boolean(pendingTransactions.length);
314− const hasInvites = Boolean(displayedInvites.length);
315−
316− const [numInvites, setNumInvites] = useState(DEFAULT_NUM_INVITES);
317− const [showInviteForm, setShowInviteForm] = useState(false);
318− const [loading, setLoading] = useState(false);
319− const [error, setError] = useState(null);
129+ const hasInvites = Boolean(invites.length);
320130
321131 const createInvites = useCallback(async () => {
322132 setLoading(true);
323133 try {
324− if (currentL2) {
325− const invites = await generateInviteCodes(numInvites);
326− console.log('INVITES', invites);
327− getPendingTransactions(point);
328− } else {
329− const invites = await generateInvites(numInvites);
330− console.log('INVITES', invites);
331− }
332− setShowInviteForm(false);
134+ const invites = await generateInviteCodes(numInvites);
135+ console.log('L2 INVITES', invites);
136+ getPendingTransactions(point);
137+ pop();
333138 } catch (error) {
334139 setError(error);
335140 } finally {
336141 setLoading(false);
337142 }
338− }, [
339− generateInviteCodes,
340− numInvites,
341− getPendingTransactions,
342− point,
343− generateInvites,
344− currentL2,
345− ]);
143+ }, [generateInviteCodes, numInvites, getPendingTransactions, point, pop]);
346144
347145 const getContent = () => {
348146 if (hasInvites) {
@@ -378,6 +176,18 @@ export default function InviteCohort() {
378176 Check back in <span className="timer"> {nextRoll}</span>
379177 </>
380178 );
179+ } else if (networkKeysNotSet) {
180+ return (
181+ <>
182+ This star's network keys are not set.
183+ <br />
184+ Either set the network keys or{' '}
185+ <span className="migrate" onClick={() => null}>
186+ {/* TODO: change this to navigate to migration */}
187+ migrate this star to L2.
188+ </span>
189+ </>
190+ );
381191 }
382192
383193 return (
@@ -398,8 +208,8 @@ export default function InviteCohort() {
398208 const header = (
399209 <h5>
400210 You have
401− <span className="number-emphasis"> {displayedInvites.length} </span>
402− Planet Code{displayedInvites.length === 1 ? '' : 's'}
211+ <span className="number-emphasis"> {invites.length} </span>
212+ Planet Code{invites.length === 1 ? '' : 's'}
403213 </h5>
404214 );
405215
@@ -415,18 +225,29 @@ export default function InviteCohort() {
415225 </HeaderPane>
416226 <BodyPane>
417227 <div className="upper">
418− <Row className="points-input">
419− I want to generate
420− <StatelessTextInput
421− className="input-box"
422− value={numInvites}
423− maxLength="3"
424− onChange={e =>
425− setNumInvites(Number(e.target.value.replace(/\D/g, '')))
426− }
427− />
428− planet invite code{numInvites > 1 ? 's' : ''}
429− </Row>
228+ {currentL2 ? (
229+ <Row className="points-input">
230+ I want to generate
231+ <StatelessTextInput
232+ className="input-box"
233+ value={numInvites}
234+ maxLength="3"
235+ onChange={e =>
236+ setNumInvites(Number(e.target.value.replace(/\D/g, '')))
237+ }
238+ />
239+ planet invite code{numInvites > 1 ? 's' : ''}
240+ </Row>
241+ ) : (
242+ <div className="migration-prompt">
243+ ETH fees are very high currently. You can spawn invites for
244+ free after{' '}
245+ <span className="migrate" onClick={() => null}>
246+ {/* TODO: change this to navigate to migration */}
247+ migrating this star to L2.
248+ </span>
249+ </div>
250+ )}
430251 {/* <div>Destination</div>
431252 <div className="ship-or-address">Ship or ethereum address</div>
432253 <Dropdown value={dropdownValue}>
@@ -439,22 +260,30 @@ export default function InviteCohort() {
439260 </div>
440261 {/* <Inviter /> */}
441262 <div className="lower">
442− <Row className="next-roll">
443− <span>{currentL2 ? 'Next Roll in' : 'Transaction Fee'}</span>
444− {currentL2 ? (
263+ {currentL2 && (
264+ <Row className="next-roll">
265+ <span>Next Roll in</span>
445266 <span className="timer">{nextRoll}</span>
446− ) : (
447− <FeeDropdown />
448− )}
449− </Row>
450− <Button
451− as={'button'}
452− className="generate-codes"
453− center
454− solid
455− onClick={createInvites}>
456− Generate Planet Codes ({numInvites})
457− </Button>
267+ </Row>
268+ )}
269+ {currentL2 ? (
270+ <Button
271+ as={'button'}
272+ className="generate-codes"
273+ center
274+ solid
275+ onClick={createInvites}>
276+ Generate Planet Code{currentL2 ? `s (${numInvites})` : ''}
277+ </Button>
278+ ) : (
279+ <Grid.Item
280+ full
281+ as={InlineEthereumTransaction}
282+ label="Generate Planet Code"
283+ {...bind}
284+ onReturn={() => pop()}
285+ />
286+ )}
458287 </div>
459288 </BodyPane>
460289 </Window>
@@ -463,8 +292,15 @@ export default function InviteCohort() {
463292 );
464293 }
465294
295+ const showGenerateButton = !hasPending && !hasInvites && !networkKeysNotSet;
296+
466297 return (
467− <View pop={pop} inset className="cohort" hideBack header={<L2BackHeader />}>
298+ <View
299+ pop={pop}
300+ inset
301+ className="cohort"
302+ hideBack
303+ header={<L2BackHeader hideBalance />}>
468304 <Window>
469305 <HeaderPane>
470306 {!hasInvites ? (
@@ -483,7 +319,7 @@ export default function InviteCohort() {
483319 <div className={`content ${!hasInvites ? 'center' : ''}`}>
484320 {getContent()}
485321 </div>
486− {!hasPending && !hasInvites && (
322+ {showGenerateButton && (
487323 <Button
488324 as={'button'}
489325 className="generate-button"
@@ -493,11 +329,21 @@ export default function InviteCohort() {
493329 Generate Codes
494330 </Button>
495331 )}
332+ {networkKeysNotSet && (
333+ <Button
334+ as={'button'}
335+ className="generate-button"
336+ center
337+ solid
338+ onClick={goUrbitOS}>
339+ Set Network Keys
340+ </Button>
341+ )}
496342 {hasInvites && (
497343 <Paginator
498344 page={page}
499345 numPerPage={INVITES_PER_PAGE}
500− numElements={displayedInvites.length}
346+ numElements={invites.length}
501347 goPrevious={() => setPage(page - 1)}
502348 goNext={() => setPage(page + 1)}
503349 />
src/views/Invite/Cohort.scss+11−1
@@ -109,12 +109,22 @@
109109 }
110110 }
111111
112+ .migrate {
113+ color: $mid-gray;
114+ text-decoration: underline;
115+ cursor: pointer;
116+ }
117+
112118 &.show-invite-form {
113119 .body-pane {
114120 .upper, .lower {
115121 width: 100%;
116122 }
117−
123+
124+ .migration-prompt {
125+ line-height: 20px;
126+ }
127+
118128 .points-input {
119129 line-height: 28px;
120130 margin-bottom: 24px;
src/views/IssueChild.js+2−1
@@ -32,7 +32,7 @@ import FormError from 'form/FormError';
3232import CopiableAddress from 'components/copiable/CopiableAddress';
3333import { convertToInt } from 'lib/convertToInt';
3434
35−function useIssueChild() {
35+export function useIssueChild() {
3636 const { contracts } = useNetwork();
3737 const { syncDates } = usePointCache();
3838
@@ -43,6 +43,7 @@ function useIssueChild() {
4343 return useEthereumTransaction(
4444 useCallback(
4545 (spawnedPoint, owner) => {
46+ console.log(1, spawnedPoint, owner, _contracts)
4647 setSpawnedPoint(spawnedPoint);
4748 return azimuth.ecliptic.spawn(_contracts, spawnedPoint, owner);
4849 },
src/views/Point.js+18−46
@@ -2,37 +2,33 @@ import React, { useCallback, useEffect, useState } from 'react';
22import cn from 'classnames';
33import { Just } from 'folktale/maybe';
44import { Grid, Flex, Button } from 'indigo-react';
5+import { Icon, Row } from '@tlon/indigo-react';
56import { azimuth } from 'azimuth-js';
6−import * as ob from 'urbit-ob';
7−import * as wg from 'lib/walletgen';
87
98import { usePointCursor } from 'store/pointCursor';
109import { useWallet } from 'store/wallet';
10+import { usePointCache } from 'store/pointCache';
11+import { useRollerStore } from 'store/roller';
1112
1213import View from 'components/View';
1314import Greeting from 'components/Greeting';
1415import Passport from 'components/Passport';
15−import Blinky, { matchBlinky } from 'components/Blinky';
16+import Blinky from 'components/Blinky';
1617import BarGraph from 'components/BarGraph';
1718import Chip from 'components/Chip';
1819import InviteSigilList from 'components/InviteSigilList';
1920import { ForwardButton } from 'components/Buttons';
21+import L2PointHeader from 'components/L2/Headers/L2PointHeader';
22+import LayerIndicator from 'components/L2/LayerIndicator';
23+import Card from 'components/L2/Card';
2024
2125import * as need from 'lib/need';
2226import useInvites from 'lib/useInvites';
2327import { useSyncExtras } from 'lib/useSyncPoints';
2428import useCurrentPermissions from 'lib/useCurrentPermissions';
2529import { useLocalRouter } from 'lib/LocalRouter';
26−
2730import Inviter from 'views/Invite/Inviter';
28−import { usePointCache } from 'store/pointCache';
29−import Card from 'components/L2/Card';
30−import { Icon, Row } from '@tlon/indigo-react';
31−import L2PointHeader from 'components/L2/Headers/L2PointHeader';
3231import useRoller from 'lib/useRoller';
33−import { useRollerStore } from 'store/roller';
34−import LayerIndicator from 'components/L2/LayerIndicator';
35−import { convertToInt } from 'lib/convertToInt';
3632
3733import './Point.scss';
3834
@@ -132,30 +128,31 @@ export default function Point() {
132128 const {
133129 pendingTransactions,
134130 nextRoll,
135− setCurrentPoint,
136− currentL2,
137131 invites,
138132 recentlyCompleted,
133+ setCurrentPoint,
134+ setPendingTransactions,
139135 } = useRollerStore();
140136
137+ const [loaded, setLoaded] = useState(false);
138+
141139 useEffect(() => {
142140 const loadL2Info = async () => {
143− console.log('POINT', point);
144141 const getTransactions = async () => {
145142 const pointInfo = await api.getPoint(Number(point));
146143 setCurrentPoint(pointInfo);
147− console.log('POINT', pointInfo);
148144
149− if (pointInfo.dominion !== 'l1') {
150− getInvites();
151− }
145+ getInvites();
152146 };
153147
154− getTransactions();
148+ if (!loaded) {
149+ getTransactions();
150+ setLoaded(true);
151+ }
155152 };
156153
157154 loadL2Info();
158− }, [api, point, authToken]); // eslint-disable-line
155+ }, [api, point, authToken, setPendingTransactions, loaded]); // eslint-disable-line
159156
160157 const { residentCount, requestCount } = getResidents(point);
161158
@@ -197,11 +194,6 @@ export default function Point() {
197194
198195 const goResidents = useCallback(() => push(names.RESIDENTS), [push, names]);
199196
200− const goPartiesSetPoolSize = useCallback(
201− () => push(names.PARTY_SET_POOL_SIZE),
202− [push, names]
203− );
204−
205197 const goIssuePoint = useCallback(() => push(names.ISSUE_CHILD), [
206198 names.ISSUE_CHILD,
207199 push,
@@ -211,25 +203,6 @@ export default function Point() {
211203
212204 const [showInviteForm, setShowInviteForm] = useState(false);
213205
214− const inviteButton = (() => {
215− if (azimuth.getPointSize(point) === azimuth.PointSize.Star) {
216− return (
217− <>
218− <Grid.Item
219− full
220− as={ForwardButton}
221− disabled={!isActiveOwner}
222− onClick={goPartiesSetPoolSize}>
223− Manage Invite Pools
224− </Grid.Item>
225− <Grid.Divider />
226− </>
227− );
228− }
229−
230− return null;
231− })();
232−
233206 const senateButton = (() => {
234207 if (azimuth.getPointSize(point) !== azimuth.PointSize.Galaxy) {
235208 return null;
@@ -255,7 +228,6 @@ export default function Point() {
255228
256229 const _requestCount = requestCount.getOrElse(0);
257230 const numPending = pendingTransactions.length;
258− const numInvites = currentL2 ? invites.length : sentInvites.length;
259231
260232 return (
261233 <View
@@ -263,7 +235,7 @@ export default function Point() {
263235 inset
264236 className="point"
265237 header={
266− <L2PointHeader hideTimer={!!numPending} numInvites={numInvites} />
238+ <L2PointHeader hideTimer={!!numPending} numInvites={invites.length} />
267239 }>
268240 <Greeting point={point} />
269241 {!!numPending && (
src/views/Points.js+2−1
@@ -37,7 +37,8 @@ export const maybeGetResult = (obj, key, defaultValue) =>
3737 }),
3838 });
3939
40−const hasTransferProxy = details => !isZeroAddress(details.transferProxy);
40+export const hasTransferProxy = details =>
41+ !isZeroAddress(details.transferProxy);
4142
4243const PointList = function({
4344 points,
src/views/UrbitID/Home.js+3
@@ -30,6 +30,8 @@ export default function UrbitIDHome() {
3030 names,
3131 ]);
3232
33+ const goUrbitOS = useCallback(() => push(names.URBIT_OS), [push, names]);
34+
3335 const goResetKeys = useCallback(() => push(names.RESET_KEYS), [push, names]);
3436
3537 const isMasterTicket = Just.hasInstance(urbitWallet);
@@ -135,6 +137,7 @@ export default function UrbitIDHome() {
135137 <Grid.Item
136138 full
137139 as={ForwardButton}
140+ onClick={goUrbitOS}
138141 className="f6"
139142 accessory={<span className="underline pointer">Set Keys</span>}>
140143 Network Keys Required