on master help sign in
Show confirmation when points migrate
commitaca52c86a327d67e625ac949d7d3b07c7012efc6 authorWill Galebach <willbach@gmail.com> dateTue 30 Nov 2021, 22:23 (5 years ago) parent48be87c
26 files changed+558−312
src/components/InlineEthereumTransaction.scss+2 src/lib/types/L2Transaction.ts+2 src/lib/types/Point.ts+24−3 src/lib/useRoller.ts+254−174 src/lib/utils/point.ts+9 src/lib/utils/roller.ts+38−9 src/store/rollerStore.ts+17−7 src/views/AcceptLockup.js+1−1 src/views/AcceptTransfer.js+14−4 src/views/Adopt.js+1−1 src/views/CancelTransfer.js+23−8 src/views/CreateGalaxy.js+1−1 src/views/Ops/Ops.tsx+17−11 src/views/Party/PartySetPoolSize.js+1−1 src/views/Point/IssueChild.tsx+51−13 src/views/Point/MigrateL2.tsx+11−3 src/views/Point/Point.scss−1 src/views/Point/Point.tsx+1−2 src/views/Points.js+25−22 src/views/Release/Transfer.js+1−1 src/views/UrbitID/Home.tsx+1−2 src/views/UrbitID/SetProxy.js+26−16 src/views/UrbitID/Transfer.js+2−2 src/views/UrbitOS/ChangeSponsor.js+4−10 src/views/UrbitOS/NetworkingKeys.js+32−19 src/views/UrbitOS/UrbitOS.scss−1
src/components/InlineEthereumTransaction.scss+2
@@ -1,6 +1,8 @@
11@import 'style/shared/variables.scss';
22
33.inline-ethereum-tx {
4+ width: 100%;
5+
46 span.eth-tx-button {
57 border-radius: $medium-border-radius;
68 font-size: 14px;
src/lib/types/L2Transaction.ts+2
@@ -1,4 +1,5 @@
11import RollerRPCAPI, { EthAddress, Proxy, Ship } from '@urbit/roller-api';
2+import WalletConnect from '@walletconnect/client';
23
34export type TransactionType =
45 | 'transferPoint'
@@ -25,6 +26,7 @@ export interface L2TransactionArgs extends SendL2Params {
2526 wallet: any;
2627 walletType: symbol;
2728 web3: any;
29+ connector: WalletConnect | null;
2830}
2931
3032export interface TransactionData {
src/lib/types/Point.ts+24−3
@@ -8,7 +8,7 @@ export interface Points {
88 [key: number]: Point;
99}
1010
11−enum PointField {
11+export enum PointField {
1212 value = 'value',
1313 layer = 'layer',
1414 active = 'active',
@@ -52,6 +52,14 @@ enum PointField {
5252 showInvites = 'showInvites',
5353}
5454
55+interface PointConstructorParams {
56+ value: number;
57+ details: L1Point;
58+ address: string;
59+ l2Quota?: number;
60+ isPlaceholder?: boolean;
61+}
62+
5563export default class Point {
5664 value: number;
5765 patp: string;
@@ -99,12 +107,19 @@ export default class Point {
99107 canVote: boolean;
100108 showInvites: boolean;
101109 isDefault: boolean;
110+ isPlaceholder: boolean;
102111
103− constructor(value: number, details: L1Point, address: string, l2Quota = 0) {
112+ constructor({
113+ value,
114+ details,
115+ address,
116+ l2Quota = 0,
117+ isPlaceholder = false,
118+ }: PointConstructorParams) {
104119 this.value = value;
105120 this.patp = value < 0 ? 'null' : ob.patp(value);
106121
107− this.layer = details.layer;
122+ this.layer = details.layer || 1;
108123 this.l2Quota = l2Quota;
109124 this.isL2Spawn = Boolean(details.isL2Spawn);
110125 this.active = details.active;
@@ -155,6 +170,7 @@ export default class Point {
155170 ((this.isGalaxy && !this.isL2Spawn) || this.isStar || this.isPlanet);
156171 this.showInvites = this.canManage || this.canSpawn;
157172 this.isDefault = value === -1;
173+ this.isPlaceholder = isPlaceholder;
158174 }
159175
160176 equals = (point: Point) => {
@@ -164,6 +180,11 @@ export default class Point {
164180 );
165181 };
166182
183+ getChangedField = (point: Point, targetField?: PointField) =>
184+ targetField
185+ ? this[targetField] !== point[targetField] && targetField
186+ : Object.values(PointField).find(field => this[field] !== point[field]);
187+
167188 getAddressProxy = (proxyType: Proxy) =>
168189 this.isOwner
169190 ? 'own'
src/lib/useRoller.ts+254−174
@@ -1,7 +1,6 @@
11import { useCallback, useEffect, useMemo, useState } from 'react';
22import * as azimuth from 'azimuth-js';
33import * as need from 'lib/need';
4−import * as ob from 'urbit-ob';
54import SecureLS from 'secure-ls';
65
76import { Invite } from 'lib/types/Invite';
@@ -22,7 +21,7 @@ import {
2221import { usePointCache } from 'store/pointCache';
2322import { getOutgoingPoints } from 'views/Points';
2423
25−import { isPlanet, toL1Details } from './utils/point';
24+import { getUpdatedPointMessage, isPlanet, toL1Details } from './utils/point';
2625import { convertToInt } from './convertToInt';
2726import { isDevelopment, isRopsten } from './flags';
2827import {
@@ -47,11 +46,12 @@ import {
4746 UnspawnedPoints,
4847} from '@urbit/roller-api';
4948
50−import Point, { Points } from './types/Point';
49+import Point, { PointField, Points } from './types/Point';
5150import { useTimerStore } from 'store/timerStore';
5251import { ReticketParams, SendL2Params } from './types/L2Transaction';
5352import { L1Point } from './types/L1Point';
5453import { ddmmmYYYY } from './utils/date';
54+import { useWalletConnect } from './useWalletConnect';
5555
5656const ONE_SECOND = 1000;
5757
@@ -75,6 +75,7 @@ export default function useRoller() {
7575 urbitWallet,
7676 walletType,
7777 }: any = useWallet();
78+ const { connector } = useWalletConnect();
7879 const { pointCursor }: any = usePointCursor();
7980 const { web3, contracts }: any = useNetwork();
8081 const allPoints: any = usePointCache();
@@ -135,12 +136,28 @@ export default function useRoller() {
135136 [authToken]
136137 );
137138
139+ const getNextQuotaTime = useCallback(
140+ (nextSlice: number) => {
141+ setTimeout(() => {
142+ api
143+ .whenNextSlice()
144+ .then(next => {
145+ setNextQuotaTime(next);
146+ getNextQuotaTime(next);
147+ })
148+ .catch(err => console.warn(err));
149+ }, nextSlice - new Date().getTime());
150+ },
151+ [api, setNextQuotaTime]
152+ );
153+
138154 const fetchConfig = useCallback(async () => {
139− Promise.all([api.getRollerConfig(), api.whenNextSlice()])
140− .then(([config, nextSlice]) => {
155+ getNextQuotaTime(1);
156+ api
157+ .getRollerConfig()
158+ .then(config => {
141159 setConfig(config);
142160 setNextBatchTime(config.nextBatch);
143− setNextQuotaTime(nextSlice);
144161 })
145162 .catch(err => {
146163 // TODO: more elegant error handling
@@ -149,7 +166,7 @@ export default function useRoller() {
149166 err
150167 );
151168 });
152− }, [api]); // eslint-disable-line react-hooks/exhaustive-deps
169+ }, [api, getNextQuotaTime, setNextBatchTime]);
153170
154171 const quotaReached = useCallback(() => {
155172 if (point.l2Quota <= 0) {
@@ -172,13 +189,19 @@ export default function useRoller() {
172189
173190 const pointNum = Number(point);
174191 try {
175− const [rawDetails, l2Quota] = await Promise.all([
176− api.getPoint(pointNum),
177− api.getRemainingQuota(pointNum),
178− ]);
192+ const rawDetails = await api.getPoint(pointNum);
193+ const l2Quota =
194+ rawDetails?.dominion === 'l2'
195+ ? await api.getRemainingQuota(pointNum)
196+ : 0;
179197 const details = toL1Details(rawDetails);
180198
181− return new Point(pointNum, details, _wallet.address, l2Quota);
199+ return new Point({
200+ value: pointNum,
201+ details,
202+ address: _wallet.address,
203+ l2Quota,
204+ });
182205 } catch (e) {
183206 console.warn(e);
184207
@@ -186,12 +209,21 @@ export default function useRoller() {
186209 try {
187210 const _contracts = need.contracts(contracts);
188211 const details = await azimuth.azimuth.getPoint(_contracts, point);
189− return new Point(pointNum, details, _wallet.address);
212+ return new Point({
213+ value: pointNum,
214+ details,
215+ address: _wallet.address,
216+ });
190217 } catch (e) {
191218 console.warn(e);
192219 // Just return a placeholder Point
193220 const details: L1Point = toL1Details();
194− return new Point(pointNum, details, _wallet.address);
221+ return new Point({
222+ value: pointNum,
223+ details,
224+ address: _wallet.address,
225+ isPlaceholder: true,
226+ });
195227 }
196228 }
197229 },
@@ -199,19 +231,25 @@ export default function useRoller() {
199231 );
200232
201233 const checkForUpdates = useCallback(
202− async (point: number, message?: string) => {
234+ async (
235+ point: number,
236+ message?: string,
237+ notify = true,
238+ field?: PointField
239+ ) => {
203240 const interval = setInterval(async () => {
204241 const updatedPoint = await initPoint(point);
242+ const changedField = !points[point]
243+ ? 'newPoint'
244+ : updatedPoint.getChangedField(points[point], field);
205245
206− console.log('CHECK UPDATES', updatedPoint.equals(points[point]), updatedPoint)
207−
208− if (!updatedPoint.equals(points[point])) {
209− console.log('UPDATED')
246+ if (!updatedPoint.isPlaceholder && changedField) {
247+ console.log('UPDATED', changedField, updatedPoint)
210248 updatePoint(updatedPoint);
211249 clearInterval(interval);
212− if (Notification?.permission === 'granted') {
250+ if (Notification?.permission === 'granted' && notify) {
213251 new Notification(
214− `${message || `${updatedPoint.patp} has been updated!`}`
252+ `${message || getUpdatedPointMessage(updatedPoint, changedField)}`
215253 );
216254 }
217255 }
@@ -220,6 +258,162 @@ export default function useRoller() {
220258 [points, initPoint, updatePoint]
221259 );
222260
261+ const spawnPoint = useCallback(
262+ async (pointToSpawn: number) => {
263+ if (quotaReached()) {
264+ return;
265+ }
266+
267+ const _point = need.point(pointCursor);
268+ const _contracts = contracts.getOrElse(null);
269+ const _web3 = web3.getOrElse(null);
270+ const _wallet = wallet.getOrElse(null);
271+ const _authToken = authToken.getOrElse(null);
272+
273+ if (!_contracts || !_web3 || !_wallet || !_authToken) {
274+ // not using need because we want a custom error
275+ throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
276+ }
277+
278+ const proxy = point.getSpawnProxy();
279+
280+ if (proxy === undefined)
281+ throw new Error("Error: Address doesn't match proxy");
282+
283+ const nonce = await api.getNonce({
284+ ship: _point,
285+ proxy,
286+ });
287+
288+ await submitL2Transaction({
289+ api,
290+ address: _wallet.address,
291+ wallet: _wallet,
292+ ship: _point,
293+ proxy,
294+ nonce,
295+ pointToSpawn,
296+ type: 'spawn',
297+ walletType,
298+ web3: _web3,
299+ connector,
300+ });
301+ },
302+ [
303+ api,
304+ authToken,
305+ connector,
306+ contracts,
307+ pointCursor,
308+ point,
309+ wallet,
310+ walletType,
311+ web3,
312+ quotaReached,
313+ ]
314+ );
315+
316+ const generateInviteInfo = async (planet: number, _authToken: string) => {
317+ const { ticket, inviteWallet } = await generateInviteWallet(
318+ planet,
319+ _authToken
320+ );
321+
322+ return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
323+ };
324+
325+ const getInvites = useCallback(async () => {
326+ try {
327+ setInvitesLoading(true);
328+ const curPoint: number = Number(need.point(pointCursor));
329+ const _authToken = authToken.getOrElse(null);
330+ const _contracts = contracts.getOrElse(null);
331+
332+ if (_authToken && _contracts) {
333+ const spawnedPoints = point.isL2Spawn
334+ ? await api.getSpawned(curPoint)
335+ : [];
336+
337+ const newPending = await api.getPendingByShip(curPoint);
338+ setPendingTransactions(newPending);
339+ const pendingSpawns = getPendingSpawns(newPending);
340+
341+ const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
342+ _contracts,
343+ curPoint
344+ );
345+
346+ const outgoingPoints = getOutgoingPoints(
347+ controlledPoints,
348+ getDetails
349+ ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
350+
351+ const invitePlanets = spawnedPoints
352+ .concat(
353+ outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
354+ )
355+ .filter((p: number) => !pendingSpawns.has(p))
356+ .sort();
357+
358+ setInvites(
359+ curPoint,
360+ invitePlanets.map(p => inviteTemplate(p))
361+ );
362+
363+ const newInvites: Invite[] = [];
364+ // Iterate over all of the stored invites, generating wallet info as necessary
365+ const storedInvites = getStoredInvites(ls);
366+
367+ for (let i = 0; i < invitePlanets.length; i++) {
368+ setInviteGeneratingNum(i + 1);
369+ const planet = invitePlanets[i];
370+ const storedInvite = storedInvites[planet];
371+ const invite =
372+ storedInvite || (await generateInviteInfo(planet, _authToken));
373+
374+ // TODO: check if the invite point's owner still matches the deterministic wallet address
375+ const planetInfo = await api.getPoint(planet);
376+ setStoredInvite(ls, invite);
377+
378+ if (
379+ invite.owner.toLowerCase() ===
380+ planetInfo.ownership?.owner?.address ||
381+ planetInfo.ownership?.transferProxy?.address !== ETH_ZERO_ADDR
382+ ) {
383+ newInvites.push(invite);
384+ updateInvite(curPoint, invite);
385+ } else {
386+ removeInvite(curPoint, invite.planet);
387+ }
388+
389+ if (storedInvite) {
390+ setStoredInvite(ls, invite);
391+ }
392+ }
393+ setInvites(curPoint, newInvites);
394+ setInvitesLoading(false);
395+ return newInvites;
396+ }
397+ } catch (error) {
398+ console.warn('ERROR GETTING INVITES', error);
399+ }
400+ }, [
401+ api,
402+ authToken,
403+ contracts,
404+ controlledPoints,
405+ getDetails,
406+ pointCursor,
407+ ls,
408+ point,
409+ removeInvite,
410+ setInvites,
411+ setInvitesLoading,
412+ setInviteGeneratingNum,
413+ setPendingTransactions,
414+ updateInvite,
415+ ]);
416+
223417 const generateInviteCodes = useCallback(
224418 async (numInvites: number) => {
225419 const _point = need.point(pointCursor);
@@ -281,6 +475,7 @@ export default function useRoller() {
281475 type: 'spawn',
282476 walletType,
283477 web3: _web3,
478+ connector,
284479 });
285480
286481 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
@@ -297,6 +492,7 @@ export default function useRoller() {
297492 type: 'configureKeys',
298493 walletType,
299494 web3: _web3,
495+ connector,
300496 });
301497
302498 await registerProxyAddress(
@@ -308,7 +504,8 @@ export default function useRoller() {
308504 1,
309505 inviteWallet.management.keys.address,
310506 walletType,
311− _web3
507+ _web3,
508+ connector
312509 );
313510
314511 await submitL2Transaction({
@@ -321,6 +518,7 @@ export default function useRoller() {
321518 address: inviteWallet.ownership.keys.address,
322519 walletType,
323520 web3: _web3,
521+ connector,
324522 });
325523
326524 invites.push({
@@ -332,10 +530,12 @@ export default function useRoller() {
332530 }
333531
334532 setStoredInvites(ls, invites);
533+ getInvites();
335534 },
336535 [
337536 api,
338537 authToken,
538+ connector,
339539 contracts,
340540 pointCursor,
341541 point,
@@ -344,6 +544,7 @@ export default function useRoller() {
344544 web3,
345545 getDetails,
346546 ls,
547+ getInvites,
347548 setInviteGeneratingNum,
348549 ]
349550 );
@@ -379,107 +580,6 @@ export default function useRoller() {
379580 }
380581 }, [api, setPendingTransactions, point]);
381582
382− const generateInviteInfo = async (planet: number, _authToken: string) => {
383− const { ticket, inviteWallet } = await generateInviteWallet(
384− planet,
385− _authToken
386− );
387−
388− return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
389− };
390−
391− const getInvites = useCallback(async () => {
392− try {
393− setInvitesLoading(true);
394− const curPoint: number = Number(need.point(pointCursor));
395− const _authToken = authToken.getOrElse(null);
396− const _contracts = contracts.getOrElse(null);
397−
398− if (_authToken && _contracts) {
399− const spawnedPoints = point.isL2Spawn
400− ? await api.getSpawned(curPoint)
401− : [];
402−
403− const newPending = await api.getPendingByShip(curPoint);
404− setPendingTransactions(newPending);
405− const pendingSpawns = getPendingSpawns(newPending);
406−
407− const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
408− _contracts,
409− curPoint
410− );
411−
412− const outgoingPoints = getOutgoingPoints(
413− controlledPoints,
414− getDetails
415− ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
416−
417− const invitePlanets = spawnedPoints
418− .concat(
419− outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
420− )
421− .filter((p: number) => !pendingSpawns.has(p))
422− .sort();
423−
424− setInvites(
425− curPoint,
426− invitePlanets.map(p => inviteTemplate(p))
427− );
428−
429− const newInvites: Invite[] = [];
430− // Iterate over all of the stored invites, generating wallet info as necessary
431− const storedInvites = getStoredInvites(ls);
432−
433− for (let i = 0; i < invitePlanets.length; i++) {
434− setInviteGeneratingNum(i + 1);
435− const planet = invitePlanets[i];
436− const storedInvite = storedInvites[planet];
437− const invite =
438− storedInvite || (await generateInviteInfo(planet, _authToken));
439−
440− // TODO: check if the invite point's owner still matches the deterministic wallet address
441− const planetInfo = await api.getPoint(planet);
442− setStoredInvite(ls, invite);
443−
444− if (
445− invite.owner.toLowerCase() ===
446− planetInfo.ownership?.owner?.address ||
447− planetInfo.ownership?.transferProxy?.address !== ETH_ZERO_ADDR
448− ) {
449− newInvites.push(invite);
450− updateInvite(curPoint, invite);
451− } else {
452− removeInvite(curPoint, invite.planet);
453− }
454−
455− if (storedInvite) {
456− setStoredInvite(ls, invite);
457− }
458− }
459− setInvites(curPoint, newInvites);
460− setInvitesLoading(false);
461− return newInvites;
462− }
463− } catch (error) {
464− console.warn('ERROR GETTING INVITES', error);
465− }
466− }, [
467− api,
468− authToken,
469− contracts,
470− controlledPoints,
471− getDetails,
472− pointCursor,
473− ls,
474− point,
475− removeInvite,
476− setInvites,
477− setInvitesLoading,
478− setInviteGeneratingNum,
479− setPendingTransactions,
480− updateInvite,
481− ]);
482−
483583 interface ConfigureKeysParams {
484584 breach: boolean;
485585 customNetworkSeed?: string;
@@ -530,13 +630,10 @@ export default function useRoller() {
530630 type: 'configureKeys',
531631 walletType,
532632 web3: _web3,
633+ connector,
533634 breach,
534635 });
535636
536− checkForUpdates(
537− point.value,
538− `${point.patp}'s network keys have been updated!`
539− );
540637 return api.getPendingTx(txHash);
541638 },
542639 [// eslint-disable-line
@@ -560,9 +657,6 @@ export default function useRoller() {
560657 fromWallet,
561658 toWallet,
562659 }: ReticketParams) => {
563− if (quotaReached()) {
564− return;
565− }
566660 const azimuthPoint = await api.getPoint(point);
567661 const proxy = 'own';
568662 const _web3 = web3.getOrElse(null);
@@ -587,6 +681,7 @@ export default function useRoller() {
587681 walletType,
588682 type: 'configureKeys',
589683 web3: _web3,
684+ connector,
590685 });
591686 nonce = nonce + 1;
592687 requests.push(configureKeysRequest);
@@ -601,7 +696,8 @@ export default function useRoller() {
601696 nonce,
602697 manager,
603698 walletType,
604− _web3
699+ _web3,
700+ connector
605701 );
606702 nonce = nonce + 1;
607703 requests.push(registerMgmtRequest);
@@ -617,7 +713,8 @@ export default function useRoller() {
617713 nonce,
618714 toWallet.spawn.keys.address,
619715 walletType,
620− _web3
716+ _web3,
717+ connector
621718 );
622719 nonce = nonce + 1;
623720 requests.push(registerSpawnRequest);
@@ -633,6 +730,7 @@ export default function useRoller() {
633730 address: to,
634731 walletType,
635732 web3: _web3,
733+ connector,
636734 });
637735 requests.push(transferTxRequest);
638736
@@ -675,16 +773,13 @@ export default function useRoller() {
675773 nonce,
676774 address,
677775 walletType,
678− _web3
776+ _web3,
777+ connector
679778 );
680779
681− checkForUpdates(
682− point.value,
683− `${point.patp}'s ${proxyType} proxy has been updated!`
684− );
685780 return api.getPendingTx(txHash);
686781 },
687− [api, point, wallet, web3, walletType, checkForUpdates, quotaReached]
782+ [api, connector, point, wallet, web3, walletType, quotaReached]
688783 );
689784
690785 const adoptPoint = useCallback(
@@ -703,13 +798,9 @@ export default function useRoller() {
703798 const nonce = await api.getNonce({ ship: point.value, proxy });
704799 const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
705800
706− checkForUpdates(
707− point.value,
708− `${ob.patp(ship)} is now a resident of ${point.patp}`
709− );
710801 return api.getPendingTx(txHash);
711802 },
712− [api, point, wallet, checkForUpdates, quotaReached]
803+ [api, point, wallet, quotaReached]
713804 );
714805
715806 const kickPoint = useCallback(
@@ -732,13 +823,9 @@ export default function useRoller() {
732823 const nonce = await api.getNonce({ ship: sponsor, proxy });
733824 const txHash = await detach(api, _wallet, sponsor, proxy, nonce, ship);
734825
735− checkForUpdates(
736− point.value,
737− `${ob.patp(ship)} is no longer a resident of ${point.patp}`
738− );
739826 return api.getPendingTx(txHash);
740827 },
741− [api, point, wallet, checkForUpdates, quotaReached]
828+ [api, point, wallet, quotaReached]
742829 );
743830
744831 const rejectPoint = useCallback(
@@ -761,13 +848,9 @@ export default function useRoller() {
761848 const nonce = await api.getNonce({ ship: sponsor, proxy });
762849 const txHash = await reject(api, _wallet, sponsor, proxy, nonce, ship);
763850
764− checkForUpdates(
765− point.value,
766− `${ob.patp(ship)} has been rejected as a resident of ${point.patp}`
767− );
768851 return api.getPendingTx(txHash);
769852 },
770− [api, point, wallet, checkForUpdates, quotaReached]
853+ [api, point, wallet, quotaReached]
771854 );
772855
773856 const transferPoint = useCallback(
@@ -802,25 +885,13 @@ export default function useRoller() {
802885 type: 'transferPoint',
803886 walletType,
804887 web3: _web3,
888+ connector,
805889 reset,
806890 });
807891
808− checkForUpdates(
809− point.value,
810− `${point.patp} has been transferred to ${address}!`
811− );
812892 return api.getPendingTx(txHash);
813893 },
814− [
815− api,
816− point,
817− pointCursor,
818− wallet,
819− web3,
820− walletType,
821− checkForUpdates,
822− quotaReached,
823− ]
894+ [api, connector, point, pointCursor, wallet, web3, walletType, quotaReached]
824895 );
825896
826897 const sendL2Transaction = useCallback(
@@ -837,18 +908,17 @@ export default function useRoller() {
837908 throw new Error('Internal Error: Missing Wallet or Point Info');
838909 }
839910
840− checkForUpdates(point.value);
841−
842911 return submitL2Transaction({
843912 ...params,
844913 api,
845914 wallet: _wallet,
846915 walletType,
847916 web3: _web3,
917+ connector,
848918 ship: point.value,
849919 });
850920 },
851− [api, point, wallet, web3, walletType, checkForUpdates, quotaReached]
921+ [api, connector, point, wallet, web3, walletType, quotaReached]
852922 );
853923
854924 const changeSponsor = useCallback(
@@ -939,28 +1009,38 @@ export default function useRoller() {
9391009 });
9401010
9411011 getPendingTransactions();
1012+
1013+ setTimeout(() => {
1014+ getPendingTransactions();
1015+
1016+ if (!point.isDefault) {
1017+ getInvites();
1018+ }
1019+ }, TEN_SECONDS); // Should this be more like a minute?
9421020 }
9431021 }, time);
9441022
9451023 return () => clearInterval(interval);
946− }, [nextBatchTime, getPendingTransactions, point]); // eslint-disable-line react-hooks/exhaustive-deps
1024+ }, [point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
9471025
9481026 return {
9491027 adoptPoint,
9501028 api,
9511029 changeSponsor,
1030+ checkForUpdates,
9521031 config,
9531032 configureNetworkingKeys,
1033+ generateInviteCodes,
9541034 getInvites,
9551035 getPoints,
9561036 getPointsDetails,
9571037 getPendingTransactions,
958− generateInviteCodes,
9591038 kickPoint,
960− rejectPoint,
961− transferPoint,
9621039 ls,
9631040 performL2Reticket,
1041+ rejectPoint,
9641042 setProxyAddress,
1043+ spawnPoint,
1044+ transferPoint,
9651045 };
9661046}
src/lib/utils/point.ts+9
@@ -1,6 +1,7 @@
11import { L2Point } from '@urbit/roller-api';
22import { azimuth } from 'azimuth-js';
33import { L1Point } from 'lib/types/L1Point';
4+import Point from 'lib/types/Point';
45
56export const isGalaxy = (point: number) =>
67 azimuth.getPointSize(point) === azimuth.PointSize.Galaxy;
@@ -32,3 +33,11 @@ export const toL1Details = (point?: L2Point): L1Point => {
3233 votingProxy: point?.ownership?.votingProxy?.address!,
3334 };
3435};
36+
37+export const getUpdatedPointMessage = (point: Point, field: string) => {
38+ if (field === 'newPoint') {
39+ return `${point.patp} has been spawned!`;
40+ }
41+
42+ return `${point.patp} has been updated!`;
43+};
src/lib/utils/roller.ts+38−9
@@ -1,5 +1,6 @@
11import { Just, Nothing } from 'folktale/maybe';
22import { randomHex } from 'web3-utils';
3+import WalletConnect from '@walletconnect/client';
34import RollerRPCAPI, {
45 Proxy,
56 Signature,
@@ -60,12 +61,21 @@ export const generateHashAndSign = async (
6061 type: string,
6162 data: any,
6263 walletType: symbol,
63− web3: any
64+ web3: any,
65+ connector: WalletConnect | null
6466) => {
6567 if (walletType === WALLET_TYPES.METAMASK) {
6668 const hash = await api.prepareForSigning(nonce, from, type, data);
6769 const sig = await web3.eth.personal.sign(hash, wallet.address, '');
6870 return sig;
71+ } else if (walletType === WALLET_TYPES.WALLET_CONNECT) {
72+ if (!connector || !connector.connected) {
73+ throw new Error('Awaiting WalletConnect connection...');
74+ }
75+
76+ const hash = await api.prepareForSigning(nonce, from, type, data);
77+ const sig = await connector.signPersonalMessage([hash, wallet.address]);
78+ return sig;
6979 } else {
7080 const hash = await api.getUnsignedTx(nonce, from, type, data);
7181 const sig = await signTransactionHash(hash, wallet.privateKey);
@@ -81,7 +91,8 @@ export const spawn = async (
8191 nonce: number,
8292 planet: number,
8393 walletType: symbol,
84− web3: any
94+ web3: any,
95+ connector: WalletConnect | null
8596) => {
8697 const from = {
8798 ship: _point, //ship that is spawning the planet
@@ -101,7 +112,8 @@ export const spawn = async (
101112 'spawn',
102113 data,
103114 walletType,
104− web3
115+ web3,
116+ connector
105117 );
106118 return api.spawn(sig, from, wallet.address, data);
107119};
@@ -115,6 +127,7 @@ export const configureKeys = async (
115127 networkSeed: any,
116128 walletType: symbol,
117129 web3: any,
130+ connector: WalletConnect | null,
118131 breach?: boolean
119132) => {
120133 const from = {
@@ -149,7 +162,8 @@ export const configureKeys = async (
149162 'configureKeys',
150163 data,
151164 walletType,
152− web3
165+ web3,
166+ connector
153167 );
154168 return api.configureKeys(sig, from, wallet.address, data);
155169};
@@ -163,6 +177,7 @@ export const transferPointRequest = async (
163177 address: string,
164178 walletType: symbol,
165179 web3: any,
180+ connector: WalletConnect | null,
166181 reset?: boolean
167182) => {
168183 const from = {
@@ -183,7 +198,8 @@ export const transferPointRequest = async (
183198 'transferPoint',
184199 data,
185200 walletType,
186− web3
201+ web3,
202+ connector
187203 );
188204
189205 return api.transferPoint(sig, from, wallet.address, data);
@@ -297,7 +313,8 @@ export const registerProxyAddress = async (
297313 nonce: number,
298314 address: string,
299315 walletType: symbol,
300− web3: any
316+ web3: any,
317+ connector: WalletConnect | null
301318) => {
302319 const from = {
303320 ship: _point,
@@ -313,7 +330,8 @@ export const registerProxyAddress = async (
313330 proxyType(proxyAddressType),
314331 data,
315332 walletType,
316− web3
333+ web3,
334+ connector
317335 );
318336
319337 return setProxy(api, proxyAddressType, sig, from, wallet.address, data);
@@ -370,7 +388,17 @@ const getDataAndMethod = (args: L2TransactionArgs) => {
370388};
371389
372390export const submitL2Transaction = async (args: L2TransactionArgs) => {
373− const { api, wallet, ship, type, proxy, nonce, walletType, web3 } = args;
391+ const {
392+ api,
393+ wallet,
394+ ship,
395+ type,
396+ proxy,
397+ nonce,
398+ walletType,
399+ web3,
400+ connector,
401+ } = args;
374402
375403 const from = { ship, proxy };
376404 const { data, method } = getDataAndMethod(args);
@@ -383,7 +411,8 @@ export const submitL2Transaction = async (args: L2TransactionArgs) => {
383411 type,
384412 data,
385413 walletType,
386− web3
414+ web3,
415+ connector
387416 );
388417
389418 return method(sig, from, wallet.address, data);
src/store/rollerStore.ts+17−7
@@ -6,7 +6,17 @@ import { Invite, Invites } from 'lib/types/Invite';
66import Point, { Points } from 'lib/types/Point';
77import { toL1Details } from 'lib/utils/point';
88
9−export const EMPTY_POINT = new Point(-1, toL1Details({}), '');
9+const getPointsAndList = (points: Points, point: Point) => ({
10+ points,
11+ pointList: Object.values(points),
12+ point: point.isDefault ? point : points[point.value],
13+});
14+
15+export const EMPTY_POINT = new Point({
16+ value: -1,
17+ details: toL1Details({}),
18+ address: '',
19+});
1020
1121export interface RollerStore {
1222 nextBatchTime: number;
@@ -62,7 +72,7 @@ export const useRollerStore = create<RollerStore>(set => ({
6272 setPoint: (point: number) =>
6373 set(state => ({ point: state.points[point] || EMPTY_POINT })),
6474 setPoints: (points: Points) =>
65− set(() => ({ points, pointList: Object.values(points) })),
75+ set(({ point }) => getPointsAndList(points, point)),
6676 setInvites: (point: number, invites: Invite[]) =>
6777 set(state => {
6878 const newInvites: Invites = {};
@@ -89,11 +99,11 @@ export const useRollerStore = create<RollerStore>(set => ({
8999 }),
90100 setInvitesLoading: (invitesLoading: boolean) =>
91101 set(() => ({ invitesLoading })),
92− updatePoint: (point: Point) =>
93− set(({ points }) => {
94− const newPoints: Points = {};
95− newPoints[point.value] = point;
96− return { points: Object.assign(points, newPoints) };
102+ updatePoint: (newPoint: Point) =>
103+ set(({ point, points }) => {
104+ const newPoints: Points = Object.assign({}, points);
105+ newPoints[newPoint.value] = newPoint;
106+ return getPointsAndList(newPoints, point);
97107 }),
98108 setModalText: (modalText?: string) => set(() => ({ modalText })),
99109}));
src/views/AcceptLockup.js+1−1
@@ -148,7 +148,7 @@ export default function AcceptLockup() {
148148 full
149149 as={InlineEthereumTransaction}
150150 {...bind}
151− onReturn={() => pop()}
151+ onReturn={pop}
152152 />
153153 </Grid>
154154 </View>
src/views/AcceptTransfer.js+14−4
@@ -1,4 +1,4 @@
1−import React, { useCallback, useMemo, useState } from 'react';
1+import React, { useCallback, useEffect, useMemo, useState } from 'react';
22import cn from 'classnames';
33import { Nothing } from 'folktale/maybe';
44import { Grid, Text, CheckboxInput, Button } from 'indigo-react';
@@ -60,7 +60,7 @@ export default function AcceptTransfer() {
6060 const { pop } = useLocalRouter();
6161 const { setPointCursor } = usePointCursor();
6262 const { point } = useRollerStore();
63− const { transferPoint } = useRoller();
63+ const { transferPoint, checkForUpdates } = useRoller();
6464
6565 const { wallet } = useWallet();
6666 const _address = need.addressFromWallet(wallet);
@@ -71,6 +71,15 @@ export default function AcceptTransfer() {
7171
7272 const { completed, bind, inputsLocked, construct } = useAcceptTransfer();
7373
74+ useEffect(() => {
75+ if (completed) {
76+ checkForUpdates(
77+ point.value,
78+ `${point.patp} has been transferred to you!`
79+ );
80+ }
81+ }, [completed]); // eslint-disable-line react-hooks/exhaustive-deps
82+
7483 const initialValues = useMemo(() => ({ noReset: false }), []);
7584
7685 const validate = useMemo(
@@ -92,8 +101,9 @@ export default function AcceptTransfer() {
92101
93102 const acceptTransfer = useCallback(async () => {
94103 await transferPoint(_address, reset);
104+ checkForUpdates(point.value, `${point.patp} has been transferred to you!`);
95105 pop();
96− }, [transferPoint, pop, _address, reset]);
106+ }, [transferPoint, checkForUpdates, point, pop, _address, reset]);
97107
98108 return (
99109 <View
@@ -151,7 +161,7 @@ export default function AcceptTransfer() {
151161 full
152162 as={InlineEthereumTransaction}
153163 {...bind}
154− onReturn={() => pop()}
164+ onReturn={pop}
155165 />
156166 )}
157167 </Grid>
src/views/Adopt.js+1−1
@@ -80,7 +80,7 @@ export default function Adopt() {
8080 full
8181 {...bind}
8282 as={InlineEthereumTransaction}
83− onReturn={() => pop()}
83+ onReturn={pop}
8484 />
8585 </Grid>
8686 </View>
src/views/CancelTransfer.js+23−8
@@ -1,4 +1,4 @@
1−import React, { useCallback } from 'react';
1+import React, { useCallback, useEffect } from 'react';
22import cn from 'classnames';
33import { Grid, Text, Button } from 'indigo-react';
44import * as azimuth from 'azimuth-js';
@@ -56,7 +56,7 @@ function AdminCancelTransfer() {
5656 const { pop } = useLocalRouter();
5757 const { getDetails } = usePointCache();
5858 const { point } = useRollerStore();
59− const { setProxyAddress } = useRoller();
59+ const { setProxyAddress, checkForUpdates } = useRoller();
6060 const { pointCursor } = usePointCursor();
6161 const _point = need.point(pointCursor);
6262
@@ -65,10 +65,20 @@ function AdminCancelTransfer() {
6565
6666 const { completed, bind } = useCancelTransfer();
6767
68+ useEffect(() => {
69+ if (completed) {
70+ checkForUpdates(point.value, `${point.patp} transfer cancelled`);
71+ }
72+ }, [completed]); // eslint-disable-line react-hooks/exhaustive-deps
73+
6874 const cancelTransfer = useCallback(async () => {
6975 await setProxyAddress('transfer', ETH_ZERO_ADDR);
76+ checkForUpdates(point.value, `${point.patp} transfer cancelled`);
7077 pop();
71− }, [setProxyAddress, pop]);
78+ }, [setProxyAddress, pop, checkForUpdates, point]);
79+
80+ const formattedName = <span className="mono">{name}</span>;
81+ const formattedProxy = <span className="mono">{_details.transferProxy}</span>;
7282
7383 return (
7484 <Window>
@@ -81,12 +91,17 @@ function AdminCancelTransfer() {
8191 <Grid.Item
8292 full
8393 as={Text}
84− className={cn('f5 wrap', {
94+ className={cn('f5 wrap mb5', {
8595 green3: completed,
8696 })}>
87− {completed
88− ? `The outgoing transfer of ${name} has been cancelled.`
89− : `Cancel the outgoing transfer of ${name} to ${_details.transferProxy}.`}
97+ {completed ? (
98+ <>The outgoing transfer of {formattedName} has been cancelled.</>
99+ ) : (
100+ <>
101+ Cancel the outgoing transfer of {formattedName} to{' '}
102+ {formattedProxy}.
103+ </>
104+ )}
90105 </Grid.Item>
91106
92107 {point.isL2 ? (
@@ -104,7 +119,7 @@ function AdminCancelTransfer() {
104119 full
105120 as={InlineEthereumTransaction}
106121 {...bind}
107− onReturn={() => pop()}
122+ onReturn={pop}
108123 />
109124 )}
110125 </BodyPane>
src/views/CreateGalaxy.js+1−1
@@ -155,7 +155,7 @@ export default function CreateGalaxy() {
155155 full
156156 as={InlineEthereumTransaction}
157157 {...bind}
158− onReturn={() => pop()}
158+ onReturn={pop}
159159 />
160160 </>
161161 )}
src/views/Ops/Ops.tsx+17−11
@@ -1,20 +1,24 @@
1+import { useCallback } from 'react';
12import * as need from 'lib/need';
23import { Box, Row, Button, Icon } from '@tlon/indigo-react';
34
5+import { useRollerStore } from 'store/rollerStore';
6+import { usePointCursor } from 'store/pointCursor';
7+
8+import { useLocalRouter } from 'lib/LocalRouter';
9+import { isGalaxy } from 'lib/utils/point';
10+
411import Window from 'components/L2/Window/Window';
512import HeaderPane from 'components/L2/Window/HeaderPane';
613import BodyPane from 'components/L2/Window/BodyPane';
7−import { useCallback } from 'react';
8−import { useLocalRouter } from 'lib/LocalRouter';
9−import { isGalaxy } from 'lib/utils/point';
10−import { usePointCursor } from 'store/pointCursor';
1114import View from 'components/View';
1215import L2BackHeader from 'components/L2/Headers/L2BackHeader';
1316
1417export const Ops = () => {
1518 const { pop, push, names }: any = useLocalRouter();
1619 const { pointCursor }: any = usePointCursor();
17− const point = need.point(pointCursor);
20+ const { point } = useRollerStore();
21+ const _point = need.point(pointCursor);
1822
1923 const goSenate = useCallback(() => push(names.SENATE), [push, names]);
2024 const goResidents = useCallback(() => push(names.RESIDENTS), [push, names]);
@@ -34,11 +38,13 @@ export const Ops = () => {
3438 <Window className="id-ops">
3539 <HeaderPane>
3640 <Row className="header-row">
37− <h5>{isGalaxy(point) ? 'Galaxy' : 'Star'} Ops</h5>
38− <Button onClick={goIssuePoint} className="header-button">
39− <Icon icon="Node" /> &nbsp;Spawn&nbsp;
40− {isGalaxy(point) ? 'Stars' : 'Planets'}
41− </Button>
41+ <h5>{isGalaxy(_point) ? 'Galaxy' : 'Star'} Ops</h5>
42+ {point.networkKeysSet && (
43+ <Button onClick={goIssuePoint} className="header-button">
44+ <Icon icon="Node" /> &nbsp;Spawn&nbsp;
45+ {isGalaxy(_point) ? 'Stars' : 'Planets'}
46+ </Button>
47+ )}
4248 </Row>
4349 </HeaderPane>
4450 <BodyPane>
@@ -62,7 +68,7 @@ export const Ops = () => {
6268 View
6369 </Button>
6470 </Row>
65− {isGalaxy(point) && (
71+ {point.isGalaxy && (
6672 <Row className="between-row management">
6773 <Box>
6874 <Box>Proposals</Box>
src/views/Party/PartySetPoolSize.js+1−1
@@ -174,7 +174,7 @@ export default function PartySetPoolSize() {
174174 full
175175 as={InlineEthereumTransaction}
176176 {...bind}
177− onReturn={() => pop()}
177+ onReturn={pop}
178178 />
179179 </>
180180 )}
src/views/Point/IssueChild.tsx+51−13
@@ -1,6 +1,6 @@
11import React, { useCallback, useEffect, useMemo, useState } from 'react';
22import cn from 'classnames';
3−import { Grid } from 'indigo-react';
3+import { Button, Grid } from 'indigo-react';
44import Window from 'components/L2/Window/Window';
55import HeaderPane from 'components/L2/Window/HeaderPane';
66import BodyPane from 'components/L2/Window/BodyPane';
@@ -11,6 +11,7 @@ import ob from 'urbit-ob';
1111import { useNetwork } from 'store/network';
1212import { usePointCache } from 'store/pointCache';
1313import { usePointCursor } from 'store/pointCursor';
14+import { useRollerStore } from 'store/rollerStore';
1415
1516import * as need from 'lib/need';
1617import useEthereumTransaction from 'lib/useEthereumTransaction';
@@ -65,14 +66,17 @@ export function useIssueChild() {
6566
6667export default function IssueChild() {
6768 const { pop }: any = useLocalRouter();
68− const { api } = useRoller();
69+ const { api, checkForUpdates, spawnPoint } = useRoller();
6970 const { pointCursor }: any = usePointCursor();
71+ const { point } = useRollerStore();
7072
7173 const _point = convertToInt(need.point(pointCursor), 10);
7274 const [availablePoints, setAvailablePoints] = useState<Set<number> | null>(
7375 null
7476 );
7577 const [candidates, setCandidates] = useState<string[]>([]);
78+ const [pointToSpawn, setPointToSpawn] = useState<number | null>();
79+ const [l2SpawnSent, setL2SpawnSent] = useState(false);
7680
7781 const fetchAvailablePoints = useCallback(async () => {
7882 const available = await api.getUnspawned(_point);
@@ -107,6 +111,15 @@ export default function IssueChild() {
107111 shuffle();
108112 }, [fetchAvailablePoints, shuffle]);
109113
114+ const spawnNewPoint = useCallback(async () => {
115+ if (pointToSpawn) {
116+ await spawnPoint(pointToSpawn);
117+ checkForUpdates(pointToSpawn);
118+ setPointToSpawn(null);
119+ setL2SpawnSent(true);
120+ }
121+ }, [pointToSpawn, spawnPoint, checkForUpdates]);
122+
110123 const {
111124 isDefaultState,
112125 construct,
@@ -116,6 +129,12 @@ export default function IssueChild() {
116129 bind,
117130 } = useIssueChild();
118131
132+ useEffect(() => {
133+ if (completed && pointToSpawn) {
134+ checkForUpdates(pointToSpawn);
135+ }
136+ }, [completed]); // eslint-disable-line react-hooks/exhaustive-deps
137+
119138 const validateFormAsync = useCallback(
120139 async values => {
121140 const point = patp2dec(values.point);
@@ -154,7 +173,9 @@ export default function IssueChild() {
154173 const onValues = useCallback(
155174 ({ valid, values }) => {
156175 if (valid) {
157− construct(patp2dec(values.point), values.owner);
176+ const pointDecimal = patp2dec(values.point);
177+ construct(pointDecimal, values.owner);
178+ setPointToSpawn(pointDecimal);
158179 } else {
159180 unconstruct();
160181 }
@@ -183,14 +204,16 @@ export default function IssueChild() {
183204 onValues={onValues}>
184205 {({ handleSubmit, values }: any) => (
185206 <>
186− {completed && (
207+ {(completed || l2SpawnSent) && (
187208 <Grid.Item
188209 full
189− as={Text}
190− className={cn('f5 wrap', {
210+ as={'div'}
211+ style={{ fontSize: 14 }}
212+ className={cn('f5 wrap mb5', {
191213 green3: completed,
192214 })}>
193− {values.point} has been spawned and can be claimed by{' '}
215+ <span className="mono">{values.point}</span> has been
216+ spawned and can be claimed by{' '}
194217 <CopiableAddress>{values.owner}</CopiableAddress>.
195218 </Grid.Item>
196219 )}
@@ -246,12 +269,27 @@ export default function IssueChild() {
246269
247270 <Grid.Item full as={FormError} />
248271
249− <Grid.Item
250− full
251− as={InlineEthereumTransaction}
252− {...bind}
253− onReturn={() => pop()}
254− />
272+ {point.isL2Spawn ? (
273+ <Grid.Item
274+ as={Button}
275+ full
276+ className="mt4"
277+ center
278+ solid
279+ disabled={!pointToSpawn}
280+ onClick={spawnNewPoint}>
281+ {pointToSpawn
282+ ? `Spawn ${ob.patp(pointToSpawn)}`
283+ : `Please enter a ${point.isStar ? 'planet' : 'star'}`}
284+ </Grid.Item>
285+ ) : (
286+ <Grid.Item
287+ full
288+ as={InlineEthereumTransaction}
289+ {...bind}
290+ onReturn={pop}
291+ />
292+ )}
255293 </>
256294 )}
257295 </BridgeForm>
src/views/Point/MigrateL2.tsx+11−3
@@ -36,7 +36,8 @@ import { isPlanet } from 'lib/utils/point';
3636
3737import './MigrateL2.scss';
3838import { useRollerStore } from 'store/rollerStore';
39−import Point from 'lib/types/Point';
39+import Point, { PointField } from 'lib/types/Point';
40+import useRoller from 'lib/useRoller';
4041
4142const PointEntry = ({
4243 point,
@@ -99,6 +100,7 @@ const useMigrate = () => {
99100export default function MigrateL2() {
100101 const { pop }: any = useLocalRouter();
101102 const { point, pointList } = useRollerStore();
103+ const { checkForUpdates } = useRoller();
102104 const l1Points = pointList.filter(({ canMigrate }) => canMigrate);
103105
104106 const [proceed, setProceed] = useState(false);
@@ -126,9 +128,15 @@ export default function MigrateL2() {
126128
127129 useEffect(() => {
128130 if (completed) {
131+ checkForUpdates(
132+ selectedPoint.value,
133+ `${selectedPoint.patp} has been migrated to Layer 2!`,
134+ true,
135+ PointField.layer
136+ );
129137 pop();
130138 }
131− }, [completed, pop]);
139+ }, [completed, pop]); // eslint-disable-line react-hooks/exhaustive-deps
132140
133141 const goBack = useCallback(() => {
134142 if (proceed) {
@@ -241,7 +249,7 @@ export default function MigrateL2() {
241249 full
242250 as={InlineEthereumTransaction}
243251 label={`${
244− transfer ? 'Transfer point' : 'Set spawn proxy'
252+ transfer ? 'Transfer ship' : 'Set spawn proxy'
245253 } to Layer 2`}
246254 {...bind}
247255 onReturn={pop}
src/views/Point/Point.scss−1
@@ -51,7 +51,6 @@
5151}
5252
5353.network-keys-modal {
54− padding: 16px;
5554 background-color: white;
5655 border-radius: $medium-border-radius;
5756 line-height: 24px;
src/views/Point/Point.tsx+1−2
@@ -18,7 +18,6 @@ import Card from 'components/L2/Card';
1818
1919import * as need from 'lib/need';
2020import useInvites from 'lib/useInvites';
21−import useCurrentPermissions from 'lib/useCurrentPermissions';
2221import { useLocalRouter } from 'lib/LocalRouter';
2322import useRoller from 'lib/useRoller';
2423import useSeenMissingKeys from 'lib/useSeenMissingKeys';
@@ -79,7 +78,7 @@ export default function Point() {
7978 loadL2Info();
8079 }, [point]); // eslint-disable-line react-hooks/exhaustive-deps
8180
82− const { isParent, canManage, isSpawnProxy } = useCurrentPermissions();
81+ const { isParent, canManage, isSpawnProxy } = point;
8382
8483 // L1 invites
8584 const l1Invites = useInvites(point.value);
src/views/Points.js+25−22
@@ -9,6 +9,7 @@ import { usePointCache } from 'store/pointCache';
99import { usePointCursor } from 'store/pointCursor';
1010import { useStarReleaseCache } from 'store/starRelease';
1111import { useNetwork } from 'store/network';
12+import { useRollerStore } from 'store/rollerStore';
1213
1314import * as need from 'lib/need';
1415import { isZeroAddress, abbreviateAddress } from 'lib/utils/address';
@@ -138,11 +139,21 @@ export default function Points() {
138139 rejectedPoints,
139140 addRejectedPoint,
140141 ] = useRejectedIncomingPointTransfers();
142+ const { pointList } = useRollerStore();
141143 const { syncStarReleaseDetails, starReleaseDetails } = useStarReleaseCache();
142144
143145 const outgoingPoints = useMemo(
144− () => getOutgoingPoints(controlledPoints, getDetails),
145− [getDetails, controlledPoints]
146+ () => pointList.filter(({ isTransferProxySet }) => isTransferProxySet),
147+ [pointList]
148+ );
149+
150+ const incomingPoints = useMemo(
151+ () =>
152+ pointList.filter(
153+ ({ isTransferProxy, value }) =>
154+ isTransferProxy && !rejectedPoints.includes(value)
155+ ),
156+ [pointList, rejectedPoints]
146157 );
147158
148159 const _contracts = need.contracts(contracts);
@@ -178,10 +189,7 @@ export default function Points() {
178189 // if there are any pending transfers, incoming or outgoing, stay on this
179190 // page, because those can only be completed/cancelled here.
180191 useEffect(() => {
181− if (
182− Nothing.hasInstance(outgoingPoints) ||
183− Nothing.hasInstance(starReleaseDetails)
184− ) {
192+ if (Nothing.hasInstance(starReleaseDetails)) {
185193 return;
186194 }
187195 controlledPoints.matchWith({
@@ -196,12 +204,9 @@ export default function Points() {
196204 ...c.value.managingPoints,
197205 ...c.value.spawningPoints,
198206 ];
199− const incoming = c.value.incomingPoints.filter(
200− p => !rejectedPoints.includes(p)
201− );
202207 if (
203208 all.length === 1 &&
204− incoming.length === 0 &&
209+ incomingPoints.length === 0 &&
205210 outgoingPoints.length === 0 &&
206211 (starReleaseDetails.value === null ||
207212 starReleaseDetails.value.total === 0)
@@ -217,6 +222,7 @@ export default function Points() {
217222 controlledPoints,
218223 rejectedPoints,
219224 outgoingPoints,
225+ incomingPoints,
220226 setPointCursor,
221227 popAndPush,
222228 names,
@@ -228,18 +234,15 @@ export default function Points() {
228234 const loading = Nothing.hasInstance(controlledPoints);
229235
230236 const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
231− const incomingPoints = maybeGetResult(
232− controlledPoints,
233− 'incomingPoints',
234− []
235− ).filter(point => !rejectedPoints.includes(point));
236237 const managingPoints = maybeGetResult(controlledPoints, 'managingPoints', []);
237238 const votingPoints = maybeGetResult(controlledPoints, 'votingPoints', []);
238239 const spawningPoints = maybeGetResult(controlledPoints, 'spawningPoints', []);
239240 const lockedPoints = maybeLockedPoints.getOrElse([]);
240241
241242 const allPoints = [
242− ...ownedPoints.filter(p => !outgoingPoints.includes(p)),
243+ ...ownedPoints.filter(
244+ p => !outgoingPoints.find(({ value }) => value === p)
245+ ),
243246 ...managingPoints,
244247 ...votingPoints,
245248 ...spawningPoints,
@@ -338,15 +341,15 @@ export default function Points() {
338341 {abbreviateAddress(address)}
339342 </CopiableAddress>
340343 </NavHeader>
341− {incomingPoints.map(point => (
344+ {incomingPoints.map(({ value }) => (
342345 <IncomingPoint
343− key={point}
344− point={point}
346+ key={value}
347+ point={value}
345348 accept={() => {
346− setPointCursor(Just(point));
349+ setPointCursor(Just(value));
347350 push(names.ACCEPT_TRANSFER);
348351 }}
349− reject={() => addRejectedPoint(point)}
352+ reject={() => addRejectedPoint(value)}
350353 />
351354 ))}
352355 <Grid>
@@ -369,7 +372,7 @@ export default function Points() {
369372 <Grid.Item
370373 full
371374 as={PointList}
372− points={outgoingPoints}
375+ points={outgoingPoints.map(({ value }) => value)}
373376 actions={(point, i) => (
374377 <ActionButtons
375378 actions={[
src/views/Release/Transfer.js+1−1
@@ -159,7 +159,7 @@ export default function Transfer({ className, goActive }) {
159159 as={InlineEthereumTransaction}
160160 {...bind}
161161 label="Approve Transfer"
162− onReturn={() => pop()}
162+ onReturn={pop}
163163 />
164164 </>
165165 )}
src/views/UrbitID/Home.tsx+1−2
@@ -4,7 +4,6 @@ import { Box, Button, Icon, Row } from '@tlon/indigo-react';
44
55import { useLocalRouter } from 'lib/LocalRouter';
66import { PROXY_TYPE } from 'lib/proxy';
7−import useCurrentPermissions from 'lib/useCurrentPermissions';
87import { convertToInt } from 'lib/convertToInt';
98import { ETH_ZERO_ADDR, MASTER_TICKET_TOOLTIP } from 'lib/constants';
109import { abbreviateAddress } from 'lib/utils/address';
@@ -45,7 +44,7 @@ export default function UrbitIDHome() {
4544 const goResetKeys = useCallback(() => push(names.RESET_KEYS), [push, names]);
4645
4746 const isMasterTicket = Just.hasInstance(urbitWallet);
48− const { isOwner, canManage } = useCurrentPermissions();
47+ const { isOwner, canManage } = point;
4948 const networkRevision = convertToInt(point.keyRevisionNumber, 10);
5049
5150 const goSetProxy = useCallback(
src/views/UrbitID/SetProxy.js+26−16
@@ -1,4 +1,4 @@
1−import React, { useCallback, useMemo, useState } from 'react';
1+import React, { useCallback, useEffect, useMemo, useState } from 'react';
22import cn from 'classnames';
33import { Grid, Text, Flex, Button } from 'indigo-react';
44import * as azimuth from 'azimuth-js';
@@ -124,20 +124,21 @@ function useSetProxy(proxyType) {
124124
125125export default function SetProxy() {
126126 const { data, pop } = useLocalRouter();
127− const { getDetails } = usePointCache();
128− const { pointCursor } = usePointCursor();
129127 const { contracts } = useNetwork();
128+ const { point, setLoading } = useRollerStore();
130129 const {
131− point: { isL2, isL2Spawn },
132− } = useRollerStore();
133− const { setProxyAddress, getPendingTransactions } = useRoller();
130+ setProxyAddress,
131+ getPendingTransactions,
132+ checkForUpdates,
133+ } = useRoller();
134134 const [newAddress, setNewAddress] = useState('');
135135
136− const _point = need.point(pointCursor);
137− const _details = need.details(getDetails(_point));
138136 const _contracts = need.contracts(contracts);
139137
140− const properProxyType = capitalize(proxyTypeToHuman(data.proxyType));
138+ const humanProxyType = proxyTypeToHuman(data.proxyType);
139+ const properProxyType = capitalize(humanProxyType);
140+
141+ const { isL2, isL2Spawn } = point;
141142
142143 const isL2Tx =
143144 (data.proxyType === 'MANAGEMENT' && isL2) ||
@@ -154,27 +155,36 @@ export default function SetProxy() {
154155 } = useSetProxy(data.proxyType);
155156
156157 const setProxy = useCallback(async () => {
157− // setLoading(true);
158− console.log(newAddress);
158+ setLoading(true);
159159 if (newAddress === '') return;
160160 try {
161161 await setProxyAddress(getL2ProxyName(data.proxyType), newAddress);
162− getPendingTransactions(_point);
162+ getPendingTransactions(point.value);
163163 pop();
164164 } catch (error) {
165165 // setError(error);
166166 } finally {
167− // setLoading(false);
167+ setLoading(false);
168168 }
169169 }, [
170170 newAddress,
171171 getPendingTransactions,
172− _point,
172+ point,
173173 pop,
174174 data.proxyType,
175175 setProxyAddress,
176+ setLoading,
176177 ]);
177178
179+ useEffect(() => {
180+ if (completed) {
181+ checkForUpdates(
182+ point.value,
183+ `${point.patp}'s ${humanProxyType} proxy has been set!`
184+ );
185+ }
186+ }, [completed]); // eslint-disable-line react-hooks/exhaustive-deps
187+
178188 const validateForm = useCallback((values, errors) => {
179189 if (!values.unset && errors.address) {
180190 return errors;
@@ -213,7 +223,7 @@ export default function SetProxy() {
213223
214224 const initialValues = useMemo(() => ({ unset: false }), []);
215225
216− const proxyAddress = proxyFromDetails(_details, _contracts, data.proxyType);
226+ const proxyAddress = proxyFromDetails(point, _contracts, data.proxyType);
217227 const isProxySet = !isZeroAddress(proxyAddress);
218228
219229 const header = useMemo(() => {
@@ -296,7 +306,7 @@ export default function SetProxy() {
296306 full
297307 as={InlineEthereumTransaction}
298308 {...bind}
299− onReturn={() => pop()}
309+ onReturn={pop}
300310 />
301311 )}
302312 </>
src/views/UrbitID/Transfer.js+2−2
@@ -133,7 +133,7 @@ export default function AdminTransfer() {
133133 {completed && (
134134 <>
135135 <Grid.Item full as={Text} className="wrap">
136− Started transferring <span className="mono">{name}</span>
136+ Started transferring <span className="mono">{name}</span>{' '}
137137 to{' '}
138138 </Grid.Item>
139139 <Grid.Item full as={Text}>
@@ -175,7 +175,7 @@ export default function AdminTransfer() {
175175 full
176176 as={InlineEthereumTransaction}
177177 {...bind}
178− onReturn={() => pop()}
178+ onReturn={pop}
179179 />
180180 )}
181181 </>
src/views/UrbitOS/ChangeSponsor.js+4−10
@@ -216,19 +216,13 @@ function CurrentEscape({ onDone }) {
216216}
217217
218218export default function UrbitOSChangeSponsor() {
219− const { pointCursor } = usePointCursor();
220−
221− const point = need.point(pointCursor);
222−
223− const { getDetails } = usePointCache();
224−
225− const details = need.details(getDetails(point));
219+ const { point } = useRollerStore();
226220
227− const [requested, setRequested] = useState(details.escapeRequested);
221+ const [requested, setRequested] = useState(point.escapeRequested);
228222
229223 const onDone = useCallback(() => {
230− setRequested(details.escapeRequested);
231− }, [details, setRequested]);
224+ setRequested(point.escapeRequested);
225+ }, [point, setRequested]);
232226
233227 return requested ? (
234228 <CurrentEscape onDone={onDone} />
src/views/UrbitOS/NetworkingKeys.js+32−19
@@ -1,4 +1,10 @@
1−import React, { useCallback, useMemo, useRef, useState } from 'react';
1+import React, {
2+ useCallback,
3+ useEffect,
4+ useMemo,
5+ useRef,
6+ useState,
7+} from 'react';
28import { Just } from 'folktale/maybe';
39import { Grid, ToggleInput, CheckboxInput, Button } from 'indigo-react';
410import * as azimuth from 'azimuth-js';
@@ -144,23 +150,15 @@ export default function UrbitOSNetworkingKeys({
144150 setManualNetworkSeed,
145151}) {
146152 const { pop } = useLocalRouter();
147− const { pointCursor } = usePointCursor();
148− const { getDetails } = usePointCache();
153+ const { point, setLoading } = useRollerStore();
149154 const {
150− point: { isL2 },
151− } = useRollerStore();
152− const { configureNetworkingKeys, getPendingTransactions } = useRoller();
155+ configureNetworkingKeys,
156+ getPendingTransactions,
157+ checkForUpdates,
158+ } = useRoller();
153159 const [breach, setBreach] = useState(false);
154160
155− const point = need.point(pointCursor);
156− const details = getDetails(point);
157−
158− const hasKeys = details.matchWith({
159− Nothing: () => false,
160− Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
161− // we actually don't mind the default NaN behavior of parseInt here,
162− // since NaN > 0 === false and that's a reasonable result
163− });
161+ const hasKeys = point.networkKeysSet;
164162
165163 const {
166164 construct,
@@ -171,6 +169,15 @@ export default function UrbitOSNetworkingKeys({
171169 keyfileBind,
172170 } = useSetKeys(manualNetworkSeed, setManualNetworkSeed);
173171
172+ useEffect(() => {
173+ if (completed) {
174+ checkForUpdates(
175+ point.value,
176+ `${point.patp}'s Networking Keys have been set!`
177+ );
178+ }
179+ }, [completed]); // eslint-disable-line react-hooks/exhaustive-deps
180+
174181 const validateForm = useCallback((values, errors) => {
175182 if (values.useNetworkSeed && errors.networkSeed) {
176183 return errors;
@@ -213,7 +220,7 @@ export default function UrbitOSNetworkingKeys({
213220 );
214221
215222 const setNetworkingKeys = useCallback(async () => {
216− // setLoading(true);
223+ setLoading(true);
217224 try {
218225 await configureNetworkingKeys({
219226 breach,
@@ -228,11 +235,15 @@ export default function UrbitOSNetworkingKeys({
228235 // set since they are in pending in the Roller...
229236 // we could inspect if there's a changeKeys in the local list of pending txs,...
230237 //
238+ checkForUpdates(
239+ point.value,
240+ `${point.patp}'s Networking Keys have been set!`
241+ );
231242 pop();
232243 } catch (error) {
233244 // setError(error);
234245 } finally {
235− // setLoading(false);
246+ setLoading(false);
236247 }
237248 }, [
238249 breach,
@@ -241,6 +252,8 @@ export default function UrbitOSNetworkingKeys({
241252 point,
242253 pop,
243254 configureNetworkingKeys,
255+ setLoading,
256+ checkForUpdates,
244257 ]);
245258
246259 const initialValues = useMemo(
@@ -330,7 +343,7 @@ export default function UrbitOSNetworkingKeys({
330343 </>
331344 )}
332345
333− {isL2 ? (
346+ {point.isL2 ? (
334347 <Grid.Item
335348 as={Button}
336349 full
@@ -346,7 +359,7 @@ export default function UrbitOSNetworkingKeys({
346359 as={InlineEthereumTransaction}
347360 {...bind}
348361 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
349− onReturn={() => pop()}
362+ onReturn={pop}
350363 />
351364 )}
352365 </Box>
src/views/UrbitOS/UrbitOS.scss−1
@@ -9,7 +9,6 @@
99.show-keys-modal-content {
1010 width: 375px;
1111 background-color: white;
12− padding: 1rem 1.5rem;
1312 border-radius: $large-border-radius;
1413 position: relative;
1514}