on master help sign in
WIP: worker
commit65d744f0f8cccf112ff64c460e23d8a05979d5f0 authorWill Galebach <willbach@gmail.com> dateTue 16 Nov 2021, 08:12 (5 years ago) parent27fa6e7
40 files changed+1051−531
package-lock.json+88 package.json+1 src/components/Greeting.js+39−39 src/components/InlineEthereumTransaction.js+3−3 src/components/L2/Card.scss+18 src/components/L2/Card.tsx+8 src/components/L2/Dropdowns/AccountsDropdown.tsx+25−41 src/components/L2/Headers/L2BackButton.scss → src/components/L2/Headers/HeaderButton.scss src/components/L2/Headers/HeaderButton.tsx+23 src/components/L2/Headers/L2BackButton.tsx−21 src/components/L2/Headers/L2BackHeader.tsx+13−18 src/components/L2/Headers/L2PointHeader.scss+4 src/components/L2/Headers/L2PointHeader.tsx+27−21 src/lib/types/L1Point.ts+2−1 src/lib/types/L2Transaction.ts+41 src/lib/types/Point.ts+125 src/lib/useRoller.ts+200−96 src/lib/utils/address.ts+8−6 src/lib/utils/invite.ts+2−2 src/lib/utils/point.ts+24 src/lib/utils/roller.ts+61−4 src/lib/worker/MyWebworker.js+8 src/lib/worker/WebWorker.js+9 src/store/lib/useControlledPointsStore.js+35−39 src/store/lib/useDetailsStore.ts+3−25 src/store/pointCursor.js+18−2 src/store/roller.ts → src/store/rollerStore.ts+18−19 src/store/timerStore.ts+16 src/views/Invite/Cohort.tsx+16−11 src/views/Login/LoginSelector.tsx+22−17 src/views/Point/MigrateL2.tsx+51−36 src/views/Point/Point.tsx+49−75 src/views/Points.js+2−1 src/views/Transactions/TransactionRow.tsx+1−1 src/views/UrbitID/Home.tsx+9−18 src/views/UrbitID/ResetKeys/ResetExecute.js+5−3 src/views/UrbitID/SetProxy.js+23−12 src/views/UrbitID/Transfer.js+8−6 src/views/UrbitOS/ChangeSponsor.js+39−9 src/views/UrbitOS/NetworkingKeys.js+7−5
package-lock.json+88
@@ -11521,6 +11521,52 @@
1152111521 "delayed-stream": "~1.0.0"
1152211522 }
1152311523 },
11524+ "comlink": {
11525+ "version": "4.3.1",
11526+ "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.3.1.tgz",
11527+ "integrity": "sha512-+YbhUdNrpBZggBAHWcgQMLPLH1KDF3wJpeqrCKieWQ8RL7atmgsgTQko1XEBK6PsecfopWNntopJ+ByYG1lRaA==",
11528+ "dev": true
11529+ },
11530+ "comlink-loader": {
11531+ "version": "2.0.0",
11532+ "resolved": "https://registry.npmjs.org/comlink-loader/-/comlink-loader-2.0.0.tgz",
11533+ "integrity": "sha512-106/k0JSssSCo2QO4ucbgbyWsb2zAEOSXFAVH2A+EO8kOz3n2BkvmkToxsoo+SWKUIKy8qubhAOLCS5mpFUvHw==",
11534+ "dev": true,
11535+ "requires": {
11536+ "comlink": "^4.2.0",
11537+ "loader-utils": "^1.1.0",
11538+ "slash": "^3.0.0",
11539+ "worker-loader": "^2.0.0"
11540+ },
11541+ "dependencies": {
11542+ "json5": {
11543+ "version": "1.0.1",
11544+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
11545+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
11546+ "dev": true,
11547+ "requires": {
11548+ "minimist": "^1.2.0"
11549+ }
11550+ },
11551+ "loader-utils": {
11552+ "version": "1.4.0",
11553+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
11554+ "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
11555+ "dev": true,
11556+ "requires": {
11557+ "big.js": "^5.2.2",
11558+ "emojis-list": "^3.0.0",
11559+ "json5": "^1.0.1"
11560+ }
11561+ },
11562+ "slash": {
11563+ "version": "3.0.0",
11564+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
11565+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
11566+ "dev": true
11567+ }
11568+ }
11569+ },
1152411570 "commander": {
1152511571 "version": "2.20.3",
1152611572 "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@@ -35992,6 +36038,48 @@
3599236038 "errno": "~0.1.7"
3599336039 }
3599436040 },
36041+ "worker-loader": {
36042+ "version": "2.0.0",
36043+ "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-2.0.0.tgz",
36044+ "integrity": "sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==",
36045+ "dev": true,
36046+ "requires": {
36047+ "loader-utils": "^1.0.0",
36048+ "schema-utils": "^0.4.0"
36049+ },
36050+ "dependencies": {
36051+ "json5": {
36052+ "version": "1.0.1",
36053+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
36054+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
36055+ "dev": true,
36056+ "requires": {
36057+ "minimist": "^1.2.0"
36058+ }
36059+ },
36060+ "loader-utils": {
36061+ "version": "1.4.0",
36062+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
36063+ "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
36064+ "dev": true,
36065+ "requires": {
36066+ "big.js": "^5.2.2",
36067+ "emojis-list": "^3.0.0",
36068+ "json5": "^1.0.1"
36069+ }
36070+ },
36071+ "schema-utils": {
36072+ "version": "0.4.7",
36073+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",
36074+ "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==",
36075+ "dev": true,
36076+ "requires": {
36077+ "ajv": "^6.1.0",
36078+ "ajv-keywords": "^3.1.0"
36079+ }
36080+ }
36081+ }
36082+ },
3599536083 "worker-rpc": {
3599636084 "version": "0.1.1",
3599736085 "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz",
package.json+1
@@ -92,6 +92,7 @@
9292 "@types/keccak": "^3.0.1",
9393 "@types/lodash": "^4.14.170",
9494 "@types/react-transition-group": "^4.4.2",
95+ "comlink-loader": "^2.0.0",
9596 "eslint-config-prettier": "^5.0.0",
9697 "eslint-plugin-prettier": "^3.1.0",
9798 "eslint-plugin-react-hooks": "^1.6.0",
src/components/Greeting.js+39−39
@@ -16,45 +16,45 @@ export default function ActivateDisclaimer({ point }) {
1616 setWasGreeted(true);
1717 }, [setWasGreeted]);
1818
19+ if (wasGreeted) {
20+ return null;
21+ }
22+
1923 return (
20− !wasGreeted && (
21− <Grid gap={4} className="mb10">
22− <Grid.Item full>
23− <Text className={cn(TEXT_STYLE, 'block mb4')}>
24− Welcome <code>{pointName}</code>,
25− </Text>
26−
27− <Text className={cn(TEXT_STYLE, 'block mb2')}>
28− As of this moment, you own a piece of Urbit. No one can take it from
29− you, and you can keep it for the rest of your life.
30− </Text>
31− <Text className={cn(TEXT_STYLE, 'block mb2')}>
32− Keep your Master Ticket safe. No one can recover it for you. But it
33− can get you back into Urbit at any time.
34− </Text>
35− <Text className={cn(TEXT_STYLE, 'block mb2')}>
36− Right now you can:
37− </Text>
38− </Grid.Item>
39−
40− <Grid.Item
41− full
42− as={LinkButton}
43− href="https://urbit.org/docs/getting-started/">
44− <Text className={cn(TEXT_STYLE, 'block mb2')}>
45− Boot Arvo, the Urbit OS
46− </Text>
47− </Grid.Item>
48−
49− <Grid.Item full>
50− <Text className={cn(TEXT_STYLE, 'block mb4')}>
51− Welcome to Urbit. See you online.
52− </Text>
53− </Grid.Item>
54− <Grid.Item full as={LinkButton} onClick={dismiss}>
55− Close
56− </Grid.Item>
57− </Grid>
58− )
24+ <Grid gap={4} className="mb10">
25+ <Grid.Item full>
26+ <Text className={cn(TEXT_STYLE, 'block mb4')}>
27+ Welcome <code>{pointName}</code>,
28+ </Text>
29+
30+ <Text className={cn(TEXT_STYLE, 'block mb2')}>
31+ As of this moment, you own a piece of Urbit. No one can take it from
32+ you, and you can keep it for the rest of your life.
33+ </Text>
34+ <Text className={cn(TEXT_STYLE, 'block mb2')}>
35+ Keep your Master Ticket safe. No one can recover it for you. But it
36+ can get you back into Urbit at any time.
37+ </Text>
38+ <Text className={cn(TEXT_STYLE, 'block mb2')}>Right now you can:</Text>
39+ </Grid.Item>
40+
41+ <Grid.Item
42+ full
43+ as={LinkButton}
44+ href="https://urbit.org/docs/getting-started/">
45+ <Text className={cn(TEXT_STYLE, 'block mb2')}>
46+ Boot Arvo, the Urbit OS
47+ </Text>
48+ </Grid.Item>
49+
50+ <Grid.Item full>
51+ <Text className={cn(TEXT_STYLE, 'block mb4')}>
52+ Welcome to Urbit. See you online.
53+ </Text>
54+ </Grid.Item>
55+ <Grid.Item full as={LinkButton} onClick={dismiss}>
56+ Close
57+ </Grid.Item>
58+ </Grid>
5959 );
6060}
src/components/InlineEthereumTransaction.js+3−3
@@ -121,7 +121,7 @@ export default function InlineEthereumTransaction({
121121 } else if (showBroadcastButton) {
122122 return (
123123 <Grid.Item
124− className="eth-tx-button"
124+ className="ph4 eth-tx-button"
125125 full
126126 as={ForwardButton}
127127 solid
@@ -134,7 +134,7 @@ export default function InlineEthereumTransaction({
134134 } else if (showLoadingButton) {
135135 return (
136136 <Grid.Item
137− className="eth-tx-button"
137+ className="ph4 eth-tx-button"
138138 full
139139 as={ProgressButton}
140140 success
@@ -146,7 +146,7 @@ export default function InlineEthereumTransaction({
146146 } else {
147147 return (
148148 <Grid.Item
149− className="eth-tx-button"
149+ className="ph4 eth-tx-button"
150150 full
151151 as={GenerateButton}
152152 onClick={generateAndSign}
src/components/L2/Card.scss+18
@@ -7,6 +7,7 @@
77 line-height: 24px;
88 border: 1px solid rgba(0, 0, 0, 0.2);
99 padding: 16px 13px;
10+ position: relative;
1011
1112 .card-title {
1213 font-weight: bold;
@@ -25,4 +26,21 @@
2526 &:hover {
2627 cursor: pointer;
2728 }
29+
30+ .card-warning {
31+ font-size: 12px;
32+ font-weight: bold;
33+ color: $warning-red;
34+ position: absolute;
35+ top: 4px;
36+ right: 8px;
37+
38+ svg {
39+ background-color: $warning-red;
40+ border-radius: 7px;
41+ width: 14px;
42+ height: 14px;
43+ margin: 4px 4px 0 0;
44+ }
45+ }
2846}
src/components/L2/Card.tsx+8
@@ -7,6 +7,7 @@ export interface CardProps {
77 icon?: typeof Icon;
88 title?: string;
99 subtitle?: string;
10+ warning?: string;
1011 onClick?: () => void;
1112 disabled?: boolean;
1213 children?: React.ReactNode;
@@ -16,6 +17,7 @@ export default function Card({
1617 icon,
1718 title,
1819 subtitle,
20+ warning,
1921 onClick,
2022 children,
2123 disabled = false,
@@ -30,6 +32,12 @@ export default function Card({
3032 )}
3133 {!!subtitle && <Box className="card-subtitle">{subtitle}</Box>}
3234 {children}
35+ {!!warning && (
36+ <Row className="card-warning">
37+ <Icon icon="ExclaimationMark" color="white" />
38+ <Box>{warning}</Box>
39+ </Row>
40+ )}
3341 </Box>
3442 );
3543}
src/components/L2/Dropdowns/AccountsDropdown.tsx+25−41
@@ -1,6 +1,5 @@
11import { useCallback, useMemo, useState } from 'react';
2−import * as ob from 'urbit-ob';
3−import { Icon, Row, Box, Button } from '@tlon/indigo-react';
2+import { Icon, Row, Box } from '@tlon/indigo-react';
43import { Just } from 'folktale/maybe';
54
65import Sigil from 'components/Sigil';
@@ -11,18 +10,14 @@ import { TooltipPosition } from 'components/WithTooltip';
1110import { useWallet } from 'store/wallet';
1211import { useHistory } from 'store/history';
1312import { usePointCursor } from 'store/pointCursor';
14−import { usePointCache } from 'store/pointCache';
1513import { clearInvitesStorage } from 'store/storage/roller';
14+import { useRollerStore } from 'store/rollerStore';
1615
17−import { PointLayer } from 'lib/types/PointLayer';
1816import { abbreviateAddress } from 'lib/utils/address';
17+import useCurrentPermissions from 'lib/useCurrentPermissions';
1918
2019import Dropdown from './Dropdown';
2120import './AccountsDropdown.scss';
22−import Modal from '../Modal';
23−import { useHasNetworkKeysSet } from 'lib/useHasNetworkKeysSet';
24−import { useRollerStore } from 'store/roller';
25−import useCurrentPermissions from 'lib/useCurrentPermissions';
2621
2722interface AccountsDropdownProps {
2823 showMigrate: boolean;
@@ -33,22 +28,10 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
3328 const walletInfo: any = useWallet();
3429 const { push, popTo, names, reset }: any = useHistory();
3530 const { setPointCursor }: any = usePointCursor();
36− const { controlledPoints }: any = usePointCache();
37− const { currentL2 }: any = useRollerStore();
38−
39− const networkKeysSet = useHasNetworkKeysSet();
40− const showMigrateOption = useMemo(() => {
41− return showMigrate || (!currentL2 && networkKeysSet);
42− }, [currentL2, networkKeysSet, showMigrate]);
43−
44− const [showModal, setShowModal] = useState(false);
31+ const { pointList } = useRollerStore();
4532
4633 const canBitcoin = Just.hasInstance(walletInfo.urbitWallet);
4734
48− const points = (controlledPoints?.value?.value?.pointsWithLayers || []).sort(
49− (a: PointLayer, b: PointLayer) => a.point - b.point
50− );
51−
5235 const currentAddress = walletInfo?.wallet?.value?.address || '';
5336 const displayAddress = abbreviateAddress(currentAddress);
5437
@@ -86,6 +69,11 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
8669
8770 const { canSpawn, isParent, isStar } = useCurrentPermissions();
8871
72+ const hasL1Points = Boolean(pointList.find(({ layer }) => layer === 1));
73+ const showMigrateOption = useMemo(() => {
74+ return showMigrate || hasL1Points;
75+ }, [hasL1Points, showMigrate]);
76+
8977 return (
9078 <Dropdown
9179 className="accounts-dropdown"
@@ -100,28 +88,24 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
10088 </CopiableAddressWrap>
10189 <Box className="divider" />
10290 <Box className="points">
103− {points.map(({ point, layer }: PointLayer) => {
104− const patp = ob.patp(point);
105−
106− return (
107− <Row
108− className="entry point"
109− onClick={selectPoint(point)}
110− key={`point-${point}`}>
111− <Box>{patp}</Box>
112− <Row>
113− <LayerIndicator layer={layer} size="md" />
114− <Box className="sigil">
115− <Sigil patp={patp} size={1} colors={['#000000', '#FFFFFF']} />
116− </Box>
117− </Row>
91+ {pointList.map(({ value, layer, patp }) => (
92+ <Row
93+ className="entry point"
94+ onClick={selectPoint(value)}
95+ key={`point-${value}`}>
96+ <Box>{patp}</Box>
97+ <Row>
98+ <LayerIndicator layer={layer} size="md" />
99+ <Box className="sigil">
100+ <Sigil patp={patp} size={1} colors={['#000000', '#FFFFFF']} />
101+ </Box>
118102 </Row>
119− );
120− })}
103+ </Row>
104+ ))}
121105 </Box>
122106 <Box className="divider" />
123107 {showMigrateOption && (
124− <Row className="entry" onClick={() => setShowModal(true)}>
108+ <Row className="entry" onClick={goMigrate}>
125109 <Box>Migrate</Box>
126110 <Row className="layer-migration">
127111 <LayerIndicator layer={1} size="sm" />
@@ -146,7 +130,7 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
146130 <Box>Logout</Box>
147131 <Icon icon="LogOut" />
148132 </Row>
149− <Modal show={showModal} hide={() => setShowModal(false)}>
133+ {/* <Modal show={showModal} hide={() => setShowModal(false)}>
150134 <Box className="migrate-modal">
151135 <Box className="close" onClick={() => setShowModal(false)}>
152136 &#215;
@@ -166,7 +150,7 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
166150 </Button>
167151 </Row>
168152 </Box>
169− </Modal>
153+ </Modal> */}
170154 </Dropdown>
171155 );
172156};
src/components/L2/Headers/L2BackButton.scss → src/components/L2/Headers/HeaderButton.scssrenamed

The content is the same.

src/components/L2/Headers/HeaderButton.tsxnew file+23
@@ -0,0 +1,23 @@
1+import React from 'react';
2+import { Icon, IconIndex } from '@tlon/indigo-react';
3+import './HeaderButton.scss';
4+
5+interface HeaderButtonProps {
6+ icon: keyof IconIndex;
7+ onClick?: () => void;
8+ className?: string;
9+}
10+
11+export default function HeaderButton({
12+ icon,
13+ onClick,
14+ className = '',
15+}: HeaderButtonProps) {
16+ return (
17+ <Icon
18+ className={`back-button ${className}`}
19+ icon={icon}
20+ onClick={onClick}
21+ />
22+ );
23+}
src/components/L2/Headers/L2BackButton.tsxdeleted−21
@@ -1,21 +0,0 @@
1−import React from 'react';
2−import { Icon } from '@tlon/indigo-react';
3−import './L2BackButton.scss';
4−
5−interface L2BackButtonProps {
6− onBack?: () => void;
7− className?: string;
8−}
9−
10−export default function L2BackButton({
11− onBack,
12− className = '',
13−}: L2BackButtonProps) {
14− return (
15− <Icon
16− className={`back-button ${className}`}
17− icon="ChevronWest"
18− onClick={onBack}
19− />
20− );
21−}
src/components/L2/Headers/L2BackHeader.tsx+13−18
@@ -1,18 +1,17 @@
11import React, { useCallback, useEffect, useState } from 'react';
22import { Row, Icon, Box } from '@tlon/indigo-react';
3+import { toBN } from 'web3-utils';
4+import BN from 'bn.js';
35
4−import useRoller from 'lib/useRoller';
56import { useHistory } from 'store/history';
6−import { useRollerStore } from 'store/roller';
7−
8−import './L2BackHeader.scss';
7+import { useRollerStore } from 'store/rollerStore';
98import { useNetwork } from 'store/network';
109import { useWallet } from 'store/wallet';
11−import { toBN } from 'web3-utils';
12−import BN from 'bn.js';
13−import { isDevelopment } from 'lib/flags';
10+import { useTimerStore } from 'store/timerStore';
11+
1412import { ReactComponent as Wallet } from 'assets/wallet.svg';
15−import L2BackButton from './L2BackButton';
13+import HeaderButton from './HeaderButton';
14+import './L2BackHeader.scss';
1615
1716export interface L2BackHeaderProps {
1817 back?: () => void;
@@ -25,12 +24,14 @@ const L2BackHeader = ({
2524 className = '',
2625 hideBalance = false,
2726}: L2BackHeaderProps) => {
28− const { config } = useRoller();
29− const { nextRoll, currentL2 } = useRollerStore(store => store);
27+ const { point } = useRollerStore(store => store);
28+ const { nextRoll } = useTimerStore();
3029 const { pop }: any = useHistory();
3130 const { wallet }: any = useWallet();
3231 const { web3 }: any = useNetwork();
3332
33+ const currentL2 = !!point?.isL2;
34+
3435 const [ethBalance, setEthBalance] = useState<BN>(toBN(0));
3536
3637 const goBack = useCallback(() => {
@@ -41,12 +42,6 @@ const L2BackHeader = ({
4142 }
4243 }, [back, pop]);
4344
44− useEffect(() => {
45− if (isDevelopment) {
46− console.log('loaded config in L2BackHeader:', config);
47− }
48− }, [config]);
49−
5045 useEffect(() => {
5146 const getEthBalance = async () => {
5247 const _web3 = web3.getOrElse(null);
@@ -60,7 +55,7 @@ const L2BackHeader = ({
6055 }
6156 };
6257
63− if (!currentL2) {
58+ if (currentL2) {
6459 getEthBalance();
6560 }
6661 }, [currentL2, setEthBalance, wallet, web3]);
@@ -69,7 +64,7 @@ const L2BackHeader = ({
6964
7065 return (
7166 <Row className={`l2-back-header ${className}`}>
72− <L2BackButton onBack={goBack} />
67+ <HeaderButton icon="ChevronWest" onClick={goBack} />
7368 {currentL2 && !hideBalance ? (
7469 <Row className="rollup-timer">
7570 <Icon icon="Clock" />
src/components/L2/Headers/L2PointHeader.scss+4
@@ -8,6 +8,10 @@
88 font-size: 14px;
99 margin: 40px 0px 16px;
1010
11+ .home {
12+ margin-right: 8px;
13+ }
14+
1115 .dropdown {
1216 border-radius: $medium-border-radius;
1317 background-color: $washed-gray;
src/components/L2/Headers/L2PointHeader.tsx+27−21
@@ -1,42 +1,39 @@
1−import React, { useEffect, useCallback } from 'react';
1+import React, { useCallback } from 'react';
22import { Box, Row } from '@tlon/indigo-react';
33
4−import useRoller from 'lib/useRoller';
4+import { isStar } from 'lib/utils/point';
5+
56import { useHistory } from 'store/history';
6−import { ReactComponent as InviteIcon } from 'assets/invite.svg';
7+import { useRollerStore } from 'store/rollerStore';
8+import { useTimerStore } from 'store/timerStore';
9+import { usePointCursor } from 'store/pointCursor';
10+
711import { ReactComponent as HistoryIcon } from 'assets/history.svg';
12+import { ReactComponent as InviteIcon } from 'assets/invite.svg';
13+import HeaderButton from './HeaderButton';
814import AccountsDropdown from '../Dropdowns/AccountsDropdown';
9−import { useRollerStore } from 'store/roller';
10−
1115import './L2PointHeader.scss';
12−import { isDevelopment } from 'lib/flags';
13−import { usePointCursor } from 'store/pointCursor';
14−import { isStar } from 'lib/utils/point';
1516
1617export interface L2PointHeaderProps {
1718 numInvites: number;
19+ hideHome?: boolean;
1820 hideTimer?: boolean;
1921 hideInvites?: boolean;
2022 showMigrate?: boolean;
2123}
2224
2325const L2PointHeader = ({
26+ numInvites = 0,
27+ hideHome = false,
2428 hideTimer = false,
2529 hideInvites = false,
2630 showMigrate = false,
27− numInvites = 0,
2831}: L2PointHeaderProps) => {
29− const { config } = useRoller();
30− const { nextRoll, currentL2, pendingTransactions } = useRollerStore();
32+ const { point, pendingTransactions } = useRollerStore();
33+ const { nextRoll } = useTimerStore();
3134 const { pointCursor }: any = usePointCursor();
3235
33− useEffect(() => {
34− if (isDevelopment) {
35− console.log('loaded config in L2PointHeader:', config);
36− }
37− }, [config]);
38−
39− const { push, names }: any = useHistory();
36+ const { popTo, push, names }: any = useHistory();
4037
4138 const goToInvites = useCallback(() => push(names.INVITE_COHORT), [
4239 push,
@@ -52,15 +49,24 @@ const L2PointHeader = ({
5249
5350 return (
5451 <Row className="l2-point-header">
55− <AccountsDropdown showMigrate={showMigrate} />
52+ <Row>
53+ {!hideHome && (
54+ <HeaderButton
55+ className="home"
56+ icon="Home"
57+ onClick={() => popTo(names.POINTS)}
58+ />
59+ )}
60+ <AccountsDropdown showMigrate={showMigrate} />
61+ </Row>
5662 <Row className="info">
57− {showInvites && (
63+ {showInvites && point.canSpawn && (
5864 <Row onClick={goToInvites} className="invites">
5965 <InviteIcon />
6066 {numInvites}
6167 </Row>
6268 )}
63− {!hideTimer && currentL2 && !pendingTransactions.length && (
69+ {!hideTimer && point?.isL2Spawn && !pendingTransactions.length && (
6470 <Box className="rollup-timer" onClick={goToHistory}>
6571 {nextRoll}
6672 </Box>
src/lib/types/L1Point.ts+2−1
@@ -1,10 +1,11 @@
11// Response returned by L1's azimuth#getPoint
22export type L1Point = {
33 active: boolean;
4+ layer: 1 | 2;
5+ isL2Spawn?: boolean;
46 authenticationKey: string;
57 continuityNumber: string;
68 cryptoSuiteVersion: string;
7− dominion?: string;
89 encryptionKey: string;
910 escapeRequested: boolean;
1011 escapeRequestedTo: string;
src/lib/types/L2Transaction.tsnew file+41
@@ -0,0 +1,41 @@
1+import RollerRPCAPI, { EthAddress, Proxy, Ship } from '@urbit/roller-api';
2+
3+export type TransactionType =
4+ | 'transferPoint'
5+ | 'spawn'
6+ | 'configureKeys'
7+ | 'escape';
8+
9+export interface SendL2Params {
10+ address?: string; // Destination address
11+ breach?: boolean;
12+ networkSeed?: any;
13+ newSponsor?: Ship;
14+ nonce: number;
15+ pointToSpawn?: number;
16+ proxy: Proxy;
17+ proxyAddressType?: string;
18+ reset?: boolean;
19+ ship: Ship;
20+ type: TransactionType;
21+}
22+
23+export interface L2TransactionArgs extends SendL2Params {
24+ api: RollerRPCAPI;
25+ wallet: any;
26+ walletType: symbol;
27+ web3: any;
28+}
29+
30+export interface TransactionData {
31+ data: any;
32+ method: Function;
33+}
34+
35+export interface ReticketParams {
36+ point: Ship;
37+ to: EthAddress;
38+ manager: EthAddress;
39+ fromWallet: any; // TODO: wallet type
40+ toWallet: any; // TODO: wallet type
41+}
src/lib/types/Point.tsnew file+125
@@ -0,0 +1,125 @@
1+import * as ob from 'urbit-ob';
2+import { isGalaxy, isPlanet, isStar } from 'lib/utils/point';
3+import { eqAddr, isZeroAddress } from 'lib/utils/address';
4+import { L1Point } from './L1Point';
5+
6+export interface Points {
7+ [key: number]: Point;
8+}
9+
10+export enum Relationship {
11+ own,
12+ transfer,
13+ manage,
14+ vote,
15+ spawn,
16+}
17+
18+export default class Point {
19+ value: number;
20+ patp: string;
21+ layer: 1 | 2;
22+ active: boolean;
23+ authenticationKey: string;
24+ continuityNumber: string;
25+ cryptoSuiteVersion: string;
26+ encryptionKey: string;
27+ escapeRequested: boolean;
28+ escapeRequestedTo: string;
29+ hasSponsor: boolean;
30+ keyRevisionNumber: string;
31+ managementProxy: string;
32+ owner: string;
33+ spawnProxy: string;
34+ sponsor: string;
35+ transferProxy: string;
36+ votingProxy: string;
37+ relationship: Relationship;
38+ // Derived details
39+ isL2: boolean;
40+ isL2Spawn: boolean;
41+ isL1: boolean;
42+ isGalaxy: boolean;
43+ isStar: boolean;
44+ isPlanet: boolean;
45+ isParent: boolean;
46+ canMigrate: boolean;
47+ networkKeysSet: boolean;
48+ // usePermissionsForPoint details
49+ isOwner: boolean;
50+ isActiveOwner: boolean;
51+ isManagementProxy: boolean;
52+ isSpawnProxy: boolean;
53+ isVotingProxy: boolean;
54+ isTransferProxy: boolean;
55+ isManagementProxySet: boolean;
56+ isSpawnProxySet: boolean;
57+ isVotingProxySet: boolean;
58+ isTransferProxySet: boolean;
59+ canManage: boolean;
60+ canSpawn: boolean;
61+ canTransfer: boolean;
62+ canVote: boolean;
63+
64+ constructor(
65+ value: number,
66+ relationship: Relationship,
67+ details: L1Point,
68+ address: string
69+ ) {
70+ this.value = value;
71+ this.patp = value < 0 ? 'null' : ob.patp(value);
72+ this.relationship = relationship;
73+
74+ this.layer = details.layer;
75+ this.isL2Spawn = Boolean(details.isL2Spawn);
76+ this.active = details.active;
77+ this.authenticationKey = details.authenticationKey;
78+ this.continuityNumber = details.continuityNumber;
79+ this.cryptoSuiteVersion = details.cryptoSuiteVersion;
80+ this.encryptionKey = details.encryptionKey;
81+ this.escapeRequested = details.escapeRequested;
82+ this.escapeRequestedTo = details.escapeRequestedTo;
83+ this.hasSponsor = details.hasSponsor;
84+ this.keyRevisionNumber = details.keyRevisionNumber;
85+ this.managementProxy = details.managementProxy;
86+ this.owner = details.owner;
87+ this.spawnProxy = details.spawnProxy;
88+ this.sponsor = details.sponsor;
89+ this.transferProxy = details.transferProxy;
90+ this.votingProxy = details.votingProxy;
91+ // Derived details here
92+ this.isL2 = this.layer === 2;
93+ this.isL1 = this.layer === 1;
94+ this.isGalaxy = isGalaxy(value);
95+ this.isStar = isStar(value);
96+ this.isPlanet = isPlanet(value);
97+ this.isParent = !this.isPlanet;
98+ this.networkKeysSet = Number(this.keyRevisionNumber) > 0;
99+ // usePermissionsForPoint
100+ this.isOwner = eqAddr(address, this.owner);
101+ this.isActiveOwner = this.isOwner && this.active;
102+ this.isManagementProxy = eqAddr(address, this.managementProxy);
103+ this.isSpawnProxy = eqAddr(address, this.spawnProxy);
104+ this.isVotingProxy = eqAddr(address, this.votingProxy);
105+ this.isTransferProxy = eqAddr(address, this.transferProxy);
106+ this.isManagementProxySet = !isZeroAddress(this.managementProxy);
107+ this.isSpawnProxySet = !isZeroAddress(this.spawnProxy);
108+ this.isVotingProxySet = !isZeroAddress(this.votingProxy);
109+ this.isTransferProxySet = !isZeroAddress(this.transferProxy);
110+ this.canManage = this.isOwner || this.isManagementProxy;
111+ this.canTransfer = this.isOwner || this.isTransferProxy;
112+ this.canSpawn =
113+ this.isParent &&
114+ (this.isOwner || this.isSpawnProxy) &&
115+ this.networkKeysSet;
116+ this.canVote =
117+ this.isGalaxy && this.active && (this.isOwner || this.isVotingProxy);
118+
119+ this.canMigrate =
120+ this.canManage &&
121+ this.networkKeysSet &&
122+ this.isL1 &&
123+ ((this.isGalaxy && !this.isL2Spawn) || this.isStar || this.isPlanet);
124+ }
125+}
src/lib/useRoller.ts+200−96
@@ -4,36 +4,39 @@ import * as need from 'lib/need';
44import SecureLS from 'secure-ls';
55
66import { Invite } from 'lib/types/Invite';
7−import { convertToInt } from './convertToInt';
8−import { isDevelopment, isRopsten } from './flags';
97import {
10− adopt,
11− detach,
12− generateInviteWallet,
13− getPendingSpawns,
14− reject,
15−} from './utils/roller';
16−import { ROLLER_HOSTS } from './constants';
8+ deriveNetworkSeedFromUrbitWallet,
9+ attemptNetworkSeedDerivation,
10+} from 'lib/keys';
1711
1812import { useNetwork } from 'store/network';
1913import { usePointCursor } from 'store/pointCursor';
20−import { useRollerStore } from 'store/roller';
14+import { EMPTY_POINT, useRollerStore } from 'store/rollerStore';
2115import { useWallet } from 'store/wallet';
2216import { getStoredInvites, setStoredInvites } from 'store/storage/roller';
2317import { usePointCache } from 'store/pointCache';
2418import { getOutgoingPoints } from 'views/Points';
19+
2520import {
2621 getSpawnProxy,
2722 getManagerProxy,
2823 getAddressProxy,
2924 getTransferProxy,
3025} from './utils/proxy';
31−import { isPlanet } from './utils/point';
32−
26+import { isPlanet, toL1Details } from './utils/point';
27+import { convertToInt } from './convertToInt';
28+import { isDevelopment, isRopsten } from './flags';
3329import {
34− deriveNetworkSeedFromUrbitWallet,
35− attemptNetworkSeedDerivation,
36−} from 'lib/keys';
30+ generateInviteWallet,
31+ getPendingSpawns,
32+ submitL2Transaction,
33+ getTimeToNextBatch,
34+ registerProxyAddress,
35+ adopt,
36+ detach,
37+ reject,
38+} from './utils/roller';
39+import { ROLLER_HOSTS } from './constants';
3740
3841import {
3942 Config,
@@ -45,13 +48,9 @@ import {
4548 UnspawnedPoints,
4649} from '@urbit/roller-api';
4750
48−import {
49− configureKeys,
50− getTimeToNextBatch,
51− spawn,
52− transferPointRequest,
53− registerProxyAddress,
54−} from './utils/roller';
51+import Point, { Points, Relationship } from './types/Point';
52+import { useTimerStore } from 'store/timerStore';
53+import { ReticketParams, SendL2Params } from './types/L2Transaction';
5554
5655const ONE_SECOND = 1000;
5756
@@ -81,17 +80,19 @@ export default function useRoller() {
8180 const controlledPoints = allPoints?.controlledPoints;
8281 const getDetails = allPoints?.getDetails;
8382
83+ const { setNextRoll } = useTimerStore();
84+
8485 const {
8586 nextBatchTime,
86− currentL2,
87+ point,
8788 invitePoints,
8889 invites,
8990 setNextBatchTime,
90− setNextRoll,
9191 setPendingTransactions,
9292 setInvites,
9393 setInvitePoints,
9494 setInviteGeneratingNum,
95+ setPoints,
9596 } = useRollerStore();
9697 const [config, setConfig] = useState<Config | null>(null);
9798
@@ -158,6 +159,7 @@ export default function useRoller() {
158159 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
159160 }
160161
162+ // TODO: remove when UI disables management proxy from spawning invites
161163 // if (Just.hasInstance(authMnemonic)) {
162164 // throw new Error(
163165 // "Auth key Error: A management mnemonic can't create invite codes"
@@ -173,7 +175,6 @@ export default function useRoller() {
173175
174176 const starInfo = await api.getPoint(_point);
175177 const invites: Invite[] = [];
176− const requests: Promise<string>[] = [];
177178
178179 const proxy = getSpawnProxy(starInfo.ownership!, _wallet.address);
179180
@@ -195,33 +196,36 @@ export default function useRoller() {
195196 _authToken
196197 );
197198
198− const spawnRequest = spawn(
199+ await submitL2Transaction({
199200 api,
200− _wallet,
201− _point,
201+ address: _wallet.address,
202+ wallet: _wallet,
203+ ship: _point,
202204 proxy,
203− nonceInc,
204− planet,
205+ nonce: nonceInc,
206+ pointToSpawn: planet,
207+ type: 'spawn',
205208 walletType,
206− _web3
207− );
209+ web3: _web3,
210+ });
208211
209212 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
210213 inviteWallet,
211214 1
212215 );
213− const configureKeysRequest = configureKeys(
216+ await submitL2Transaction({
214217 api,
215− _wallet,
216− planet,
217− 'own',
218− 0,
218+ wallet: _wallet,
219+ ship: planet,
220+ proxy: 'own',
221+ nonce: 0,
219222 networkSeed,
223+ type: 'configureKeys',
220224 walletType,
221− _web3
222− );
225+ web3: _web3,
226+ });
223227
224− const setManagementProxyRequest = registerProxyAddress(
228+ await registerProxyAddress(
225229 api,
226230 _wallet,
227231 planet,
@@ -233,24 +237,17 @@ export default function useRoller() {
233237 _web3
234238 );
235239
236− const transferRequest = transferPointRequest(
240+ await submitL2Transaction({
237241 api,
238− _wallet,
239− planet,
240− 'own',
241− 2,
242− inviteWallet.ownership.keys.address,
242+ wallet: _wallet,
243+ ship: planet,
244+ proxy: 'own',
245+ type: 'transferPoint',
246+ nonce: 2,
247+ address: inviteWallet.ownership.keys.address,
243248 walletType,
244− _web3
245− );
246−
247− requests.push(
248− spawnRequest,
249− configureKeysRequest,
250− setManagementProxyRequest,
251− transferRequest
252− );
253− await Promise.all(requests);
249+ web3: _web3,
250+ });
254251
255252 invites.push({
256253 hash: '',
@@ -497,30 +494,23 @@ export default function useRoller() {
497494 authToken,
498495 revision: nextRevision,
499496 });
500− const txHash = await configureKeys(
497+ const txHash = await submitL2Transaction({
501498 api,
502− _wallet,
503− _point,
504− proxy!,
499+ wallet: _wallet,
500+ ship: _point,
501+ proxy: proxy!,
505502 nonce,
506503 networkSeed,
504+ type: 'configureKeys',
507505 walletType,
508− _web3,
509− breach
510− );
506+ web3: _web3,
507+ breach,
508+ });
511509 const pendingTx = await api.getPendingTx(txHash);
512510
513511 return pendingTx;
514512 };
515513
516− interface ReticketParams {
517− point: Ship;
518− to: EthAddress;
519− manager: EthAddress;
520− fromWallet: any; // TODO: wallet type
521− toWallet: any; // TODO: wallet type
522− }
523−
524514 const performL2Reticket = async ({
525515 point,
526516 to,
@@ -542,16 +532,17 @@ export default function useRoller() {
542532 toWallet,
543533 nextRevision
544534 );
545− const configureKeysRequest = await configureKeys(
535+ const configureKeysRequest = await submitL2Transaction({
546536 api,
547− fromWallet,
548− point,
537+ wallet: fromWallet,
538+ ship: point,
549539 proxy,
550540 nonce,
551541 networkSeed,
552542 walletType,
553− _web3
554− );
543+ type: 'configureKeys',
544+ web3: _web3,
545+ });
555546 nonce = nonce + 1;
556547 requests.push(configureKeysRequest);
557548
@@ -587,16 +578,17 @@ export default function useRoller() {
587578 requests.push(registerSpawnRequest);
588579 }
589580 // 4. Transfer point
590− const transferTxRequest = await transferPointRequest(
581+ const transferTxRequest = await submitL2Transaction({
591582 api,
592− fromWallet,
593− point,
594− proxy,
583+ wallet: fromWallet,
584+ ship: point,
585+ proxy: 'own',
586+ type: 'transferPoint',
595587 nonce,
596− to,
588+ address: to,
597589 walletType,
598− _web3
599− );
590+ web3: _web3,
591+ });
600592 requests.push(transferTxRequest);
601593
602594 const hashes = await Promise.all(requests);
@@ -724,17 +716,19 @@ export default function useRoller() {
724716
725717 const nonce = await api.getNonce({ ship: _point, proxy });
726718
727− const txHash = await transferPointRequest(
719+ const txHash = await submitL2Transaction({
728720 api,
729− _wallet,
730− _point,
721+ wallet: _wallet,
722+ ship: _point,
731723 proxy,
732724 nonce,
733725 address,
726+ type: 'transferPoint',
734727 walletType,
735− _web3,
736− reset || false
737− );
728+ web3: _web3,
729+ reset,
730+ });
731+
738732 const pendingTx = await api.getPendingTx(txHash);
739733
740734 return pendingTx;
@@ -742,6 +736,114 @@ export default function useRoller() {
742736 [api, pointCursor, wallet, web3, walletType]
743737 );
744738
739+ const sendL2Transaction = useCallback(
740+ async (params: SendL2Params) => {
741+ const _point = need.point(pointCursor);
742+ const _wallet = wallet.getOrElse(null);
743+ const _web3 = web3.getOrElse(null);
744+
745+ if (!_wallet) {
746+ // not using need because we want a custom error
747+ throw new Error('Internal Error: Missing Wallet');
748+ }
749+
750+ return submitL2Transaction({
751+ ...params,
752+ api,
753+ wallet: _wallet,
754+ walletType,
755+ web3: _web3,
756+ ship: _point,
757+ });
758+ },
759+ [api, pointCursor, wallet, web3, walletType]
760+ );
761+
762+ const changeSponsor = useCallback(
763+ async (newSponsor: number) => {
764+ const _wallet = wallet.getOrElse(null);
765+ const _point = need.point(pointCursor);
766+
767+ if (!_wallet) {
768+ // not using need because we want a custom error
769+ throw new Error('Internal Error: Missing Wallet');
770+ }
771+
772+ const pointDetails = await api.getPoint(_point);
773+ const proxy = getManagerProxy(pointDetails.ownership!, _wallet.address);
774+
775+ if (proxy === undefined)
776+ throw new Error("Error: Address doesn't match proxy");
777+
778+ const nonce = await api.getNonce({ ship: _point, proxy });
779+
780+ sendL2Transaction({
781+ newSponsor,
782+ nonce,
783+ proxy,
784+ type: 'escape',
785+ ship: _point,
786+ });
787+ },
788+ [api, sendL2Transaction, wallet, pointCursor]
789+ );
790+
791+ const initPoint = useCallback(
792+ (relationship: Relationship) => async (point: string | number) => {
793+ const _wallet = wallet.getOrElse(null);
794+
795+ if (!_wallet) {
796+ return EMPTY_POINT;
797+ }
798+ try {
799+ const pointNum = Number(point);
800+ const rawDetails = await api.getPoint(pointNum);
801+ const details = toL1Details(rawDetails);
802+
803+ return new Point(pointNum, relationship, details, _wallet.address);
804+ } catch (e) {
805+ console.warn(e);
806+ }
807+ },
808+ [api, wallet]
809+ );
810+
811+ const getPointDetails = useCallback(
812+ async (
813+ ownedPoints: number[],
814+ incomingPoints: number[],
815+ managingPoints: number[],
816+ votingPoints: number[],
817+ spawningPoints: number[]
818+ ) => {
819+ try {
820+ const allPoints = [
821+ await Promise.all(ownedPoints.map(initPoint(Relationship.own))),
822+ await Promise.all(
823+ incomingPoints.map(initPoint(Relationship.transfer))
824+ ),
825+ await Promise.all(managingPoints.map(initPoint(Relationship.manage))),
826+ await Promise.all(votingPoints.map(initPoint(Relationship.vote))),
827+ await Promise.all(spawningPoints.map(initPoint(Relationship.spawn))),
828+ ];
829+
830+ setPoints(
831+ allPoints.reduce((acc: Points, cur: (Point | undefined)[]) => {
832+ cur.forEach(p => {
833+ if (p) {
834+ acc[p.value] = p;
835+ }
836+ });
837+ return acc;
838+ }, {})
839+ );
840+ } catch (e) {
841+ console.warn(e);
842+ }
843+ },
844+ [setPoints, initPoint]
845+ );
846+
745847 // On load, get initial config
746848 useEffect(() => {
747849 if (config) {
@@ -759,30 +861,32 @@ export default function useRoller() {
759861 if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
760862 api.getRollerConfig().then(response => {
761863 setNextBatchTime(response.nextBatch);
762− getNumInvites(currentL2);
864+ getNumInvites(!!point?.isL2);
763865 });
764866 }
765867 }, 10000000);
766868
767869 return () => clearInterval(interval);
768− }, [nextBatchTime, getPendingTransactions, currentL2]); // eslint-disable-line react-hooks/exhaustive-deps
870+ }, [nextBatchTime, getPendingTransactions, point]); // eslint-disable-line react-hooks/exhaustive-deps
769871
770872 return {
771873 adoptPoint,
772874 api,
773− performL2Reticket,
875+ changeSponsor,
774876 config,
775− getPoints,
877+ configureNetworkingKeys,
776878 getInvites,
879+ getNumInvites,
880+ getPoints,
881+ getPointDetails,
777882 getPendingTransactions,
778883 generateInviteCodes,
779884 kickPoint,
780885 rejectPoint,
781886 transferPoint,
887+ ls,
888+ performL2Reticket,
782889 setProxyAddress,
783− configureNetworkingKeys,
784− getNumInvites,
785890 showInvite,
786− ls,
787891 };
788892}
src/lib/utils/address.ts+8−6
@@ -11,15 +11,15 @@ export const addHexPrefix = (hex: string) => {
1111 return hex.startsWith('0x') ? hex : `0x${hex}`;
1212};
1313
14−export const isValidAddress = a => '0x0' === a || isAddress(a);
14+export const isValidAddress = (a: string) => '0x0' === a || isAddress(a);
1515
16−export const abbreviateAddress = address =>
16+export const abbreviateAddress = (address: string) =>
1717 `${address.slice(0, 6)}…${address.slice(-4)}`;
1818
19−export const isZeroAddress = a =>
19+export const isZeroAddress = (a: string) =>
2020 a === ETH_ZERO_ADDR || a === ETH_ZERO_ADDR_SHORT;
2121
22−export const toChecksumAddress = address => {
22+export const toChecksumAddress = (address: string) => {
2323 const addr = stripHexPrefix(address).toLowerCase();
2424 const hash = keccak256(addr).toString('hex');
2525
@@ -31,8 +31,10 @@ export const toChecksumAddress = address => {
3131 );
3232};
3333
34−export const eqAddr = (addr0, addr1) =>
35− toChecksumAddress(addr0) === toChecksumAddress(addr1);
34+export const eqAddr = (addr0: string, addr1: string) =>
35+ !addr0 || !addr1
36+ ? false
37+ : toChecksumAddress(addr0) === toChecksumAddress(addr1);
3638
3739export const publicToAddress = (publicKey: Buffer): string => {
3840 return '0x' + pubToAddress(Buffer.from(publicKey), true).toString('hex');
src/lib/utils/invite.ts+2−2
@@ -30,5 +30,5 @@ export const generateCsvLine = (ind: number, ticket: string, planet: number) =>
3030 planet
3131 )},${planet},${ticket}\n`;
3232
33−export const generateCsvName = (defaultName: string, point: number) =>
34− `${point ? ob.patp(point).slice(1) : 'bridge'}_${defaultName}`;
33+export const generateCsvName = (defaultName: string, patp: string) =>
34+ `${patp}_${defaultName}`;
src/lib/utils/point.ts+24
@@ -1,4 +1,6 @@
1+import { L2Point } from '@urbit/roller-api';
12import { azimuth } from 'azimuth-js';
3+import { L1Point } from 'lib/types/L1Point';
24
35export const isGalaxy = (point: number) =>
46 azimuth.getPointSize(point) === azimuth.PointSize.Galaxy;
@@ -8,3 +10,25 @@ export const isStar = (point: number) =>
810
911export const isPlanet = (point: number) =>
1012 azimuth.getPointSize(point) === azimuth.PointSize.Planet;
13+
14+export const toL1Details = (point: L2Point): L1Point => {
15+ return {
16+ layer: point?.dominion === 'l2' ? 2 : 1,
17+ isL2Spawn: point.dominion === 'spawn' || point.dominion === 'l2',
18+ active: true,
19+ authenticationKey: point?.network?.keys?.auth,
20+ continuityNumber: point?.network?.rift,
21+ cryptoSuiteVersion: point?.network?.keys?.suite,
22+ encryptionKey: point?.network?.keys?.crypt,
23+ escapeRequested: point?.network?.escape ? true : false,
24+ escapeRequestedTo: point?.network?.escape,
25+ hasSponsor: point?.network?.sponsor?.has,
26+ keyRevisionNumber: point?.network?.keys?.life,
27+ managementProxy: point?.ownership?.managementProxy?.address!,
28+ owner: point?.ownership?.owner?.address!,
29+ spawnProxy: point?.ownership?.spawnProxy?.address!,
30+ sponsor: point?.network?.sponsor?.who,
31+ transferProxy: point?.ownership?.transferProxy?.address!,
32+ votingProxy: point?.ownership?.votingProxy?.address!,
33+ };
34+};
src/lib/utils/roller.ts+61−4
@@ -1,8 +1,5 @@
11import { Just, Nothing } from 'folktale/maybe';
2−import { Invite } from 'lib/types/Invite';
3−import { signTransactionHash } from 'lib/authToken';
42import { randomHex } from 'web3-utils';
5−
63import RollerRPCAPI, {
74 Proxy,
85 Signature,
@@ -15,10 +12,13 @@ import RollerRPCAPI, {
1512 PendingTransaction,
1613} from '@urbit/roller-api';
1714
15+import { Invite } from 'lib/types/Invite';
16+import { signTransactionHash } from 'lib/authToken';
1817import { deriveNetworkKeys, CRYPTO_SUITE_VERSION } from 'lib/keys';
1918import { addHexPrefix } from 'lib/utils/address';
2019import { makeDeterministicTicket, generateWallet } from 'lib/walletgen';
2120import { WALLET_TYPES } from 'lib/constants';
21+import { L2TransactionArgs, TransactionData } from 'lib/types/L2Transaction';
2222
2323export const SECOND = 1000;
2424export const MINUTE = SECOND * 60;
@@ -41,7 +41,7 @@ export const getTimeToNextBatch = (nextBatch: number, now: number) => {
4141 return `${padZero(hours)}h ${padZero(minutes)}m ${padZero(seconds)}s`;
4242};
4343
44−export const isL2 = (dom?: string) => dom === 'l2' || dom === 'spawn';
44+export const isL2 = (dom?: string) => dom === 'l2';
4545
4646export const isL2Spawn = (dom?: string) => dom === 'spawn';
4747
@@ -319,6 +319,63 @@ export const registerProxyAddress = async (
319319 return setProxy(api, proxyAddressType, sig, from, wallet.address, data);
320320};
321321
322+const getDataAndMethod = (args: L2TransactionArgs) => {
323+ const { address, api, breach, networkSeed, reset, pointToSpawn } = args;
324+ const result: TransactionData = { data: { address }, method: () => null };
325+
326+ switch (args.type) {
327+ case 'transferPoint':
328+ result.method = api.transferPoint;
329+ result.data.reset = reset || false;
330+ return result;
331+ case 'spawn':
332+ result.method = api.spawn;
333+ result.data.ship = pointToSpawn;
334+ return result;
335+ case 'configureKeys':
336+ // TODO: do something here?
337+ if (Nothing.hasInstance(networkSeed)) {
338+ console.log("Network key Error: couldn't derive network keys");
339+ // throw new Error("Network key Error: couldn't derive network keys");
340+ }
341+
342+ const seed = Just.hasInstance(networkSeed)
343+ ? networkSeed.value
344+ : randomHex(32);
345+
346+ const pair = deriveNetworkKeys(seed);
347+
348+ result.data = {
349+ encrypt: addHexPrefix(pair.crypt.public),
350+ auth: addHexPrefix(pair.auth.public),
351+ cryptoSuite: String(CRYPTO_SUITE_VERSION),
352+ breach: breach || false,
353+ };
354+ result.method = api.configureKeys;
355+ return result;
356+ }
357+};
358+
359+export const submitL2Transaction = async (args: L2TransactionArgs) => {
360+ const { api, wallet, ship, type, proxy, nonce, walletType, web3 } = args;
361+
362+ const from = { ship, proxy };
363+ const { data, method } = getDataAndMethod(args);
364+
365+ const sig = await generateHashAndSign(
366+ api,
367+ wallet,
368+ nonce,
369+ from,
370+ type,
371+ data,
372+ walletType,
373+ web3
374+ );
375+
376+ return method(sig, from, wallet.address, data);
377+};
378+
322379export const reticketL2Point = async () => {};
323380
324381export const hasInvite = (point: number) => (invite: Invite) =>
src/lib/worker/MyWebworker.jsnew file+8
@@ -0,0 +1,8 @@
1+export default function MyWorker(args) {
2+ console.log('ARGS', args)
3+ let onmessage = e => { // eslint-disable-line no-unused-vars
4+ // Write your code here...
5+ console.log('MESSAGE', e)
6+ postMessage("Response");
7+ };
8+}
src/lib/worker/WebWorker.jsnew file+9
@@ -0,0 +1,9 @@
1+export default class WebWorker {
2+ constructor(worker) {
3+ let code = worker.toString();
4+ code = code.substring(code.indexOf("{") + 1, code.lastIndexOf("}"));
5+
6+ const blob = new Blob([code], { type: "application/javascript" });
7+ return new Worker(URL.createObjectURL(blob));
8+ }
9+}
src/store/lib/useControlledPointsStore.js+35−39
@@ -6,14 +6,13 @@ import * as azimuth from 'azimuth-js';
66import { useNetwork } from '../network';
77import { useWallet } from 'store/wallet';
88import useRoller from 'lib/useRoller';
9−import { isL2 } from 'lib/utils/roller';
109
1110const onlyUnique = (value, index, self) => self.indexOf(value) === index;
1211
1312export default function useControlledPointsStore() {
1413 const { contracts } = useNetwork();
1514 const { wallet } = useWallet();
16− const { api, getPoints } = useRoller();
15+ const { getPoints, getPointDetails } = useRoller();
1716
1817 const [controlledPoints, _setControlledPoints] = useState(Nothing());
1918
@@ -30,79 +29,76 @@ export default function useControlledPointsStore() {
3029
3130 try {
3231 const [
33− ownedPoints,
34− incomingPoints,
35− managingPoints,
36− votingPoints,
37− spawningPoints,
3832 ownedPointsL1,
3933 incomingPointsL1,
4034 managingPointsL1,
4135 votingPointsL1,
4236 spawningPointsL1,
37+ ownedPointsL2,
38+ incomingPointsL2,
39+ managingPointsL2,
40+ votingPointsL2,
41+ spawningPointsL2,
4342 ] = await Promise.all([
44− getPoints('own', address),
45− getPoints('transfer', address),
46− getPoints('manage', address),
47− getPoints('vote', address),
48− getPoints('spawn', address),
4943 azimuth.azimuth.getOwnedPoints(_contracts, address),
5044 azimuth.azimuth.getTransferringFor(_contracts, address),
5145 azimuth.azimuth.getManagerFor(_contracts, address),
5246 azimuth.azimuth.getVotingFor(_contracts, address),
5347 azimuth.azimuth.getSpawningFor(_contracts, address),
48+ getPoints('own', address),
49+ getPoints('transfer', address),
50+ getPoints('manage', address),
51+ getPoints('vote', address),
52+ getPoints('spawn', address),
5453 ]);
5554
56− const pointsWithLayers = await Promise.all(
57− ownedPoints.map(async ship => {
58− try {
59− const pointInfo = await api.getPoint(Number(ship));
60− return {
61− point: Number(ship),
62− layer: isL2(pointInfo?.dominion) ? 2 : 1,
63− };
64− } catch (error) {
65− console.warn(error);
66− return { point: ship, layer: 1 };
67− }
68− })
69− );
70−
7155 if (
72− ownedPoints === null &&
73− incomingPoints === null &&
74− managingPoints === null &&
75− votingPoints === null &&
76− spawningPoints === null
56+ ownedPointsL1 === null &&
57+ incomingPointsL1 === null &&
58+ managingPointsL1 === null &&
59+ votingPointsL1 === null &&
60+ spawningPointsL1 === null &&
61+ ownedPointsL2 === null &&
62+ incomingPointsL2 === null &&
63+ managingPointsL2 === null &&
64+ votingPointsL2 === null &&
65+ spawningPointsL2 === null
7766 ) {
7867 _setControlledPoints(
7968 Just(Result.Error('Failed to read the blockchain.'))
8069 );
8170 } else {
71+ getPointDetails(
72+ (ownedPointsL1 || []).concat(ownedPointsL2 || []),
73+ (incomingPointsL1 || []).concat(incomingPointsL2 || []),
74+ (managingPointsL1 || []).concat(managingPointsL2 || []),
75+ (votingPointsL1 || []).concat(votingPointsL2 || []),
76+ (spawningPointsL1 || []).concat(spawningPointsL2 || [])
77+ );
78+
8279 _setControlledPoints(
8380 Just(
8481 Result.Ok({
85− ownedPoints: ownedPoints
82+ ownedPoints: ownedPointsL2
8683 .concat(ownedPointsL1)
8784 .map(Number)
8885 .filter(onlyUnique),
89− incomingPoints: incomingPoints
86+ incomingPoints: incomingPointsL2
9087 .concat(incomingPointsL1)
9188 .map(Number)
9289 .filter(onlyUnique),
93− managingPoints: managingPoints
90+ managingPoints: managingPointsL2
9491 .concat(managingPointsL1)
9592 .map(Number)
9693 .filter(onlyUnique),
97− votingPoints: votingPoints
94+ votingPoints: votingPointsL2
9895 .concat(votingPointsL1)
9996 .map(Number)
10097 .filter(onlyUnique),
101− spawningPoints: spawningPoints
98+ spawningPoints: spawningPointsL2
10299 .concat(spawningPointsL1)
103100 .map(Number)
104101 .filter(onlyUnique),
105− pointsWithLayers,
106102 })
107103 )
108104 );
@@ -111,7 +107,7 @@ export default function useControlledPointsStore() {
111107 console.error('failed to fetch controlled points', error);
112108 _setControlledPoints(Just(Result.Error(error)));
113109 }
114− }, [contracts, wallet, getPoints, api]);
110+ }, [contracts, wallet, getPoints, getPointDetails]);
115111
116112 // sync controlled points whenever wallet or contracts changes
117113 useEffect(() => {
src/store/lib/useDetailsStore.ts+3−25
@@ -2,32 +2,10 @@ import { useState, useCallback } from 'react';
22import { Just, Nothing } from 'folktale/maybe';
33import * as azimuth from 'azimuth-js';
44
5−import useRoller from '../../lib/useRoller';
6−import { useNetwork } from '../network';
7−import { L1Point } from 'lib/types/L1Point';
8−import { L2Point } from '@urbit/roller-api';
95import { isDevelopment } from 'lib/flags';
10−
11−const toL1Details = (point: L2Point): L1Point => {
12− return {
13− dominion: 'l2',
14− active: true,
15− authenticationKey: point.network.keys.auth,
16− continuityNumber: point.network.rift,
17− cryptoSuiteVersion: point.network.keys.suite,
18− encryptionKey: point.network.keys.crypt,
19− escapeRequested: point.network.escape ? true : false,
20− escapeRequestedTo: point.network.escape,
21− hasSponsor: point.network.sponsor.has,
22− keyRevisionNumber: point.network.keys.life,
23− managementProxy: point.ownership?.managementProxy?.address!,
24− owner: point.ownership?.owner?.address!,
25− spawnProxy: point.ownership?.spawnProxy?.address!,
26− sponsor: point.network.sponsor.who,
27− transferProxy: point.ownership?.transferProxy?.address!,
28− votingProxy: point.ownership?.votingProxy?.address!,
29− };
30−};
6+import { toL1Details } from 'lib/utils/point';
7+import useRoller from 'lib/useRoller';
8+import { useNetwork } from '../network';
319
3210export default function useDetailsStore() {
3311 const { contracts } = useNetwork();
src/store/pointCursor.js+18−2
@@ -1,15 +1,31 @@
1−import React, { createContext, forwardRef, useContext, useState } from 'react';
1+import React, {
2+ createContext,
3+ forwardRef,
4+ useCallback,
5+ useContext,
6+ useState,
7+} from 'react';
28import { Nothing } from 'folktale/maybe';
9+import { useRollerStore } from './rollerStore';
310
411export const PointCursorContext = createContext(null);
512
613// pointCursor is Maybe<number>
714function _usePointCursor(initialPointCursor = Nothing()) {
815 const [pointCursor, setPointCursor] = useState(initialPointCursor);
16+ const { setPoint } = useRollerStore();
17+
18+ const handleSetPointCursor = useCallback(
19+ point => {
20+ setPoint(Number(point?.value));
21+ setPointCursor(point);
22+ },
23+ [setPoint, setPointCursor]
24+ );
925
1026 return {
1127 pointCursor,
12− setPointCursor,
28+ setPointCursor: handleSetPointCursor,
1329 };
1430}
1531
src/store/roller.ts → src/store/rollerStore.tsrenamed+18−19
@@ -1,24 +1,27 @@
11import create from 'zustand';
2−import { L2Point, PendingTransaction } from '@urbit/roller-api';
2+import { PendingTransaction } from '@urbit/roller-api';
33
4−import { HOUR, isL2, isL2Spawn } from 'lib/utils/roller';
4+import { HOUR } from 'lib/utils/roller';
55import { Invite } from 'lib/types/Invite';
6+import Point, { Points, Relationship } from 'lib/types/Point';
7+import { toL1Details } from 'lib/utils/point';
8+
9+export const EMPTY_POINT = new Point(-1, Relationship.own, toL1Details({}), '');
610
711export interface RollerStore {
812 nextBatchTime: number;
9− nextRoll: string;
1013 pendingTransactions: PendingTransaction[];
11− currentPoint: L2Point | null;
12− currentL2: boolean;
13− currentL2Spawn: boolean;
14+ point: Point;
15+ points: Points;
16+ pointList: Point[];
1417 invitePoints: number[];
1518 invites: Invite[];
1619 recentlyCompleted: number;
1720 inviteGeneratingNum: number;
1821 setNextBatchTime: (nextBatchTime: number) => void;
19− setNextRoll: (nextRoll: string) => void;
2022 setPendingTransactions: (pendingTransactions: PendingTransaction[]) => void;
21− setCurrentPoint: (currentPoint: L2Point) => void;
23+ setPoint: (point: number) => void;
24+ setPoints: (points: Points) => void;
2225 setInvitePoints: (points: number[]) => void;
2326 setInvites: (invites: Invite[]) => void;
2427 setInviteGeneratingNum: (numGenerating: number) => void;
@@ -26,17 +29,15 @@ export interface RollerStore {
2629
2730export const useRollerStore = create<RollerStore>(set => ({
2831 nextBatchTime: new Date().getTime() + HOUR,
29− nextRoll: '0h 00m 00s',
3032 pendingTransactions: [],
31− currentPoint: null,
32− currentL2: false,
33− currentL2Spawn: false,
33+ point: EMPTY_POINT,
34+ pointList: [],
35+ points: {},
3436 invitePoints: [],
3537 invites: [],
3638 recentlyCompleted: 0,
3739 inviteGeneratingNum: 0,
3840 setNextBatchTime: (nextBatchTime: number) => set(() => ({ nextBatchTime })),
39− setNextRoll: (nextRoll: string) => set(() => ({ nextRoll })),
4041 setPendingTransactions: (pendingTransactions: PendingTransaction[]) =>
4142 set(state => {
4243 const oldPending = state.pendingTransactions.length;
@@ -49,12 +50,10 @@ export const useRollerStore = create<RollerStore>(set => ({
4950 }
5051 return { ...state, pendingTransactions };
5152 }),
52− setCurrentPoint: (currentPoint: L2Point) =>
53− set(() => ({
54− currentPoint,
55− currentL2: isL2(currentPoint?.dominion),
56− currentL2Spawn: isL2Spawn(currentPoint?.dominion),
57− })),
53+ setPoint: (point: number) =>
54+ set(state => ({ point: state.points[point] || EMPTY_POINT })),
55+ setPoints: (points: Points) =>
56+ set(() => ({ points, pointList: Object.values(points) })),
5857 setInvitePoints: (invitePoints: number[]) => set(() => ({ invitePoints })),
5958 setInvites: (invites: Invite[]) => set(() => ({ invites })),
6059 setInviteGeneratingNum: (inviteGeneratingNum: number) =>
src/store/timerStore.tsnew file+16
@@ -0,0 +1,16 @@
1+import create from 'zustand';
2+import { HOUR } from 'lib/utils/roller';
3+
4+export interface TimerStore {
5+ nextBatchTime: number;
6+ nextRoll: string;
7+ setNextBatchTime: (nextBatchTime: number) => void;
8+ setNextRoll: (nextRoll: string) => void;
9+}
10+
11+export const useTimerStore = create<TimerStore>(set => ({
12+ nextBatchTime: new Date().getTime() + HOUR,
13+ nextRoll: '0h 00m 00s',
14+ setNextBatchTime: (nextBatchTime: number) => set(() => ({ nextBatchTime })),
15+ setNextRoll: (nextRoll: string) => set(() => ({ nextRoll })),
16+}));
src/views/Invite/Cohort.tsx+16−11
@@ -8,11 +8,10 @@ import {
88 Button as Button2,
99} from '@tlon/indigo-react';
1010
11−import { usePointCursor } from 'store/pointCursor';
1211import { usePointCache } from 'store/pointCache';
1312import { useWallet } from 'store/wallet';
1413import { useNetwork } from 'store/network';
15−import { useRollerStore } from 'store/roller';
14+import { useRollerStore } from 'store/rollerStore';
1615
1716import { useLocalRouter } from 'lib/LocalRouter';
1817import * as need from 'lib/need';
@@ -45,6 +44,7 @@ import {
4544} from 'lib/constants';
4645import { isPlanet } from 'lib/utils/point';
4746import { generateInviteWallet } from 'lib/utils/roller';
47+import { useTimerStore } from 'store/timerStore';
4848
4949interface L1Invite {
5050 ticket: string;
@@ -55,14 +55,16 @@ export default function InviteCohort() {
5555 const { pop }: any = useLocalRouter();
5656 const { authToken }: any = useWallet();
5757 const {
58− nextRoll,
59− currentL2,
58+ point,
6059 pendingTransactions,
6160 invites,
6261 invitePoints,
6362 setInviteGeneratingNum,
6463 setInvites,
6564 } = useRollerStore();
65+
66+ const { nextRoll } = useTimerStore();
67+
6668 const {
6769 ls,
6870 generateInviteCodes,
@@ -70,11 +72,10 @@ export default function InviteCohort() {
7072 getInvites,
7173 showInvite,
7274 } = useRoller();
73− const { pointCursor }: any = usePointCursor();
7475 const { contracts }: any = useNetwork();
7576 const { syncControlledPoints }: any = usePointCache();
77+ const currentL2 = Boolean(point?.isL2);
7678
77− const point = pointCursor.getOrElse(null);
7879 const _contracts = need.contracts(contracts);
7980
8081 const [numInvites, setNumInvites] = useState(
@@ -232,7 +233,10 @@ export default function InviteCohort() {
232233 hiddenElement.target = '_blank';
233234
234235 //provide the name for the CSV file to be downloaded
235− hiddenElement.download = generateCsvName(DEFAULT_CSV_NAME, point);
236+ hiddenElement.download = generateCsvName(
237+ DEFAULT_CSV_NAME,
238+ point?.patp?.slice(1) || 'bridge'
239+ );
236240 hiddenElement.click();
237241 } else {
238242 setError('There was an error creating the CSV');
@@ -343,10 +347,11 @@ export default function InviteCohort() {
343347 <StatelessTextInput
344348 className="input-box"
345349 value={numInvites}
346− maxLength="3"
347− onChange={e =>
348− setNumInvites(Number(e.target.value.replace(/\D/g, '')))
349− }
350+ maxLength={3}
351+ onChange={e => {
352+ const target = e.target as HTMLInputElement;
353+ setNumInvites(Number(target.value.replace(/\D/g, '')));
354+ }}
350355 />
351356 planet invite code{numInvites > 1 ? 's' : ''}
352357 </Row>
src/views/Login/LoginSelector.tsx+22−17
@@ -21,7 +21,7 @@ import Window from 'components/L2/Window/Window';
2121import HeaderPane from 'components/L2/Window/HeaderPane';
2222import BodyPane from 'components/L2/Window/BodyPane';
2323import Modal from 'components/L2/Modal';
24−import L2BackButton from 'components/L2/Headers/L2BackButton';
24+import HeaderButton from 'components/L2/Headers/HeaderButton';
2525import './LoginSelector.scss';
2626import { NAMES } from './Other';
2727
@@ -147,21 +147,22 @@ export default function LoginSelector({
147147 <Box className="close" onClick={() => setShowModal(false)}>
148148 &#215;
149149 </Box>
150− <div className="fw-bold">Other Wallet Types</div>
151− <div className="mb5">All other wallet types are now supported via Metamask or
152− WalletConnect.</div>
153− <div className="mb5">
154− If you are you using a{' '}
155− <span className="fw-bold">
156− Hardware Wallet, Ethereum Keystore, or Ethereum Private Key
157− </span>{' '}
158− please use Metamask going forward.
159− </div>
160− <div>
161− If you are using a{' '}
162− <span className="fw-bold">mobile wallet</span>, please use
163− WalletConnect going forward.
164− </div>
150+ <div className="fw-bold">Other Wallet Types</div>
151+ <div className="mb5">
152+ All other wallet types are now supported via Metamask or
153+ WalletConnect.
154+ </div>
155+ <div className="mb5">
156+ If you are you using a{' '}
157+ <span className="fw-bold">
158+ Hardware Wallet, Ethereum Keystore, or Ethereum Private Key
159+ </span>{' '}
160+ please use Metamask going forward.
161+ </div>
162+ <div>
163+ If you are using a <span className="fw-bold">mobile wallet</span>,
164+ please use WalletConnect going forward.
165+ </div>
165166 </Box>
166167 </Modal>
167168 </Grid>
@@ -173,7 +174,11 @@ export default function LoginSelector({
173174
174175 return (
175176 <Box display="flex" flexDirection="column">
176− <L2BackButton className="mb4" onBack={() => setCurrentTab(undefined)} />
177+ <HeaderButton
178+ className="mb4"
179+ icon="ChevronWest"
180+ onClick={() => setCurrentTab(undefined)}
181+ />
177182 <Window>
178183 <HeaderPane>
179184 <Row className="header-row">
src/views/Point/MigrateL2.tsx+51−36
@@ -3,6 +3,9 @@ import { Grid } from 'indigo-react';
33import { ecliptic } from 'azimuth-js';
44import * as ob from 'urbit-ob';
55
6+import { useLocalRouter } from 'lib/LocalRouter';
7+import * as need from 'lib/need';
8+
69import View from 'components/View';
710import L2BackHeader from 'components/L2/Headers/L2BackHeader';
811import BodyPane from 'components/L2/Window/BodyPane';
@@ -10,11 +13,6 @@ import HeaderPane from 'components/L2/Window/HeaderPane';
1013import Window from 'components/L2/Window/Window';
1114import { ReactComponent as StarIcon } from 'assets/star.svg';
1215
13−import { useLocalRouter } from 'lib/LocalRouter';
14−import * as need from 'lib/need';
15−
16−import { usePointCursor } from 'store/pointCursor';
17−
1816import {
1917 Box,
2018 Button,
@@ -33,11 +31,12 @@ import { usePointCache } from 'store/pointCache';
3331import useEthereumTransaction from 'lib/useEthereumTransaction';
3432import { DUMMY_L2_ADDRESS, GAS_LIMITS } from 'lib/constants';
3533import Dropdown from 'components/L2/Dropdowns/Dropdown';
36−import { PointLayer } from 'lib/types/PointLayer';
3734import Sigil from 'components/Sigil';
38−import { isPlanet, isStar } from 'lib/utils/point';
35+import { isPlanet } from 'lib/utils/point';
3936
4037import './MigrateL2.scss';
38+import { useRollerStore } from 'store/rollerStore';
39+import Point from 'lib/types/Point';
4140
4241const PointEntry = ({
4342 point,
@@ -46,6 +45,14 @@ const PointEntry = ({
4645 point: number;
4746 select?: () => void;
4847}) => {
48+ if (point < 0) {
49+ return (
50+ <Row className="entry" onClick={select}>
51+ <Box>Select a point</Box>
52+ </Row>
53+ );
54+ }
55+
4956 const patp = ob.patp(point);
5057
5158 return (
@@ -69,6 +76,9 @@ const useMigrate = () => {
6976 return useEthereumTransaction(
7077 useCallback(
7178 (selectedPoint: number, transfer: boolean) => {
79+ if (selectedPoint < 0) {
80+ return;
81+ }
7282 setSelectedPoint(selectedPoint);
7383 return transfer || isPlanet(selectedPoint)
7484 ? ecliptic.transferPoint(
@@ -88,32 +98,27 @@ const useMigrate = () => {
8898
8999export default function MigrateL2() {
90100 const { pop }: any = useLocalRouter();
91− const { pointCursor }: any = usePointCursor();
92− const { controlledPoints }: any = usePointCache();
101+ const { point, pointList } = useRollerStore();
102+ const l1Points = pointList.filter(({ canMigrate }) => canMigrate);
93103
94104 const [proceed, setProceed] = useState(false);
95105 const [hideMessage, setHideMessage] = useState(false);
96106 const [showDropdown, setShowDropdown] = useState(false);
97107 const [transfer, setTransfer] = useState(true);
98108
99− const points = controlledPoints?.value?.value?.pointsWithLayers || [];
100− const l1Points = points.filter(({ layer }: PointLayer) => layer === 1);
101−
102− let { point } =
103− l1Points.find((p: any) => isStar(p.point)) || controlledPoints[0];
104−
105− try {
106− point = need.point(pointCursor);
107− } catch (e) {}
108−
109− const [selectedPoint, setSelectedPoint] = useState(point);
109+ const [selectedPoint, setSelectedPoint] = useState(
110+ point.isL1 ? point : l1Points[0]
111+ );
110112 const hideInfo = getHideMigrationMessage();
111113
112114 const { construct, unconstruct, bind, completed } = useMigrate();
113115
114116 useEffect(() => {
115− if (point) {
116− construct(point, transfer);
117+ if (point.isGalaxy && point.isL1) {
118+ setTransfer(false);
119+ construct(point.value, false);
120+ } else if (point.isL1) {
121+ construct(point.value, transfer);
117122 } else {
118123 unconstruct();
119124 }
@@ -142,35 +147,38 @@ export default function MigrateL2() {
142147 }, [hideMessage]);
143148
144149 const selectPoint = useCallback(
145− (point: number) => {
146− if (!isStar(point)) {
150+ (point: Point) => {
151+ if (point.isPlanet || (point.isStar && point.isL2Spawn)) {
147152 setTransfer(true);
153+ } else if (point.isGalaxy) {
154+ setTransfer(false);
148155 }
149156 setSelectedPoint(point);
150157 setShowDropdown(false);
151− construct(point, transfer);
158+ construct(point.value, transfer);
152159 },
153160 [setSelectedPoint, setShowDropdown, construct, transfer]
154161 );
155162
156163 const toggleTransfer = useCallback(() => {
157− construct(selectedPoint, !transfer);
164+ construct(selectedPoint.value, !transfer);
158165 setTransfer(!transfer);
159166 }, [transfer, setTransfer, construct, selectedPoint]);
160167
161168 const getContent = () => {
162− const star = isStar(selectedPoint);
163−
164169 if (l1Points.length === 0) {
165170 return (
166171 <Box className="content">
167172 <Box className="ship-selector">
168− <Box>All of your ships are already on Layer 2.</Box>
173+ <Box>All of your eligible ships are already on Layer 2.</Box>
169174 </Box>
170175 </Box>
171176 );
172177 }
173178
179+ const isStar = Boolean(selectedPoint?.isStar);
180+ const isGalaxy = Boolean(selectedPoint?.isGalaxy);
181+
174182 if (proceed) {
175183 return (
176184 <Box className="content">
@@ -180,17 +188,17 @@ export default function MigrateL2() {
180188 <Dropdown
181189 open={showDropdown}
182190 toggleOpen={() => setShowDropdown(!showDropdown)}
183− value={<PointEntry point={selectedPoint} />} // change this to include the sigil
191+ value={<PointEntry point={selectedPoint.value} />}
184192 className="migrate-selector">
185− {l1Points.map(({ point }: PointLayer) => (
193+ {l1Points.map((p: Point) => (
186194 <PointEntry
187− point={point}
188− key={point}
189− select={() => selectPoint(point)}
195+ point={p.value}
196+ key={p.value}
197+ select={() => selectPoint(p)}
190198 />
191199 ))}
192200 </Dropdown>
193− {star && (
201+ {isStar && !point.isL2Spawn && (
194202 <Box className="transfer-spawn-selector">
195203 <Row onClick={toggleTransfer} className="transfer">
196204 <RadioButton
@@ -212,7 +220,7 @@ export default function MigrateL2() {
212220 </Row>
213221 </Box>
214222 )}
215− {star && (
223+ {isStar && (
216224 <Box>
217225 Transferring this point will allow you to conduct all
218226 transactions on Layer 2. Setting the spawn proxy of a star to
@@ -220,6 +228,13 @@ export default function MigrateL2() {
220228 will require you to do all other transactions on Layer 1.
221229 </Box>
222230 )}
231+ {isGalaxy && (
232+ <Box className="mt4">
233+ Setting the spawn proxy of this galaxy to Layer 2 will allow you
234+ to spawn stars on Layer 2 for free, but will still require you
235+ to do all other transactions on Layer 1.
236+ </Box>
237+ )}
223238 </Box>
224239 <Grid.Item
225240 full
src/views/Point/Point.tsx+49−75
@@ -3,9 +3,10 @@ import { Just } from 'folktale/maybe';
33import { Grid, Flex } from 'indigo-react';
44import { Box, Icon, Row, Button, Checkbox } from '@tlon/indigo-react';
55
6−import { usePointCursor } from 'store/pointCursor';
6+import { usePointCache } from 'store/pointCache';
77import { useWallet } from 'store/wallet';
8−import { useRollerStore } from 'store/roller';
8+import { useRollerStore } from 'store/rollerStore';
9+import { useTimerStore } from 'store/timerStore';
910
1011import View from 'components/View';
1112import Greeting from 'components/Greeting';
@@ -21,34 +22,21 @@ import useCurrentPermissions from 'lib/useCurrentPermissions';
2122import { useLocalRouter } from 'lib/LocalRouter';
2223import useRoller from 'lib/useRoller';
2324import useSeenMissingKeys from 'lib/useSeenMissingKeys';
24−
25−import './Point.scss';
26−import { isL2 } from 'lib/utils/roller';
27−import { isDevelopment } from 'lib/flags';
28−import { usePointCache } from 'store/pointCache';
29−import Modal from 'components/L2/Modal';
30−import { isPlanet, isStar } from 'lib/utils/point';
3125import { useHasNetworkKeysSet } from 'lib/useHasNetworkKeysSet';
26+
3227import { InviteForm } from './InviteForm';
28+import Modal from 'components/L2/Modal';
29+import './Point.scss';
3330import LoadingOverlay from 'components/L2/LoadingOverlay';
3431
3532export default function Point() {
3633 const { pop, push, names }: any = useLocalRouter();
3734 const { wallet }: any = useWallet();
38− const { pointCursor }: any = usePointCursor();
39− const point = need.point(pointCursor);
40− const networkKeysSet = useHasNetworkKeysSet();
4135 const { syncExtras }: any = usePointCache();
42−
43− const { api, getNumInvites, getPendingTransactions } = useRoller();
44− const {
45− pendingTransactions,
46− nextRoll,
47− invitePoints,
48− setCurrentPoint,
49− setInvites,
50− } = useRollerStore();
51−
36+ const { getNumInvites, getPendingTransactions } = useRoller();
37+ const { pendingTransactions, point, invites } = useRollerStore();
38+ const { nextRoll } = useTimerStore();
39+ const networkKeysSet = useHasNetworkKeysSet();
5240 const [seenMissingKeys, setSeenMissingKeys] = useSeenMissingKeys();
5341 const [showModal, setShowModal] = useState(false);
5442 const [hideMessage, setHideMessage] = useState(false);
@@ -56,6 +44,8 @@ export default function Point() {
5644
5745 const pointRef = useRef<number | null>(null);
5846
47+ const invitePoints = invites[point.value] || [];
48+
5949 const hideModal = useCallback(() => {
6050 if (hideMessage) {
6151 setSeenMissingKeys(true);
@@ -65,54 +55,36 @@ export default function Point() {
6555 }, [hideMessage, setShowModal, setSeenMissingKeys]);
6656
6757 useEffect(() => {
68− if (pointRef.current !== point) {
69− pointRef.current = point;
70− setShowModal(!networkKeysSet && !seenMissingKeys);
58+ if (!pointRef.current || pointRef.current !== point.value) {
59+ pointRef.current = point.value;
60+ setShowModal(!point.networkKeysSet && !seenMissingKeys);
7161 }
72− }, [networkKeysSet, seenMissingKeys, point]);
62+ }, [seenMissingKeys, point]);
7363
7464 const loadL1Info = useCallback(async () => {
75− await syncExtras(point);
65+ await syncExtras(point.value);
7666 }, [point, syncExtras]);
7767
7868 const loadL2Info = useCallback(async () => {
7969 const getTransactions = async () => {
8070 setLoading(true);
81− const pointInfo = await api.getPoint(Number(point));
82−
83− if (isDevelopment) {
84− console.log('POINT INFO', pointInfo);
85− }
8671 getPendingTransactions();
87− setCurrentPoint(pointInfo);
88− getNumInvites(isL2(pointInfo.dominion));
89−
72+ getNumInvites(point.isL2);
9073 setTimeout(() => setLoading(false), 100);
9174 };
9275
9376 await getTransactions();
94− }, [
95− api,
96− point,
97− getNumInvites,
98− setCurrentPoint,
99− getPendingTransactions,
100− setLoading,
101− ]);
77+ }, [point, getNumInvites, getPendingTransactions, setLoading]);
10278
10379 useEffect(() => {
10480 loadL1Info();
10581 loadL2Info();
10682 }, [point]); // eslint-disable-line react-hooks/exhaustive-deps
10783
108− useEffect(() => {
109− setInvites([]);
110− }, []); // eslint-disable-line react-hooks/exhaustive-deps
111−
112− const { isParent, canManage } = useCurrentPermissions();
84+ const { isParent, canManage, isSpawnProxy } = useCurrentPermissions();
11385
11486 // L1 invites
115− const l1Invites = useInvites(point);
87+ const l1Invites = useInvites(point.value);
11688 const {
11789 availableInvites,
11890 sentInvites,
@@ -141,8 +113,6 @@ export default function Point() {
141113
142114 const goOps = useCallback(() => push(names.OPS), [push, names]);
143115
144− const planet = isPlanet(point);
145−
146116 const [showInviteForm, setShowInviteForm] = useState(false);
147117
148118 const address = need.addressFromWallet(wallet);
@@ -166,7 +136,7 @@ export default function Point() {
166136 hideInvites={!networkKeysSet}
167137 />
168138 }>
169− <Greeting point={point} />
139+ <Greeting point={point.value} />
170140 {!!spawnedPending && (
171141 <div className="transaction">
172142 <Row className="title-row">
@@ -215,7 +185,7 @@ export default function Point() {
215185 </div>
216186 ))}
217187 <Passport
218− point={Just(point)}
188+ point={Just(point.value)}
219189 address={Just(address)}
220190 animationMode={'slide'}
221191 />
@@ -224,9 +194,9 @@ export default function Point() {
224194 <Grid.Item full as={Flex} justify="between">
225195 <Card
226196 icon={(<Icon icon="ShipActivated" />) as any}
227− title={`${isStar(point) ? 'Star' : 'Galaxy'} Ops`}
197+ title={`${point.isStar ? 'Star' : 'Galaxy'} Ops`}
228198 subtitle={
229− isStar(point)
199+ point.isStar
230200 ? 'Residents, Requests, Spawn Planets'
231201 : 'Residents, Requests, Spawn Stars, Vote'
232202 }
@@ -234,7 +204,7 @@ export default function Point() {
234204 />
235205 </Grid.Item>
236206 )}
237− {planet && hasInvites && (
207+ {point.isPlanet && hasInvites && (
238208 <InviteForm
239209 showInviteForm={showInviteForm}
240210 setShowInviteForm={setShowInviteForm}
@@ -247,30 +217,34 @@ export default function Point() {
247217 showInvites={showInvites}
248218 />
249219 )}
250− {!loadedInvites && planet && (
220+ {!loadedInvites && point.isPlanet && (
251221 <Grid.Item className="mv2" full>
252222 Invite Group <Blinky />
253223 </Grid.Item>
254224 )}
255225 {/* {inviteButton} */}
256− <Grid.Item full as={Flex} justify="between">
257− <Card
258− title="ID"
259− subtitle="Master Key, Passport, Proxy Addresses, etc"
260− icon={(<Icon icon="User" />) as any}
261− onClick={goUrbitID}
262− disabled={!canManage}
263− />
264− </Grid.Item>
265− <Grid.Item full as={Flex} justify="between">
266− <Card
267− title="OS"
268− subtitle="Sponsor, Network Keys, Access Key"
269− icon={(<Icon icon="Server" />) as any}
270− onClick={goUrbitOS}
271− disabled={!canManage}
272− />
273− </Grid.Item>
226+ {(canManage || isSpawnProxy) && (
227+ <Grid.Item full as={Flex} justify="between">
228+ <Card
229+ title="ID"
230+ subtitle="Master Key, Passport, Proxy Addresses, etc"
231+ icon={(<Icon icon="User" />) as any}
232+ onClick={goUrbitID}
233+ disabled={!canManage}
234+ />
235+ </Grid.Item>
236+ )}
237+ {canManage && (
238+ <Grid.Item full as={Flex} justify="between">
239+ <Card
240+ title="OS"
241+ subtitle="Sponsor, Network Keys, Access Key"
242+ icon={(<Icon icon="Server" />) as any}
243+ onClick={goUrbitOS}
244+ disabled={!canManage}
245+ />
246+ </Grid.Item>
247+ )}
274248 </Grid>
275249 <Modal show={showModal} hide={hideModal}>
276250 <Box className="network-keys-modal">
src/views/Points.js+2−1
@@ -317,7 +317,7 @@ export default function Points() {
317317 inset
318318 pop={pop}
319319 hideBack
320− header={<L2PointHeader hideTimer hideInvites showMigrate />}>
320+ header={<L2PointHeader hideHome hideTimer hideInvites showMigrate />}>
321321 <NavHeader>
322322 <CopiableAddress
323323 text={address}
@@ -327,6 +327,7 @@ export default function Points() {
327327 </NavHeader>
328328 {incomingPoints.map(point => (
329329 <IncomingPoint
330+ key={point}
330331 point={point}
331332 accept={() => {
332333 setPointCursor(Just(point));
src/views/Transactions/TransactionRow.tsx+1−1
@@ -11,7 +11,7 @@ import {
1111} from 'lib/constants';
1212import { abbreviateAddress } from 'lib/utils/address';
1313import { useMemo } from 'react';
14−import { useRollerStore } from 'store/roller';
14+import { useRollerStore } from 'store/rollerStore';
1515import LayerIndicator from 'components/L2/LayerIndicator';
1616import { isPlanet, isStar } from 'lib/utils/point';
1717
src/views/UrbitID/Home.tsx+9−18
@@ -1,5 +1,4 @@
11import React, { useCallback } from 'react';
2−import { azimuth } from 'azimuth-js';
32import { Just } from 'folktale/maybe';
43import { Box, Button, Icon, Row } from '@tlon/indigo-react';
54
@@ -11,10 +10,9 @@ import { convertToInt } from 'lib/convertToInt';
1110import { ETH_ZERO_ADDR } from 'lib/constants';
1211import { abbreviateAddress } from 'lib/utils/address';
1312
14−import { usePointCursor } from 'store/pointCursor';
1513import { usePointCache } from 'store/pointCache';
1614import { useWallet } from 'store/wallet';
17−import { useRollerStore } from 'store/roller';
15+import { useRollerStore } from 'store/rollerStore';
1816
1917import Window from 'components/L2/Window/Window';
2018import HeaderPane from 'components/L2/Window/HeaderPane';
@@ -25,10 +23,9 @@ import './UrbitID.scss';
2523
2624export default function UrbitIDHome() {
2725 const { push, names }: any = useLocalRouter();
28− const { pointCursor }: any = usePointCursor();
2926 const { getDetails }: any = usePointCache();
3027 const { urbitWallet }: any = useWallet();
31− const { currentPoint } = useRollerStore();
28+ const { point } = useRollerStore();
3229
3330 const goDownloadKeys = useCallback(() => push(names.DOWNLOAD_KEYS), [
3431 push,
@@ -40,15 +37,10 @@ export default function UrbitIDHome() {
4037 const goResetKeys = useCallback(() => push(names.RESET_KEYS), [push, names]);
4138
4239 const isMasterTicket = Just.hasInstance(urbitWallet);
43− const point = need.point(pointCursor);
44− const details = need.details(getDetails(point));
45− const { isOwner } = useCurrentPermissions();
40+ const details = need.details(getDetails(point.value));
41+ const { isOwner, canManage } = useCurrentPermissions();
4642 const networkRevision = convertToInt(details.keyRevisionNumber, 10);
4743
48− const pointSize = azimuth.getPointSize(point);
49− const isParent = pointSize !== azimuth.PointSize.Planet;
50− const isSenate = pointSize === azimuth.PointSize.Galaxy;
51−
5244 const goSetProxy = useCallback(
5345 proxyType => push(names.SET_PROXY, { proxyType }),
5446 [push, names]
@@ -58,8 +50,7 @@ export default function UrbitIDHome() {
5850
5951 const noManagement = details.managementProxy === ETH_ZERO_ADDR;
6052 const noSpawn = details.spawnProxy === ETH_ZERO_ADDR;
61− const showSpawn = currentPoint?.dominion === 'l1';
62− const showManage = showSpawn || currentPoint?.dominion === 'spawn';
53+ const showSpawn = point.isL1 && !point.isL2Spawn;
6354
6455 return (
6556 <Window className="id-home">
@@ -94,7 +85,7 @@ export default function UrbitIDHome() {
9485 </Row>
9586 )}
9687
97− {isOwner && showManage && (
88+ {canManage && (
9889 <Row className="between-row management">
9990 <Box>
10091 <Box>Management Address</Box>
@@ -120,7 +111,7 @@ export default function UrbitIDHome() {
120111 </Row>
121112 )}
122113
123− {isParent && networkRevision !== 0 && showSpawn && (
114+ {point.isParent && networkRevision !== 0 && showSpawn && (
124115 <Row className="between-row management">
125116 <Box>
126117 <Box>Spawn Proxy Address</Box>
@@ -160,7 +151,7 @@ export default function UrbitIDHome() {
160151 </Row>
161152 )}
162153
163− {isParent && networkRevision === 0 && (
154+ {point.isParent && networkRevision === 0 && (
164155 <Row className="between-row management">
165156 <Box>
166157 <Box>Network Keys Required</Box>
@@ -179,7 +170,7 @@ export default function UrbitIDHome() {
179170 <Icon className="arrow-button" icon="ArrowEast" />
180171 </Row>
181172
182− {isSenate && (
173+ {point.isGalaxy && (
183174 <Row className="between-row management">
184175 <Box>
185176 <Box>Edit Voting Key</Box>
src/views/UrbitID/ResetKeys/ResetExecute.js+5−3
@@ -16,7 +16,7 @@ import { useWallet } from 'store/wallet';
1616import { usePointCursor } from 'store/pointCursor';
1717import { usePointCache } from 'store/pointCache';
1818import { useHistory } from 'store/history';
19−import { useRollerStore } from 'store/roller';
19+import { useRollerStore } from 'store/rollerStore';
2020
2121import useRoller from 'lib/useRoller';
2222import { useWalletConnect } from 'lib/useWalletConnect';
@@ -56,7 +56,9 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
5656 walletHdPath,
5757 } = useWallet();
5858 const { performL2Reticket } = useRoller();
59− const { currentL2 } = useRollerStore();
59+ const {
60+ point: { isL2 },
61+ } = useRollerStore();
6062 const { pointCursor } = usePointCursor();
6163 const { getDetails } = usePointCache();
6264 const {
@@ -96,7 +98,7 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
9698 walletType === WALLET_TYPES.WALLET_CONNECT ? wcSend : undefined;
9799
98100 try {
99− if (currentL2) {
101+ if (isL2) {
100102 console.log(newWallet.value);
101103 // FIXME: the useEffect is called twice, which is fine since the duplicate
102104 // L2 txs will be discarded
src/views/UrbitID/SetProxy.js+23−12
@@ -8,7 +8,7 @@ import './SetProxy.scss';
88import { useNetwork } from 'store/network';
99import { usePointCache } from 'store/pointCache';
1010import { usePointCursor } from 'store/pointCursor';
11−import { useRollerStore } from 'store/roller';
11+import { useRollerStore } from 'store/rollerStore';
1212
1313import {
1414 PROXY_TYPE,
@@ -65,6 +65,19 @@ const proxyFromDetails = (details, contracts, proxyType) => {
6565 }
6666};
6767
68+const getButtonText = proxyType => {
69+ switch (proxyType) {
70+ case PROXY_TYPE.MANAGEMENT:
71+ return 'Set Management Proxy';
72+ case PROXY_TYPE.SPAWN:
73+ return 'Set Spawn Proxy';
74+ case PROXY_TYPE.TRANSFER:
75+ return 'Set Transfer Proxy';
76+ default:
77+ return 'Set Management Proxy';
78+ }
79+};
80+
6881function useSetProxy(proxyType) {
6982 const { contracts } = useNetwork();
7083 const { pointCursor } = usePointCursor();
@@ -114,7 +127,9 @@ export default function SetProxy() {
114127 const { getDetails } = usePointCache();
115128 const { pointCursor } = usePointCursor();
116129 const { contracts } = useNetwork();
117− const { currentL2, currentL2Spawn } = useRollerStore();
130+ const {
131+ point: { isL2, isL2Spawn },
132+ } = useRollerStore();
118133 const { setProxyAddress, getPendingTransactions } = useRoller();
119134 const [newAddress, setNewAddress] = useState('');
120135
@@ -124,14 +139,10 @@ export default function SetProxy() {
124139
125140 const properProxyType = capitalize(proxyTypeToHuman(data.proxyType));
126141
127− const txType =
128− data.proxyType === 'MANAGEMENT' && (currentL2Spawn || !currentL2)
129− ? 'ETH_TX'
130− : data.proxyType === 'MANAGEMENT' && currentL2 && !currentL2Spawn
131− ? 'L2'
132− : data.proxyType === 'SPAWN' && currentL2
133− ? 'L2'
134− : 'ETH_TX';
142+ const isL2Tx =
143+ (data.proxyType === 'MANAGEMENT' && isL2) ||
144+ (data.proxyType === 'SPAWN' && isL2Spawn) ||
145+ (data.proxyType === 'TRANSFER' && isL2);
135146
136147 const {
137148 construct,
@@ -269,7 +280,7 @@ export default function SetProxy() {
269280
270281 <Grid.Item full as={FormError} />
271282
272− {txType === 'L2' ? (
283+ {isL2Tx ? (
273284 <Grid.Item
274285 as={Button}
275286 full
@@ -278,7 +289,7 @@ export default function SetProxy() {
278289 solid
279290 disabled={values.unset}
280291 onClick={setProxy}>
281− {'Sign Transaction'}
292+ {getButtonText(data.proxyType)}
282293 </Grid.Item>
283294 ) : (
284295 <Grid.Item
src/views/UrbitID/Transfer.js+8−6
@@ -8,7 +8,7 @@ import { useNetwork } from 'store/network';
88import { usePointCursor } from 'store/pointCursor';
99import { usePointCache } from 'store/pointCache';
1010import { useStarReleaseCache } from 'store/starRelease';
11−import { useRollerStore } from 'store/roller';
11+import { useRollerStore } from 'store/rollerStore';
1212
1313import * as need from 'lib/need';
1414import { useLocalRouter } from 'lib/LocalRouter';
@@ -50,7 +50,9 @@ export default function AdminTransfer() {
5050 const name = useCurrentPointName();
5151 const { pointCursor } = usePointCursor();
5252 const { starReleaseDetails } = useStarReleaseCache();
53− const { currentL2 } = useRollerStore();
53+ const {
54+ point: { isL2 },
55+ } = useRollerStore();
5456 const { setProxyAddress, getPendingTransactions } = useRoller();
5557 const [owner, setNewOwner] = useState('');
5658
@@ -77,7 +79,7 @@ export default function AdminTransfer() {
7779 []
7880 );
7981
80− const onSignTx = useCallback(async () => {
82+ const transferPoint = useCallback(async () => {
8183 // setLoading(true);
8284 if (owner === '') return;
8385 try {
@@ -157,7 +159,7 @@ export default function AdminTransfer() {
157159
158160 <Grid.Item full as={FormError} />
159161
160− {currentL2 ? (
162+ {isL2 ? (
161163 <Grid.Item
162164 as={Button}
163165 full
@@ -165,8 +167,8 @@ export default function AdminTransfer() {
165167 center
166168 solid
167169 disabled={values.unset}
168− onClick={onSignTx}>
169− {'Sign Transaction'}
170+ onClick={transferPoint}>
171+ {'Transfer'}
170172 </Grid.Item>
171173 ) : (
172174 <Grid.Item
src/views/UrbitOS/ChangeSponsor.js+39−9
@@ -1,5 +1,5 @@
11import React, { useState, useMemo, useCallback, useEffect } from 'react';
2−import { Grid } from 'indigo-react';
2+import { Grid, Button } from 'indigo-react';
33import * as ob from 'urbit-ob';
44import { azimuth, ecliptic } from 'azimuth-js';
55
@@ -27,6 +27,8 @@ import Window from 'components/L2/Window/Window';
2727import HeaderPane from 'components/L2/Window/HeaderPane';
2828import { Box, Row } from '@tlon/indigo-react';
2929import BodyPane from 'components/L2/Window/BodyPane';
30+import { useRollerStore } from 'store/rollerStore';
31+import useRoller from 'lib/useRoller';
3032
3133function useChangeSponsor() {
3234 const { contracts } = useNetwork();
@@ -50,11 +52,23 @@ function useChangeSponsor() {
5052function ChangeSponsor({ onDone }) {
5153 const { contracts } = useNetwork();
5254 const { pointCursor } = usePointCursor();
55+ const { changeSponsor } = useRoller();
56+ const {
57+ point: { isL2 },
58+ } = useRollerStore();
5359
5460 const _contracts = need.contracts(contracts);
5561 const point = need.point(pointCursor);
5662 const { construct, unconstruct, inputsLocked, bind } = useChangeSponsor();
5763
64+ const [newSponsor, setNewSponsor] = useState();
65+
66+ const requestNewSponsor = useCallback(async () => {
67+ if (newSponsor !== undefined) {
68+ changeSponsor(newSponsor);
69+ }
70+ }, [newSponsor, changeSponsor]);
71+
5872 const validateFormAsync = useCallback(
5973 async values => {
6074 const sponsor = ob.patp2dec(values.sponsor);
@@ -92,13 +106,17 @@ function ChangeSponsor({ onDone }) {
92106 const onValues = useCallback(
93107 ({ valid, values }) => {
94108 if (valid) {
95− construct(ob.patp2dec(values.sponsor));
109+ const sponsorPoint = ob.patp2dec(values.sponsor);
110+ construct(sponsorPoint);
111+ setNewSponsor(Number(sponsorPoint));
96112 } else {
97113 unconstruct();
114+ setNewSponsor(undefined);
98115 }
99116 },
100117 [construct, unconstruct]
101118 );
119+
102120 return (
103121 <Window>
104122 <HeaderPane>
@@ -119,13 +137,25 @@ function ChangeSponsor({ onDone }) {
119137 className="mv4"
120138 />
121139 <Grid.Item full as={FormError} />
122− <Grid.Item
123− full
124− as={InlineEthereumTransaction}
125− {...bind}
126− onReturn={onDone}
127− label="Request"
128− />
140+ {isL2 ? (
141+ <Grid.Item
142+ as={Button}
143+ full
144+ className=""
145+ center
146+ solid
147+ onClick={requestNewSponsor}>
148+ {'Request New Sponsor'}
149+ </Grid.Item>
150+ ) : (
151+ <Grid.Item
152+ full
153+ as={InlineEthereumTransaction}
154+ {...bind}
155+ onReturn={onDone}
156+ label="Request"
157+ />
158+ )}
129159 </Box>
130160 )}
131161 </BridgeForm>
src/views/UrbitOS/NetworkingKeys.js+7−5
@@ -8,7 +8,7 @@ import { usePointCursor } from 'store/pointCursor';
88import { usePointCache } from 'store/pointCache';
99import { useNetwork } from 'store/network';
1010import { useWallet } from 'store/wallet';
11−import { useRollerStore } from 'store/roller';
11+import { useRollerStore } from 'store/rollerStore';
1212
1313import useRoller from 'lib/useRoller';
1414import { useLocalRouter } from 'lib/LocalRouter';
@@ -146,7 +146,9 @@ export default function UrbitOSNetworkingKeys({
146146 const { pop } = useLocalRouter();
147147 const { pointCursor } = usePointCursor();
148148 const { getDetails } = usePointCache();
149− const { currentL2, currentL2Spawn } = useRollerStore();
149+ const {
150+ point: { isL2 },
151+ } = useRollerStore();
150152 const { configureNetworkingKeys, getPendingTransactions } = useRoller();
151153 const [breach, setBreach] = useState(false);
152154
@@ -213,7 +215,7 @@ export default function UrbitOSNetworkingKeys({
213215 const setNetworkingKeys = useCallback(async () => {
214216 // setLoading(true);
215217 try {
216− const txHash = await configureNetworkingKeys({
218+ await configureNetworkingKeys({
217219 breach,
218220 manualNetworkSeed,
219221 });
@@ -328,7 +330,7 @@ export default function UrbitOSNetworkingKeys({
328330 </>
329331 )}
330332
331− {currentL2 && !currentL2Spawn ? (
333+ {isL2 ? (
332334 <Grid.Item
333335 as={Button}
334336 full
@@ -336,7 +338,7 @@ export default function UrbitOSNetworkingKeys({
336338 center
337339 solid
338340 onClick={setNetworkingKeys}>
339− {'Sign Transaction'}
341+ {'Reset Networking Keys'}
340342 </Grid.Item>
341343 ) : (
342344 <Grid.Item