on master help sign in
Show confirmation when points migrate
aca52c8Will Galebach
src/components/InlineEthereumTransaction.scss+2
@@ -1,6 +1,8 @@
1@import 'style/shared/variables.scss';
1@import 'style/shared/variables.scss';
2
2
3.inline-ethereum-tx {
3.inline-ethereum-tx {
4+ width: 100%;
5+
4 span.eth-tx-button {
6 span.eth-tx-button {
5 border-radius: $medium-border-radius;
7 border-radius: $medium-border-radius;
6 font-size: 14px;
8 font-size: 14px;
src/lib/types/L2Transaction.ts+2
@@ -1,4 +1,5 @@
1import RollerRPCAPI, { EthAddress, Proxy, Ship } from '@urbit/roller-api';
1import RollerRPCAPI, { EthAddress, Proxy, Ship } from '@urbit/roller-api';
2+import WalletConnect from '@walletconnect/client';
2
3
3export type TransactionType =
4export type TransactionType =
4 | 'transferPoint'
5 | 'transferPoint'
@@ -25,6 +26,7 @@ export interface L2TransactionArgs extends SendL2Params {
25 wallet: any;
26 wallet: any;
26 walletType: symbol;
27 walletType: symbol;
27 web3: any;
28 web3: any;
29+ connector: WalletConnect | null;
28}
30}
29
31
30export interface TransactionData {
32export interface TransactionData {
src/lib/types/Point.ts+24−3
@@ -8,7 +8,7 @@ export interface Points {
8 [key: number]: Point;
8 [key: number]: Point;
9}
9}
10
10
11−enum PointField {
11+export enum PointField {
12 value = 'value',
12 value = 'value',
13 layer = 'layer',
13 layer = 'layer',
14 active = 'active',
14 active = 'active',
@@ -52,6 +52,14 @@ enum PointField {
52 showInvites = 'showInvites',
52 showInvites = 'showInvites',
53}
53}
54
54
55+interface PointConstructorParams {
56+ value: number;
57+ details: L1Point;
58+ address: string;
59+ l2Quota?: number;
60+ isPlaceholder?: boolean;
61+}
62+
55export default class Point {
63export default class Point {
56 value: number;
64 value: number;
57 patp: string;
65 patp: string;
@@ -99,12 +107,19 @@ export default class Point {
99 canVote: boolean;
107 canVote: boolean;
100 showInvites: boolean;
108 showInvites: boolean;
101 isDefault: boolean;
109 isDefault: boolean;
110+ isPlaceholder: boolean;
102
111
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) {
104 this.value = value;
119 this.value = value;
105 this.patp = value < 0 ? 'null' : ob.patp(value);
120 this.patp = value < 0 ? 'null' : ob.patp(value);
106
121
107− this.layer = details.layer;
122+ this.layer = details.layer || 1;
108 this.l2Quota = l2Quota;
123 this.l2Quota = l2Quota;
109 this.isL2Spawn = Boolean(details.isL2Spawn);
124 this.isL2Spawn = Boolean(details.isL2Spawn);
110 this.active = details.active;
125 this.active = details.active;
@@ -155,6 +170,7 @@ export default class Point {
155 ((this.isGalaxy && !this.isL2Spawn) || this.isStar || this.isPlanet);
170 ((this.isGalaxy && !this.isL2Spawn) || this.isStar || this.isPlanet);
156 this.showInvites = this.canManage || this.canSpawn;
171 this.showInvites = this.canManage || this.canSpawn;
157 this.isDefault = value === -1;
172 this.isDefault = value === -1;
173+ this.isPlaceholder = isPlaceholder;
158 }
174 }
159
175
160 equals = (point: Point) => {
176 equals = (point: Point) => {
@@ -164,6 +180,11 @@ export default class Point {
164 );
180 );
165 };
181 };
166
182
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+
167 getAddressProxy = (proxyType: Proxy) =>
188 getAddressProxy = (proxyType: Proxy) =>
168 this.isOwner
189 this.isOwner
169 ? 'own'
190 ? 'own'
src/lib/useRoller.ts+254−174
@@ -1,7 +1,6 @@
1import { useCallback, useEffect, useMemo, useState } from 'react';
1import { useCallback, useEffect, useMemo, useState } from 'react';
2import * as azimuth from 'azimuth-js';
2import * as azimuth from 'azimuth-js';
3import * as need from 'lib/need';
3import * as need from 'lib/need';
4−import * as ob from 'urbit-ob';
5import SecureLS from 'secure-ls';
4import SecureLS from 'secure-ls';
6
5
7import { Invite } from 'lib/types/Invite';
6import { Invite } from 'lib/types/Invite';
@@ -22,7 +21,7 @@ import {
22import { usePointCache } from 'store/pointCache';
21import { usePointCache } from 'store/pointCache';
23import { getOutgoingPoints } from 'views/Points';
22import { getOutgoingPoints } from 'views/Points';
24
23
25−import { isPlanet, toL1Details } from './utils/point';
24+import { getUpdatedPointMessage, isPlanet, toL1Details } from './utils/point';
26import { convertToInt } from './convertToInt';
25import { convertToInt } from './convertToInt';
27import { isDevelopment, isRopsten } from './flags';
26import { isDevelopment, isRopsten } from './flags';
28import {
27import {
@@ -47,11 +46,12 @@ import {
47 UnspawnedPoints,
46 UnspawnedPoints,
48} from '@urbit/roller-api';
47} from '@urbit/roller-api';
49
48
50−import Point, { Points } from './types/Point';
49+import Point, { PointField, Points } from './types/Point';
51import { useTimerStore } from 'store/timerStore';
50import { useTimerStore } from 'store/timerStore';
52import { ReticketParams, SendL2Params } from './types/L2Transaction';
51import { ReticketParams, SendL2Params } from './types/L2Transaction';
53import { L1Point } from './types/L1Point';
52import { L1Point } from './types/L1Point';
54import { ddmmmYYYY } from './utils/date';
53import { ddmmmYYYY } from './utils/date';
54+import { useWalletConnect } from './useWalletConnect';
55
55
56const ONE_SECOND = 1000;
56const ONE_SECOND = 1000;
57
57
@@ -75,6 +75,7 @@ export default function useRoller() {
75 urbitWallet,
75 urbitWallet,
76 walletType,
76 walletType,
77 }: any = useWallet();
77 }: any = useWallet();
78+ const { connector } = useWalletConnect();
78 const { pointCursor }: any = usePointCursor();
79 const { pointCursor }: any = usePointCursor();
79 const { web3, contracts }: any = useNetwork();
80 const { web3, contracts }: any = useNetwork();
80 const allPoints: any = usePointCache();
81 const allPoints: any = usePointCache();
@@ -135,12 +136,28 @@ export default function useRoller() {
135 [authToken]
136 [authToken]
136 );
137 );
137
138
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+
138 const fetchConfig = useCallback(async () => {
154 const fetchConfig = useCallback(async () => {
155+ getNextQuotaTime(1);
156+ api
139− Promise.all([api.getRollerConfig(), api.whenNextSlice()])
157+ .getRollerConfig()
140− .then(([config, nextSlice]) => {
158+ .then(config => {
141 setConfig(config);
159 setConfig(config);
142 setNextBatchTime(config.nextBatch);
160 setNextBatchTime(config.nextBatch);
143− setNextQuotaTime(nextSlice);
144 })
161 })
145 .catch(err => {
162 .catch(err => {
146 // TODO: more elegant error handling
163 // TODO: more elegant error handling
@@ -149,7 +166,7 @@ export default function useRoller() {
149 err
166 err
150 );
167 );
151 });
168 });
152− }, [api]); // eslint-disable-line react-hooks/exhaustive-deps
169+ }, [api, getNextQuotaTime, setNextBatchTime]);
153
170
154 const quotaReached = useCallback(() => {
171 const quotaReached = useCallback(() => {
155 if (point.l2Quota <= 0) {
172 if (point.l2Quota <= 0) {
@@ -172,13 +189,19 @@ export default function useRoller() {
172
189
173 const pointNum = Number(point);
190 const pointNum = Number(point);
174 try {
191 try {
175− const [rawDetails, l2Quota] = await Promise.all([
192+ const rawDetails = await api.getPoint(pointNum);
176− api.getPoint(pointNum),
193+ const l2Quota =
194+ rawDetails?.dominion === 'l2'
177− api.getRemainingQuota(pointNum),
195+ ? await api.getRemainingQuota(pointNum)
178− ]);
196+ : 0;
179 const details = toL1Details(rawDetails);
197 const details = toL1Details(rawDetails);
180
198
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+ });
182 } catch (e) {
205 } catch (e) {
183 console.warn(e);
206 console.warn(e);
184
207
@@ -186,12 +209,21 @@ export default function useRoller() {
186 try {
209 try {
187 const _contracts = need.contracts(contracts);
210 const _contracts = need.contracts(contracts);
188 const details = await azimuth.azimuth.getPoint(_contracts, point);
211 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+ });
190 } catch (e) {
217 } catch (e) {
191 console.warn(e);
218 console.warn(e);
192 // Just return a placeholder Point
219 // Just return a placeholder Point
193 const details: L1Point = toL1Details();
220 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+ });
195 }
227 }
196 }
228 }
197 },
229 },
@@ -199,19 +231,25 @@ export default function useRoller() {
199 );
231 );
200
232
201 const checkForUpdates = useCallback(
233 const checkForUpdates = useCallback(
234+ async (
235+ point: number,
202− async (point: number, message?: string) => {
236+ message?: string,
237+ notify = true,
238+ field?: PointField
239+ ) => {
203 const interval = setInterval(async () => {
240 const interval = setInterval(async () => {
204 const updatedPoint = await initPoint(point);
241 const updatedPoint = await initPoint(point);
242+ const changedField = !points[point]
243+ ? 'newPoint'
244+ : updatedPoint.getChangedField(points[point], field);
205
245
206− console.log('CHECK UPDATES', updatedPoint.equals(points[point]), updatedPoint)
207−
208− if (!updatedPoint.equals(points[point])) {
246+ if (!updatedPoint.isPlaceholder && changedField) {
209− console.log('UPDATED')
247+ console.log('UPDATED', changedField, updatedPoint)
210 updatePoint(updatedPoint);
248 updatePoint(updatedPoint);
211 clearInterval(interval);
249 clearInterval(interval);
212− if (Notification?.permission === 'granted') {
250+ if (Notification?.permission === 'granted' && notify) {
213 new Notification(
251 new Notification(
214− `${message || `${updatedPoint.patp} has been updated!`}`
252+ `${message || getUpdatedPointMessage(updatedPoint, changedField)}`
215 );
253 );
216 }
254 }
217 }
255 }
@@ -220,6 +258,162 @@ export default function useRoller() {
220 [points, initPoint, updatePoint]
258 [points, initPoint, updatePoint]
221 );
259 );
222
260
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+
223 const generateInviteCodes = useCallback(
417 const generateInviteCodes = useCallback(
224 async (numInvites: number) => {
418 async (numInvites: number) => {
225 const _point = need.point(pointCursor);
419 const _point = need.point(pointCursor);
@@ -281,6 +475,7 @@ export default function useRoller() {
281 type: 'spawn',
475 type: 'spawn',
282 walletType,
476 walletType,
283 web3: _web3,
477 web3: _web3,
478+ connector,
284 });
479 });
285
480
286 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
481 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
@@ -297,6 +492,7 @@ export default function useRoller() {
297 type: 'configureKeys',
492 type: 'configureKeys',
298 walletType,
493 walletType,
299 web3: _web3,
494 web3: _web3,
495+ connector,
300 });
496 });
301
497
302 await registerProxyAddress(
498 await registerProxyAddress(
@@ -308,7 +504,8 @@ export default function useRoller() {
308 1,
504 1,
309 inviteWallet.management.keys.address,
505 inviteWallet.management.keys.address,
310 walletType,
506 walletType,
311− _web3
507+ _web3,
508+ connector
312 );
509 );
313
510
314 await submitL2Transaction({
511 await submitL2Transaction({
@@ -321,6 +518,7 @@ export default function useRoller() {
321 address: inviteWallet.ownership.keys.address,
518 address: inviteWallet.ownership.keys.address,
322 walletType,
519 walletType,
323 web3: _web3,
520 web3: _web3,
521+ connector,
324 });
522 });
325
523
326 invites.push({
524 invites.push({
@@ -332,10 +530,12 @@ export default function useRoller() {
332 }
530 }
333
531
334 setStoredInvites(ls, invites);
532 setStoredInvites(ls, invites);
533+ getInvites();
335 },
534 },
336 [
535 [
337 api,
536 api,
338 authToken,
537 authToken,
538+ connector,
339 contracts,
539 contracts,
340 pointCursor,
540 pointCursor,
341 point,
541 point,
@@ -344,6 +544,7 @@ export default function useRoller() {
344 web3,
544 web3,
345 getDetails,
545 getDetails,
346 ls,
546 ls,
547+ getInvites,
347 setInviteGeneratingNum,
548 setInviteGeneratingNum,
348 ]
549 ]
349 );
550 );
@@ -379,107 +580,6 @@ export default function useRoller() {
379 }
580 }
380 }, [api, setPendingTransactions, point]);
581 }, [api, setPendingTransactions, point]);
381
582
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−
483 interface ConfigureKeysParams {
583 interface ConfigureKeysParams {
484 breach: boolean;
584 breach: boolean;
485 customNetworkSeed?: string;
585 customNetworkSeed?: string;
@@ -530,13 +630,10 @@ export default function useRoller() {
530 type: 'configureKeys',
630 type: 'configureKeys',
531 walletType,
631 walletType,
532 web3: _web3,
632 web3: _web3,
633+ connector,
533 breach,
634 breach,
534 });
635 });
535
636
536− checkForUpdates(
537− point.value,
538− `${point.patp}'s network keys have been updated!`
539− );
540 return api.getPendingTx(txHash);
637 return api.getPendingTx(txHash);
541 },
638 },
542 [// eslint-disable-line
639 [// eslint-disable-line
@@ -560,9 +657,6 @@ export default function useRoller() {
560 fromWallet,
657 fromWallet,
561 toWallet,
658 toWallet,
562 }: ReticketParams) => {
659 }: ReticketParams) => {
563− if (quotaReached()) {
564− return;
565− }
566 const azimuthPoint = await api.getPoint(point);
660 const azimuthPoint = await api.getPoint(point);
567 const proxy = 'own';
661 const proxy = 'own';
568 const _web3 = web3.getOrElse(null);
662 const _web3 = web3.getOrElse(null);
@@ -587,6 +681,7 @@ export default function useRoller() {
587 walletType,
681 walletType,
588 type: 'configureKeys',
682 type: 'configureKeys',
589 web3: _web3,
683 web3: _web3,
684+ connector,
590 });
685 });
591 nonce = nonce + 1;
686 nonce = nonce + 1;
592 requests.push(configureKeysRequest);
687 requests.push(configureKeysRequest);
@@ -601,7 +696,8 @@ export default function useRoller() {
601 nonce,
696 nonce,
602 manager,
697 manager,
603 walletType,
698 walletType,
604− _web3
699+ _web3,
700+ connector
605 );
701 );
606 nonce = nonce + 1;
702 nonce = nonce + 1;
607 requests.push(registerMgmtRequest);
703 requests.push(registerMgmtRequest);
@@ -617,7 +713,8 @@ export default function useRoller() {
617 nonce,
713 nonce,
618 toWallet.spawn.keys.address,
714 toWallet.spawn.keys.address,
619 walletType,
715 walletType,
620− _web3
716+ _web3,
717+ connector
621 );
718 );
622 nonce = nonce + 1;
719 nonce = nonce + 1;
623 requests.push(registerSpawnRequest);
720 requests.push(registerSpawnRequest);
@@ -633,6 +730,7 @@ export default function useRoller() {
633 address: to,
730 address: to,
634 walletType,
731 walletType,
635 web3: _web3,
732 web3: _web3,
733+ connector,
636 });
734 });
637 requests.push(transferTxRequest);
735 requests.push(transferTxRequest);
638
736
@@ -675,16 +773,13 @@ export default function useRoller() {
675 nonce,
773 nonce,
676 address,
774 address,
677 walletType,
775 walletType,
678− _web3
776+ _web3,
777+ connector
679 );
778 );
680
779
681− checkForUpdates(
682− point.value,
683− `${point.patp}'s ${proxyType} proxy has been updated!`
684− );
685 return api.getPendingTx(txHash);
780 return api.getPendingTx(txHash);
686 },
781 },
687− [api, point, wallet, web3, walletType, checkForUpdates, quotaReached]
782+ [api, connector, point, wallet, web3, walletType, quotaReached]
688 );
783 );
689
784
690 const adoptPoint = useCallback(
785 const adoptPoint = useCallback(
@@ -703,13 +798,9 @@ export default function useRoller() {
703 const nonce = await api.getNonce({ ship: point.value, proxy });
798 const nonce = await api.getNonce({ ship: point.value, proxy });
704 const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
799 const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
705
800
706− checkForUpdates(
707− point.value,
708− `${ob.patp(ship)} is now a resident of ${point.patp}`
709− );
710 return api.getPendingTx(txHash);
801 return api.getPendingTx(txHash);
711 },
802 },
712− [api, point, wallet, checkForUpdates, quotaReached]
803+ [api, point, wallet, quotaReached]
713 );
804 );
714
805
715 const kickPoint = useCallback(
806 const kickPoint = useCallback(
@@ -732,13 +823,9 @@ export default function useRoller() {
732 const nonce = await api.getNonce({ ship: sponsor, proxy });
823 const nonce = await api.getNonce({ ship: sponsor, proxy });
733 const txHash = await detach(api, _wallet, sponsor, proxy, nonce, ship);
824 const txHash = await detach(api, _wallet, sponsor, proxy, nonce, ship);
734
825
735− checkForUpdates(
736− point.value,
737− `${ob.patp(ship)} is no longer a resident of ${point.patp}`
738− );
739 return api.getPendingTx(txHash);
826 return api.getPendingTx(txHash);
740 },
827 },
741− [api, point, wallet, checkForUpdates, quotaReached]
828+ [api, point, wallet, quotaReached]
742 );
829 );
743
830
744 const rejectPoint = useCallback(
831 const rejectPoint = useCallback(
@@ -761,13 +848,9 @@ export default function useRoller() {
761 const nonce = await api.getNonce({ ship: sponsor, proxy });
848 const nonce = await api.getNonce({ ship: sponsor, proxy });
762 const txHash = await reject(api, _wallet, sponsor, proxy, nonce, ship);
849 const txHash = await reject(api, _wallet, sponsor, proxy, nonce, ship);
763
850
764− checkForUpdates(
765− point.value,
766− `${ob.patp(ship)} has been rejected as a resident of ${point.patp}`
767− );
768 return api.getPendingTx(txHash);
851 return api.getPendingTx(txHash);
769 },
852 },
770− [api, point, wallet, checkForUpdates, quotaReached]
853+ [api, point, wallet, quotaReached]
771 );
854 );
772
855
773 const transferPoint = useCallback(
856 const transferPoint = useCallback(
@@ -802,25 +885,13 @@ export default function useRoller() {
802 type: 'transferPoint',
885 type: 'transferPoint',
803 walletType,
886 walletType,
804 web3: _web3,
887 web3: _web3,
888+ connector,
805 reset,
889 reset,
806 });
890 });
807
891
808− checkForUpdates(
809− point.value,
810− `${point.patp} has been transferred to ${address}!`
811− );
812 return api.getPendingTx(txHash);
892 return api.getPendingTx(txHash);
813 },
893 },
814− [
894+ [api, connector, point, pointCursor, wallet, web3, walletType, quotaReached]
815− api,
816− point,
817− pointCursor,
818− wallet,
819− web3,
820− walletType,
821− checkForUpdates,
822− quotaReached,
823− ]
824 );
895 );
825
896
826 const sendL2Transaction = useCallback(
897 const sendL2Transaction = useCallback(
@@ -837,18 +908,17 @@ export default function useRoller() {
837 throw new Error('Internal Error: Missing Wallet or Point Info');
908 throw new Error('Internal Error: Missing Wallet or Point Info');
838 }
909 }
839
910
840− checkForUpdates(point.value);
841−
842 return submitL2Transaction({
911 return submitL2Transaction({
843 ...params,
912 ...params,
844 api,
913 api,
845 wallet: _wallet,
914 wallet: _wallet,
846 walletType,
915 walletType,
847 web3: _web3,
916 web3: _web3,
917+ connector,
848 ship: point.value,
918 ship: point.value,
849 });
919 });
850 },
920 },
851− [api, point, wallet, web3, walletType, checkForUpdates, quotaReached]
921+ [api, connector, point, wallet, web3, walletType, quotaReached]
852 );
922 );
853
923
854 const changeSponsor = useCallback(
924 const changeSponsor = useCallback(
@@ -939,28 +1009,38 @@ export default function useRoller() {
939 });
1009 });
940
1010
941 getPendingTransactions();
1011 getPendingTransactions();
1012+
1013+ setTimeout(() => {
1014+ getPendingTransactions();
1015+
1016+ if (!point.isDefault) {
1017+ getInvites();
1018+ }
1019+ }, TEN_SECONDS); // Should this be more like a minute?
942 }
1020 }
943 }, time);
1021 }, time);
944
1022
945 return () => clearInterval(interval);
1023 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
947
1025
948 return {
1026 return {
949 adoptPoint,
1027 adoptPoint,
950 api,
1028 api,
951 changeSponsor,
1029 changeSponsor,
1030+ checkForUpdates,
952 config,
1031 config,
953 configureNetworkingKeys,
1032 configureNetworkingKeys,
1033+ generateInviteCodes,
954 getInvites,
1034 getInvites,
955 getPoints,
1035 getPoints,
956 getPointsDetails,
1036 getPointsDetails,
957 getPendingTransactions,
1037 getPendingTransactions,
958− generateInviteCodes,
959 kickPoint,
1038 kickPoint,
960− rejectPoint,
961− transferPoint,
962 ls,
1039 ls,
963 performL2Reticket,
1040 performL2Reticket,
1041+ rejectPoint,
964 setProxyAddress,
1042 setProxyAddress,
1043+ spawnPoint,
1044+ transferPoint,
965 };
1045 };
966}
1046}
src/lib/utils/point.ts+9
@@ -1,6 +1,7 @@
1import { L2Point } from '@urbit/roller-api';
1import { L2Point } from '@urbit/roller-api';
2import { azimuth } from 'azimuth-js';
2import { azimuth } from 'azimuth-js';
3import { L1Point } from 'lib/types/L1Point';
3import { L1Point } from 'lib/types/L1Point';
4+import Point from 'lib/types/Point';
4
5
5export const isGalaxy = (point: number) =>
6export const isGalaxy = (point: number) =>
6 azimuth.getPointSize(point) === azimuth.PointSize.Galaxy;
7 azimuth.getPointSize(point) === azimuth.PointSize.Galaxy;
@@ -32,3 +33,11 @@ export const toL1Details = (point?: L2Point): L1Point => {
32 votingProxy: point?.ownership?.votingProxy?.address!,
33 votingProxy: point?.ownership?.votingProxy?.address!,
33 };
34 };
34};
35};
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 @@
1import { Just, Nothing } from 'folktale/maybe';
1import { Just, Nothing } from 'folktale/maybe';
2import { randomHex } from 'web3-utils';
2import { randomHex } from 'web3-utils';
3+import WalletConnect from '@walletconnect/client';
3import RollerRPCAPI, {
4import RollerRPCAPI, {
4 Proxy,
5 Proxy,
5 Signature,
6 Signature,
@@ -60,12 +61,21 @@ export const generateHashAndSign = async (
60 type: string,
61 type: string,
61 data: any,
62 data: any,
62 walletType: symbol,
63 walletType: symbol,
63− web3: any
64+ web3: any,
65+ connector: WalletConnect | null
64) => {
66) => {
65 if (walletType === WALLET_TYPES.METAMASK) {
67 if (walletType === WALLET_TYPES.METAMASK) {
66 const hash = await api.prepareForSigning(nonce, from, type, data);
68 const hash = await api.prepareForSigning(nonce, from, type, data);
67 const sig = await web3.eth.personal.sign(hash, wallet.address, '');
69 const sig = await web3.eth.personal.sign(hash, wallet.address, '');
68 return sig;
70 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;
69 } else {
79 } else {
70 const hash = await api.getUnsignedTx(nonce, from, type, data);
80 const hash = await api.getUnsignedTx(nonce, from, type, data);
71 const sig = await signTransactionHash(hash, wallet.privateKey);
81 const sig = await signTransactionHash(hash, wallet.privateKey);
@@ -81,7 +91,8 @@ export const spawn = async (
81 nonce: number,
91 nonce: number,
82 planet: number,
92 planet: number,
83 walletType: symbol,
93 walletType: symbol,
84− web3: any
94+ web3: any,
95+ connector: WalletConnect | null
85) => {
96) => {
86 const from = {
97 const from = {
87 ship: _point, //ship that is spawning the planet
98 ship: _point, //ship that is spawning the planet
@@ -101,7 +112,8 @@ export const spawn = async (
101 'spawn',
112 'spawn',
102 data,
113 data,
103 walletType,
114 walletType,
104− web3
115+ web3,
116+ connector
105 );
117 );
106 return api.spawn(sig, from, wallet.address, data);
118 return api.spawn(sig, from, wallet.address, data);
107};
119};
@@ -115,6 +127,7 @@ export const configureKeys = async (
115 networkSeed: any,
127 networkSeed: any,
116 walletType: symbol,
128 walletType: symbol,
117 web3: any,
129 web3: any,
130+ connector: WalletConnect | null,
118 breach?: boolean
131 breach?: boolean
119) => {
132) => {
120 const from = {
133 const from = {
@@ -149,7 +162,8 @@ export const configureKeys = async (
149 'configureKeys',
162 'configureKeys',
150 data,
163 data,
151 walletType,
164 walletType,
152− web3
165+ web3,
166+ connector
153 );
167 );
154 return api.configureKeys(sig, from, wallet.address, data);
168 return api.configureKeys(sig, from, wallet.address, data);
155};
169};
@@ -163,6 +177,7 @@ export const transferPointRequest = async (
163 address: string,
177 address: string,
164 walletType: symbol,
178 walletType: symbol,
165 web3: any,
179 web3: any,
180+ connector: WalletConnect | null,
166 reset?: boolean
181 reset?: boolean
167) => {
182) => {
168 const from = {
183 const from = {
@@ -183,7 +198,8 @@ export const transferPointRequest = async (
183 'transferPoint',
198 'transferPoint',
184 data,
199 data,
185 walletType,
200 walletType,
186− web3
201+ web3,
202+ connector
187 );
203 );
188
204
189 return api.transferPoint(sig, from, wallet.address, data);
205 return api.transferPoint(sig, from, wallet.address, data);
@@ -297,7 +313,8 @@ export const registerProxyAddress = async (
297 nonce: number,
313 nonce: number,
298 address: string,
314 address: string,
299 walletType: symbol,
315 walletType: symbol,
300− web3: any
316+ web3: any,
317+ connector: WalletConnect | null
301) => {
318) => {
302 const from = {
319 const from = {
303 ship: _point,
320 ship: _point,
@@ -313,7 +330,8 @@ export const registerProxyAddress = async (
313 proxyType(proxyAddressType),
330 proxyType(proxyAddressType),
314 data,
331 data,
315 walletType,
332 walletType,
316− web3
333+ web3,
334+ connector
317 );
335 );
318
336
319 return setProxy(api, proxyAddressType, sig, from, wallet.address, data);
337 return setProxy(api, proxyAddressType, sig, from, wallet.address, data);
@@ -370,7 +388,17 @@ const getDataAndMethod = (args: L2TransactionArgs) => {
370};
388};
371
389
372export const submitL2Transaction = async (args: L2TransactionArgs) => {
390export 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;
374
402
375 const from = { ship, proxy };
403 const from = { ship, proxy };
376 const { data, method } = getDataAndMethod(args);
404 const { data, method } = getDataAndMethod(args);
@@ -383,7 +411,8 @@ export const submitL2Transaction = async (args: L2TransactionArgs) => {
383 type,
411 type,
384 data,
412 data,
385 walletType,
413 walletType,
386− web3
414+ web3,
415+ connector
387 );
416 );
388
417
389 return method(sig, from, wallet.address, data);
418 return method(sig, from, wallet.address, data);
src/store/rollerStore.ts+17−7
@@ -6,7 +6,17 @@ import { Invite, Invites } from 'lib/types/Invite';
6import Point, { Points } from 'lib/types/Point';
6import Point, { Points } from 'lib/types/Point';
7import { toL1Details } from 'lib/utils/point';
7import { toL1Details } from 'lib/utils/point';
8
8
9+const getPointsAndList = (points: Points, point: Point) => ({
10+ points,
11+ pointList: Object.values(points),
12+ point: point.isDefault ? point : points[point.value],
13+});
14+
9−export const EMPTY_POINT = new Point(-1, toL1Details({}), '');
15+export const EMPTY_POINT = new Point({
16+ value: -1,
17+ details: toL1Details({}),
18+ address: '',
19+});
10
20
11export interface RollerStore {
21export interface RollerStore {
12 nextBatchTime: number;
22 nextBatchTime: number;
@@ -62,7 +72,7 @@ export const useRollerStore = create<RollerStore>(set => ({
62 setPoint: (point: number) =>
72 setPoint: (point: number) =>
63 set(state => ({ point: state.points[point] || EMPTY_POINT })),
73 set(state => ({ point: state.points[point] || EMPTY_POINT })),
64 setPoints: (points: Points) =>
74 setPoints: (points: Points) =>
65− set(() => ({ points, pointList: Object.values(points) })),
75+ set(({ point }) => getPointsAndList(points, point)),
66 setInvites: (point: number, invites: Invite[]) =>
76 setInvites: (point: number, invites: Invite[]) =>
67 set(state => {
77 set(state => {
68 const newInvites: Invites = {};
78 const newInvites: Invites = {};
@@ -89,11 +99,11 @@ export const useRollerStore = create<RollerStore>(set => ({
89 }),
99 }),
90 setInvitesLoading: (invitesLoading: boolean) =>
100 setInvitesLoading: (invitesLoading: boolean) =>
91 set(() => ({ invitesLoading })),
101 set(() => ({ invitesLoading })),
92− updatePoint: (point: Point) =>
102+ updatePoint: (newPoint: Point) =>
93− set(({ points }) => {
103+ set(({ point, points }) => {
94− const newPoints: Points = {};
104+ const newPoints: Points = Object.assign({}, points);
95− newPoints[point.value] = point;
105+ newPoints[newPoint.value] = newPoint;
96− return { points: Object.assign(points, newPoints) };
106+ return getPointsAndList(newPoints, point);
97 }),
107 }),
98 setModalText: (modalText?: string) => set(() => ({ modalText })),
108 setModalText: (modalText?: string) => set(() => ({ modalText })),
99}));
109}));
src/views/AcceptLockup.js+1−1
@@ -148,7 +148,7 @@ export default function AcceptLockup() {
148 full
148 full
149 as={InlineEthereumTransaction}
149 as={InlineEthereumTransaction}
150 {...bind}
150 {...bind}
151− onReturn={() => pop()}
151+ onReturn={pop}
152 />
152 />
153 </Grid>
153 </Grid>
154 </View>
154 </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';
2import cn from 'classnames';
2import cn from 'classnames';
3import { Nothing } from 'folktale/maybe';
3import { Nothing } from 'folktale/maybe';
4import { Grid, Text, CheckboxInput, Button } from 'indigo-react';
4import { Grid, Text, CheckboxInput, Button } from 'indigo-react';
@@ -60,7 +60,7 @@ export default function AcceptTransfer() {
60 const { pop } = useLocalRouter();
60 const { pop } = useLocalRouter();
61 const { setPointCursor } = usePointCursor();
61 const { setPointCursor } = usePointCursor();
62 const { point } = useRollerStore();
62 const { point } = useRollerStore();
63− const { transferPoint } = useRoller();
63+ const { transferPoint, checkForUpdates } = useRoller();
64
64
65 const { wallet } = useWallet();
65 const { wallet } = useWallet();
66 const _address = need.addressFromWallet(wallet);
66 const _address = need.addressFromWallet(wallet);
@@ -71,6 +71,15 @@ export default function AcceptTransfer() {
71
71
72 const { completed, bind, inputsLocked, construct } = useAcceptTransfer();
72 const { completed, bind, inputsLocked, construct } = useAcceptTransfer();
73
73
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+
74 const initialValues = useMemo(() => ({ noReset: false }), []);
83 const initialValues = useMemo(() => ({ noReset: false }), []);
75
84
76 const validate = useMemo(
85 const validate = useMemo(
@@ -92,8 +101,9 @@ export default function AcceptTransfer() {
92
101
93 const acceptTransfer = useCallback(async () => {
102 const acceptTransfer = useCallback(async () => {
94 await transferPoint(_address, reset);
103 await transferPoint(_address, reset);
104+ checkForUpdates(point.value, `${point.patp} has been transferred to you!`);
95 pop();
105 pop();
96− }, [transferPoint, pop, _address, reset]);
106+ }, [transferPoint, checkForUpdates, point, pop, _address, reset]);
97
107
98 return (
108 return (
99 <View
109 <View
@@ -151,7 +161,7 @@ export default function AcceptTransfer() {
151 full
161 full
152 as={InlineEthereumTransaction}
162 as={InlineEthereumTransaction}
153 {...bind}
163 {...bind}
154− onReturn={() => pop()}
164+ onReturn={pop}
155 />
165 />
156 )}
166 )}
157 </Grid>
167 </Grid>
src/views/Adopt.js+1−1
@@ -80,7 +80,7 @@ export default function Adopt() {
80 full
80 full
81 {...bind}
81 {...bind}
82 as={InlineEthereumTransaction}
82 as={InlineEthereumTransaction}
83− onReturn={() => pop()}
83+ onReturn={pop}
84 />
84 />
85 </Grid>
85 </Grid>
86 </View>
86 </View>
src/views/CancelTransfer.js+23−8
@@ -1,4 +1,4 @@
1−import React, { useCallback } from 'react';
1+import React, { useCallback, useEffect } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3import { Grid, Text, Button } from 'indigo-react';
3import { Grid, Text, Button } from 'indigo-react';
4import * as azimuth from 'azimuth-js';
4import * as azimuth from 'azimuth-js';
@@ -56,7 +56,7 @@ function AdminCancelTransfer() {
56 const { pop } = useLocalRouter();
56 const { pop } = useLocalRouter();
57 const { getDetails } = usePointCache();
57 const { getDetails } = usePointCache();
58 const { point } = useRollerStore();
58 const { point } = useRollerStore();
59− const { setProxyAddress } = useRoller();
59+ const { setProxyAddress, checkForUpdates } = useRoller();
60 const { pointCursor } = usePointCursor();
60 const { pointCursor } = usePointCursor();
61 const _point = need.point(pointCursor);
61 const _point = need.point(pointCursor);
62
62
@@ -65,10 +65,20 @@ function AdminCancelTransfer() {
65
65
66 const { completed, bind } = useCancelTransfer();
66 const { completed, bind } = useCancelTransfer();
67
67
68+ useEffect(() => {
69+ if (completed) {
70+ checkForUpdates(point.value, `${point.patp} transfer cancelled`);
71+ }
72+ }, [completed]); // eslint-disable-line react-hooks/exhaustive-deps
73+
68 const cancelTransfer = useCallback(async () => {
74 const cancelTransfer = useCallback(async () => {
69 await setProxyAddress('transfer', ETH_ZERO_ADDR);
75 await setProxyAddress('transfer', ETH_ZERO_ADDR);
76+ checkForUpdates(point.value, `${point.patp} transfer cancelled`);
70 pop();
77 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>;
72
82
73 return (
83 return (
74 <Window>
84 <Window>
@@ -81,12 +91,17 @@ function AdminCancelTransfer() {
81 <Grid.Item
91 <Grid.Item
82 full
92 full
83 as={Text}
93 as={Text}
84− className={cn('f5 wrap', {
94+ className={cn('f5 wrap mb5', {
85 green3: completed,
95 green3: completed,
86 })}>
96 })}>
87− {completed
97+ {completed ? (
88− ? `The outgoing transfer of ${name} has been cancelled.`
98+ <>The outgoing transfer of {formattedName} has been cancelled.</>
99+ ) : (
100+ <>
89− : `Cancel the outgoing transfer of ${name} to ${_details.transferProxy}.`}
101+ Cancel the outgoing transfer of {formattedName} to{' '}
102+ {formattedProxy}.
103+ </>
104+ )}
90 </Grid.Item>
105 </Grid.Item>
91
106
92 {point.isL2 ? (
107 {point.isL2 ? (
@@ -104,7 +119,7 @@ function AdminCancelTransfer() {
104 full
119 full
105 as={InlineEthereumTransaction}
120 as={InlineEthereumTransaction}
106 {...bind}
121 {...bind}
107− onReturn={() => pop()}
122+ onReturn={pop}
108 />
123 />
109 )}
124 )}
110 </BodyPane>
125 </BodyPane>
src/views/CreateGalaxy.js+1−1
@@ -155,7 +155,7 @@ export default function CreateGalaxy() {
155 full
155 full
156 as={InlineEthereumTransaction}
156 as={InlineEthereumTransaction}
157 {...bind}
157 {...bind}
158− onReturn={() => pop()}
158+ onReturn={pop}
159 />
159 />
160 </>
160 </>
161 )}
161 )}
src/views/Ops/Ops.tsx+17−11
@@ -1,20 +1,24 @@
1+import { useCallback } from 'react';
1import * as need from 'lib/need';
2import * as need from 'lib/need';
2import { Box, Row, Button, Icon } from '@tlon/indigo-react';
3import { Box, Row, Button, Icon } from '@tlon/indigo-react';
3
4
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+
4import Window from 'components/L2/Window/Window';
11import Window from 'components/L2/Window/Window';
5import HeaderPane from 'components/L2/Window/HeaderPane';
12import HeaderPane from 'components/L2/Window/HeaderPane';
6import BodyPane from 'components/L2/Window/BodyPane';
13import 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';
11import View from 'components/View';
14import View from 'components/View';
12import L2BackHeader from 'components/L2/Headers/L2BackHeader';
15import L2BackHeader from 'components/L2/Headers/L2BackHeader';
13
16
14export const Ops = () => {
17export const Ops = () => {
15 const { pop, push, names }: any = useLocalRouter();
18 const { pop, push, names }: any = useLocalRouter();
16 const { pointCursor }: any = usePointCursor();
19 const { pointCursor }: any = usePointCursor();
20+ const { point } = useRollerStore();
17− const point = need.point(pointCursor);
21+ const _point = need.point(pointCursor);
18
22
19 const goSenate = useCallback(() => push(names.SENATE), [push, names]);
23 const goSenate = useCallback(() => push(names.SENATE), [push, names]);
20 const goResidents = useCallback(() => push(names.RESIDENTS), [push, names]);
24 const goResidents = useCallback(() => push(names.RESIDENTS), [push, names]);
@@ -34,11 +38,13 @@ export const Ops = () => {
34 <Window className="id-ops">
38 <Window className="id-ops">
35 <HeaderPane>
39 <HeaderPane>
36 <Row className="header-row">
40 <Row className="header-row">
37− <h5>{isGalaxy(point) ? 'Galaxy' : 'Star'} Ops</h5>
41+ <h5>{isGalaxy(_point) ? 'Galaxy' : 'Star'} Ops</h5>
42+ {point.networkKeysSet && (
38− <Button onClick={goIssuePoint} className="header-button">
43+ <Button onClick={goIssuePoint} className="header-button">
39− <Icon icon="Node" /> &nbsp;Spawn&nbsp;
44+ <Icon icon="Node" /> &nbsp;Spawn&nbsp;
40− {isGalaxy(point) ? 'Stars' : 'Planets'}
45+ {isGalaxy(_point) ? 'Stars' : 'Planets'}
41− </Button>
46+ </Button>
47+ )}
42 </Row>
48 </Row>
43 </HeaderPane>
49 </HeaderPane>
44 <BodyPane>
50 <BodyPane>
@@ -62,7 +68,7 @@ export const Ops = () => {
62 View
68 View
63 </Button>
69 </Button>
64 </Row>
70 </Row>
65− {isGalaxy(point) && (
71+ {point.isGalaxy && (
66 <Row className="between-row management">
72 <Row className="between-row management">
67 <Box>
73 <Box>
68 <Box>Proposals</Box>
74 <Box>Proposals</Box>
src/views/Party/PartySetPoolSize.js+1−1
@@ -174,7 +174,7 @@ export default function PartySetPoolSize() {
174 full
174 full
175 as={InlineEthereumTransaction}
175 as={InlineEthereumTransaction}
176 {...bind}
176 {...bind}
177− onReturn={() => pop()}
177+ onReturn={pop}
178 />
178 />
179 </>
179 </>
180 )}
180 )}
src/views/Point/IssueChild.tsx+51−13
@@ -1,6 +1,6 @@
1import React, { useCallback, useEffect, useMemo, useState } from 'react';
1import React, { useCallback, useEffect, useMemo, useState } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3−import { Grid } from 'indigo-react';
3+import { Button, Grid } from 'indigo-react';
4import Window from 'components/L2/Window/Window';
4import Window from 'components/L2/Window/Window';
5import HeaderPane from 'components/L2/Window/HeaderPane';
5import HeaderPane from 'components/L2/Window/HeaderPane';
6import BodyPane from 'components/L2/Window/BodyPane';
6import BodyPane from 'components/L2/Window/BodyPane';
@@ -11,6 +11,7 @@ import ob from 'urbit-ob';
11import { useNetwork } from 'store/network';
11import { useNetwork } from 'store/network';
12import { usePointCache } from 'store/pointCache';
12import { usePointCache } from 'store/pointCache';
13import { usePointCursor } from 'store/pointCursor';
13import { usePointCursor } from 'store/pointCursor';
14+import { useRollerStore } from 'store/rollerStore';
14
15
15import * as need from 'lib/need';
16import * as need from 'lib/need';
16import useEthereumTransaction from 'lib/useEthereumTransaction';
17import useEthereumTransaction from 'lib/useEthereumTransaction';
@@ -65,14 +66,17 @@ export function useIssueChild() {
65
66
66export default function IssueChild() {
67export default function IssueChild() {
67 const { pop }: any = useLocalRouter();
68 const { pop }: any = useLocalRouter();
68− const { api } = useRoller();
69+ const { api, checkForUpdates, spawnPoint } = useRoller();
69 const { pointCursor }: any = usePointCursor();
70 const { pointCursor }: any = usePointCursor();
71+ const { point } = useRollerStore();
70
72
71 const _point = convertToInt(need.point(pointCursor), 10);
73 const _point = convertToInt(need.point(pointCursor), 10);
72 const [availablePoints, setAvailablePoints] = useState<Set<number> | null>(
74 const [availablePoints, setAvailablePoints] = useState<Set<number> | null>(
73 null
75 null
74 );
76 );
75 const [candidates, setCandidates] = useState<string[]>([]);
77 const [candidates, setCandidates] = useState<string[]>([]);
78+ const [pointToSpawn, setPointToSpawn] = useState<number | null>();
79+ const [l2SpawnSent, setL2SpawnSent] = useState(false);
76
80
77 const fetchAvailablePoints = useCallback(async () => {
81 const fetchAvailablePoints = useCallback(async () => {
78 const available = await api.getUnspawned(_point);
82 const available = await api.getUnspawned(_point);
@@ -107,6 +111,15 @@ export default function IssueChild() {
107 shuffle();
111 shuffle();
108 }, [fetchAvailablePoints, shuffle]);
112 }, [fetchAvailablePoints, shuffle]);
109
113
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+
110 const {
123 const {
111 isDefaultState,
124 isDefaultState,
112 construct,
125 construct,
@@ -116,6 +129,12 @@ export default function IssueChild() {
116 bind,
129 bind,
117 } = useIssueChild();
130 } = useIssueChild();
118
131
132+ useEffect(() => {
133+ if (completed && pointToSpawn) {
134+ checkForUpdates(pointToSpawn);
135+ }
136+ }, [completed]); // eslint-disable-line react-hooks/exhaustive-deps
137+
119 const validateFormAsync = useCallback(
138 const validateFormAsync = useCallback(
120 async values => {
139 async values => {
121 const point = patp2dec(values.point);
140 const point = patp2dec(values.point);
@@ -154,7 +173,9 @@ export default function IssueChild() {
154 const onValues = useCallback(
173 const onValues = useCallback(
155 ({ valid, values }) => {
174 ({ valid, values }) => {
156 if (valid) {
175 if (valid) {
157− construct(patp2dec(values.point), values.owner);
176+ const pointDecimal = patp2dec(values.point);
177+ construct(pointDecimal, values.owner);
178+ setPointToSpawn(pointDecimal);
158 } else {
179 } else {
159 unconstruct();
180 unconstruct();
160 }
181 }
@@ -183,14 +204,16 @@ export default function IssueChild() {
183 onValues={onValues}>
204 onValues={onValues}>
184 {({ handleSubmit, values }: any) => (
205 {({ handleSubmit, values }: any) => (
185 <>
206 <>
186− {completed && (
207+ {(completed || l2SpawnSent) && (
187 <Grid.Item
208 <Grid.Item
188 full
209 full
189− as={Text}
210+ as={'div'}
211+ style={{ fontSize: 14 }}
190− className={cn('f5 wrap', {
212+ className={cn('f5 wrap mb5', {
191 green3: completed,
213 green3: completed,
192 })}>
214 })}>
215+ <span className="mono">{values.point}</span> has been
193− {values.point} has been spawned and can be claimed by{' '}
216+ spawned and can be claimed by{' '}
194 <CopiableAddress>{values.owner}</CopiableAddress>.
217 <CopiableAddress>{values.owner}</CopiableAddress>.
195 </Grid.Item>
218 </Grid.Item>
196 )}
219 )}
@@ -246,12 +269,27 @@ export default function IssueChild() {
246
269
247 <Grid.Item full as={FormError} />
270 <Grid.Item full as={FormError} />
248
271
272+ {point.isL2Spawn ? (
249− <Grid.Item
273+ <Grid.Item
274+ as={Button}
250− full
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
251− as={InlineEthereumTransaction}
288+ as={InlineEthereumTransaction}
252− {...bind}
289+ {...bind}
253− onReturn={() => pop()}
290+ onReturn={pop}
254− />
291+ />
292+ )}
255 </>
293 </>
256 )}
294 )}
257 </BridgeForm>
295 </BridgeForm>
src/views/Point/MigrateL2.tsx+11−3
@@ -36,7 +36,8 @@ import { isPlanet } from 'lib/utils/point';
36
36
37import './MigrateL2.scss';
37import './MigrateL2.scss';
38import { useRollerStore } from 'store/rollerStore';
38import { 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';
40
41
41const PointEntry = ({
42const PointEntry = ({
42 point,
43 point,
@@ -99,6 +100,7 @@ const useMigrate = () => {
99export default function MigrateL2() {
100export default function MigrateL2() {
100 const { pop }: any = useLocalRouter();
101 const { pop }: any = useLocalRouter();
101 const { point, pointList } = useRollerStore();
102 const { point, pointList } = useRollerStore();
103+ const { checkForUpdates } = useRoller();
102 const l1Points = pointList.filter(({ canMigrate }) => canMigrate);
104 const l1Points = pointList.filter(({ canMigrate }) => canMigrate);
103
105
104 const [proceed, setProceed] = useState(false);
106 const [proceed, setProceed] = useState(false);
@@ -126,9 +128,15 @@ export default function MigrateL2() {
126
128
127 useEffect(() => {
129 useEffect(() => {
128 if (completed) {
130 if (completed) {
131+ checkForUpdates(
132+ selectedPoint.value,
133+ `${selectedPoint.patp} has been migrated to Layer 2!`,
134+ true,
135+ PointField.layer
136+ );
129 pop();
137 pop();
130 }
138 }
131− }, [completed, pop]);
139+ }, [completed, pop]); // eslint-disable-line react-hooks/exhaustive-deps
132
140
133 const goBack = useCallback(() => {
141 const goBack = useCallback(() => {
134 if (proceed) {
142 if (proceed) {
@@ -241,7 +249,7 @@ export default function MigrateL2() {
241 full
249 full
242 as={InlineEthereumTransaction}
250 as={InlineEthereumTransaction}
243 label={`${
251 label={`${
244− transfer ? 'Transfer point' : 'Set spawn proxy'
252+ transfer ? 'Transfer ship' : 'Set spawn proxy'
245 } to Layer 2`}
253 } to Layer 2`}
246 {...bind}
254 {...bind}
247 onReturn={pop}
255 onReturn={pop}
src/views/Point/Point.scss−1
@@ -51,7 +51,6 @@
51}
51}
52
52
53.network-keys-modal {
53.network-keys-modal {
54− padding: 16px;
55 background-color: white;
54 background-color: white;
56 border-radius: $medium-border-radius;
55 border-radius: $medium-border-radius;
57 line-height: 24px;
56 line-height: 24px;
src/views/Point/Point.tsx+1−2
@@ -18,7 +18,6 @@ import Card from 'components/L2/Card';
18
18
19import * as need from 'lib/need';
19import * as need from 'lib/need';
20import useInvites from 'lib/useInvites';
20import useInvites from 'lib/useInvites';
21−import useCurrentPermissions from 'lib/useCurrentPermissions';
22import { useLocalRouter } from 'lib/LocalRouter';
21import { useLocalRouter } from 'lib/LocalRouter';
23import useRoller from 'lib/useRoller';
22import useRoller from 'lib/useRoller';
24import useSeenMissingKeys from 'lib/useSeenMissingKeys';
23import useSeenMissingKeys from 'lib/useSeenMissingKeys';
@@ -79,7 +78,7 @@ export default function Point() {
79 loadL2Info();
78 loadL2Info();
80 }, [point]); // eslint-disable-line react-hooks/exhaustive-deps
79 }, [point]); // eslint-disable-line react-hooks/exhaustive-deps
81
80
82− const { isParent, canManage, isSpawnProxy } = useCurrentPermissions();
81+ const { isParent, canManage, isSpawnProxy } = point;
83
82
84 // L1 invites
83 // L1 invites
85 const l1Invites = useInvites(point.value);
84 const l1Invites = useInvites(point.value);
src/views/Points.js+25−22
@@ -9,6 +9,7 @@ import { usePointCache } from 'store/pointCache';
9import { usePointCursor } from 'store/pointCursor';
9import { usePointCursor } from 'store/pointCursor';
10import { useStarReleaseCache } from 'store/starRelease';
10import { useStarReleaseCache } from 'store/starRelease';
11import { useNetwork } from 'store/network';
11import { useNetwork } from 'store/network';
12+import { useRollerStore } from 'store/rollerStore';
12
13
13import * as need from 'lib/need';
14import * as need from 'lib/need';
14import { isZeroAddress, abbreviateAddress } from 'lib/utils/address';
15import { isZeroAddress, abbreviateAddress } from 'lib/utils/address';
@@ -138,11 +139,21 @@ export default function Points() {
138 rejectedPoints,
139 rejectedPoints,
139 addRejectedPoint,
140 addRejectedPoint,
140 ] = useRejectedIncomingPointTransfers();
141 ] = useRejectedIncomingPointTransfers();
142+ const { pointList } = useRollerStore();
141 const { syncStarReleaseDetails, starReleaseDetails } = useStarReleaseCache();
143 const { syncStarReleaseDetails, starReleaseDetails } = useStarReleaseCache();
142
144
143 const outgoingPoints = useMemo(
145 const outgoingPoints = useMemo(
144− () => getOutgoingPoints(controlledPoints, getDetails),
146+ () => pointList.filter(({ isTransferProxySet }) => isTransferProxySet),
145− [getDetails, controlledPoints]
147+ [pointList]
148+ );
149+
150+ const incomingPoints = useMemo(
151+ () =>
152+ pointList.filter(
153+ ({ isTransferProxy, value }) =>
154+ isTransferProxy && !rejectedPoints.includes(value)
155+ ),
156+ [pointList, rejectedPoints]
146 );
157 );
147
158
148 const _contracts = need.contracts(contracts);
159 const _contracts = need.contracts(contracts);
@@ -178,10 +189,7 @@ export default function Points() {
178 // if there are any pending transfers, incoming or outgoing, stay on this
189 // if there are any pending transfers, incoming or outgoing, stay on this
179 // page, because those can only be completed/cancelled here.
190 // page, because those can only be completed/cancelled here.
180 useEffect(() => {
191 useEffect(() => {
181− if (
182− Nothing.hasInstance(outgoingPoints) ||
183− Nothing.hasInstance(starReleaseDetails)
192+ if (Nothing.hasInstance(starReleaseDetails)) {
184− ) {
185 return;
193 return;
186 }
194 }
187 controlledPoints.matchWith({
195 controlledPoints.matchWith({
@@ -196,12 +204,9 @@ export default function Points() {
196 ...c.value.managingPoints,
204 ...c.value.managingPoints,
197 ...c.value.spawningPoints,
205 ...c.value.spawningPoints,
198 ];
206 ];
199− const incoming = c.value.incomingPoints.filter(
200− p => !rejectedPoints.includes(p)
201− );
202 if (
207 if (
203 all.length === 1 &&
208 all.length === 1 &&
204− incoming.length === 0 &&
209+ incomingPoints.length === 0 &&
205 outgoingPoints.length === 0 &&
210 outgoingPoints.length === 0 &&
206 (starReleaseDetails.value === null ||
211 (starReleaseDetails.value === null ||
207 starReleaseDetails.value.total === 0)
212 starReleaseDetails.value.total === 0)
@@ -217,6 +222,7 @@ export default function Points() {
217 controlledPoints,
222 controlledPoints,
218 rejectedPoints,
223 rejectedPoints,
219 outgoingPoints,
224 outgoingPoints,
225+ incomingPoints,
220 setPointCursor,
226 setPointCursor,
221 popAndPush,
227 popAndPush,
222 names,
228 names,
@@ -228,18 +234,15 @@ export default function Points() {
228 const loading = Nothing.hasInstance(controlledPoints);
234 const loading = Nothing.hasInstance(controlledPoints);
229
235
230 const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
236 const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
231− const incomingPoints = maybeGetResult(
232− controlledPoints,
233− 'incomingPoints',
234− []
235− ).filter(point => !rejectedPoints.includes(point));
236 const managingPoints = maybeGetResult(controlledPoints, 'managingPoints', []);
237 const managingPoints = maybeGetResult(controlledPoints, 'managingPoints', []);
237 const votingPoints = maybeGetResult(controlledPoints, 'votingPoints', []);
238 const votingPoints = maybeGetResult(controlledPoints, 'votingPoints', []);
238 const spawningPoints = maybeGetResult(controlledPoints, 'spawningPoints', []);
239 const spawningPoints = maybeGetResult(controlledPoints, 'spawningPoints', []);
239 const lockedPoints = maybeLockedPoints.getOrElse([]);
240 const lockedPoints = maybeLockedPoints.getOrElse([]);
240
241
241 const allPoints = [
242 const allPoints = [
242− ...ownedPoints.filter(p => !outgoingPoints.includes(p)),
243+ ...ownedPoints.filter(
244+ p => !outgoingPoints.find(({ value }) => value === p)
245+ ),
243 ...managingPoints,
246 ...managingPoints,
244 ...votingPoints,
247 ...votingPoints,
245 ...spawningPoints,
248 ...spawningPoints,
@@ -338,15 +341,15 @@ export default function Points() {
338 {abbreviateAddress(address)}
341 {abbreviateAddress(address)}
339 </CopiableAddress>
342 </CopiableAddress>
340 </NavHeader>
343 </NavHeader>
341− {incomingPoints.map(point => (
344+ {incomingPoints.map(({ value }) => (
342 <IncomingPoint
345 <IncomingPoint
343− key={point}
346+ key={value}
344− point={point}
347+ point={value}
345 accept={() => {
348 accept={() => {
346− setPointCursor(Just(point));
349+ setPointCursor(Just(value));
347 push(names.ACCEPT_TRANSFER);
350 push(names.ACCEPT_TRANSFER);
348 }}
351 }}
349− reject={() => addRejectedPoint(point)}
352+ reject={() => addRejectedPoint(value)}
350 />
353 />
351 ))}
354 ))}
352 <Grid>
355 <Grid>
@@ -369,7 +372,7 @@ export default function Points() {
369 <Grid.Item
372 <Grid.Item
370 full
373 full
371 as={PointList}
374 as={PointList}
372− points={outgoingPoints}
375+ points={outgoingPoints.map(({ value }) => value)}
373 actions={(point, i) => (
376 actions={(point, i) => (
374 <ActionButtons
377 <ActionButtons
375 actions={[
378 actions={[
src/views/Release/Transfer.js+1−1
@@ -159,7 +159,7 @@ export default function Transfer({ className, goActive }) {
159 as={InlineEthereumTransaction}
159 as={InlineEthereumTransaction}
160 {...bind}
160 {...bind}
161 label="Approve Transfer"
161 label="Approve Transfer"
162− onReturn={() => pop()}
162+ onReturn={pop}
163 />
163 />
164 </>
164 </>
165 )}
165 )}
src/views/UrbitID/Home.tsx+1−2
@@ -4,7 +4,6 @@ import { Box, Button, Icon, Row } from '@tlon/indigo-react';
4
4
5import { useLocalRouter } from 'lib/LocalRouter';
5import { useLocalRouter } from 'lib/LocalRouter';
6import { PROXY_TYPE } from 'lib/proxy';
6import { PROXY_TYPE } from 'lib/proxy';
7−import useCurrentPermissions from 'lib/useCurrentPermissions';
8import { convertToInt } from 'lib/convertToInt';
7import { convertToInt } from 'lib/convertToInt';
9import { ETH_ZERO_ADDR, MASTER_TICKET_TOOLTIP } from 'lib/constants';
8import { ETH_ZERO_ADDR, MASTER_TICKET_TOOLTIP } from 'lib/constants';
10import { abbreviateAddress } from 'lib/utils/address';
9import { abbreviateAddress } from 'lib/utils/address';
@@ -45,7 +44,7 @@ export default function UrbitIDHome() {
45 const goResetKeys = useCallback(() => push(names.RESET_KEYS), [push, names]);
44 const goResetKeys = useCallback(() => push(names.RESET_KEYS), [push, names]);
46
45
47 const isMasterTicket = Just.hasInstance(urbitWallet);
46 const isMasterTicket = Just.hasInstance(urbitWallet);
48− const { isOwner, canManage } = useCurrentPermissions();
47+ const { isOwner, canManage } = point;
49 const networkRevision = convertToInt(point.keyRevisionNumber, 10);
48 const networkRevision = convertToInt(point.keyRevisionNumber, 10);
50
49
51 const goSetProxy = useCallback(
50 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';
2import cn from 'classnames';
2import cn from 'classnames';
3import { Grid, Text, Flex, Button } from 'indigo-react';
3import { Grid, Text, Flex, Button } from 'indigo-react';
4import * as azimuth from 'azimuth-js';
4import * as azimuth from 'azimuth-js';
@@ -124,20 +124,21 @@ function useSetProxy(proxyType) {
124
124
125export default function SetProxy() {
125export default function SetProxy() {
126 const { data, pop } = useLocalRouter();
126 const { data, pop } = useLocalRouter();
127− const { getDetails } = usePointCache();
128− const { pointCursor } = usePointCursor();
129 const { contracts } = useNetwork();
127 const { contracts } = useNetwork();
128+ const { point, setLoading } = useRollerStore();
130 const {
129 const {
131− point: { isL2, isL2Spawn },
130+ setProxyAddress,
132− } = useRollerStore();
133− const { setProxyAddress, getPendingTransactions } = useRoller();
131+ getPendingTransactions,
132+ checkForUpdates,
133+ } = useRoller();
134 const [newAddress, setNewAddress] = useState('');
134 const [newAddress, setNewAddress] = useState('');
135
135
136− const _point = need.point(pointCursor);
137− const _details = need.details(getDetails(_point));
138 const _contracts = need.contracts(contracts);
136 const _contracts = need.contracts(contracts);
139
137
140− const properProxyType = capitalize(proxyTypeToHuman(data.proxyType));
138+ const humanProxyType = proxyTypeToHuman(data.proxyType);
139+ const properProxyType = capitalize(humanProxyType);
140+
141+ const { isL2, isL2Spawn } = point;
141
142
142 const isL2Tx =
143 const isL2Tx =
143 (data.proxyType === 'MANAGEMENT' && isL2) ||
144 (data.proxyType === 'MANAGEMENT' && isL2) ||
@@ -154,27 +155,36 @@ export default function SetProxy() {
154 } = useSetProxy(data.proxyType);
155 } = useSetProxy(data.proxyType);
155
156
156 const setProxy = useCallback(async () => {
157 const setProxy = useCallback(async () => {
157− // setLoading(true);
158+ setLoading(true);
158− console.log(newAddress);
159 if (newAddress === '') return;
159 if (newAddress === '') return;
160 try {
160 try {
161 await setProxyAddress(getL2ProxyName(data.proxyType), newAddress);
161 await setProxyAddress(getL2ProxyName(data.proxyType), newAddress);
162− getPendingTransactions(_point);
162+ getPendingTransactions(point.value);
163 pop();
163 pop();
164 } catch (error) {
164 } catch (error) {
165 // setError(error);
165 // setError(error);
166 } finally {
166 } finally {
167− // setLoading(false);
167+ setLoading(false);
168 }
168 }
169 }, [
169 }, [
170 newAddress,
170 newAddress,
171 getPendingTransactions,
171 getPendingTransactions,
172− _point,
172+ point,
173 pop,
173 pop,
174 data.proxyType,
174 data.proxyType,
175 setProxyAddress,
175 setProxyAddress,
176+ setLoading,
176 ]);
177 ]);
177
178
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+
178 const validateForm = useCallback((values, errors) => {
188 const validateForm = useCallback((values, errors) => {
179 if (!values.unset && errors.address) {
189 if (!values.unset && errors.address) {
180 return errors;
190 return errors;
@@ -213,7 +223,7 @@ export default function SetProxy() {
213
223
214 const initialValues = useMemo(() => ({ unset: false }), []);
224 const initialValues = useMemo(() => ({ unset: false }), []);
215
225
216− const proxyAddress = proxyFromDetails(_details, _contracts, data.proxyType);
226+ const proxyAddress = proxyFromDetails(point, _contracts, data.proxyType);
217 const isProxySet = !isZeroAddress(proxyAddress);
227 const isProxySet = !isZeroAddress(proxyAddress);
218
228
219 const header = useMemo(() => {
229 const header = useMemo(() => {
@@ -296,7 +306,7 @@ export default function SetProxy() {
296 full
306 full
297 as={InlineEthereumTransaction}
307 as={InlineEthereumTransaction}
298 {...bind}
308 {...bind}
299− onReturn={() => pop()}
309+ onReturn={pop}
300 />
310 />
301 )}
311 )}
302 </>
312 </>
src/views/UrbitID/Transfer.js+2−2
@@ -133,7 +133,7 @@ export default function AdminTransfer() {
133 {completed && (
133 {completed && (
134 <>
134 <>
135 <Grid.Item full as={Text} className="wrap">
135 <Grid.Item full as={Text} className="wrap">
136− Started transferring <span className="mono">{name}</span>
136+ Started transferring <span className="mono">{name}</span>{' '}
137 to{' '}
137 to{' '}
138 </Grid.Item>
138 </Grid.Item>
139 <Grid.Item full as={Text}>
139 <Grid.Item full as={Text}>
@@ -175,7 +175,7 @@ export default function AdminTransfer() {
175 full
175 full
176 as={InlineEthereumTransaction}
176 as={InlineEthereumTransaction}
177 {...bind}
177 {...bind}
178− onReturn={() => pop()}
178+ onReturn={pop}
179 />
179 />
180 )}
180 )}
181 </>
181 </>
src/views/UrbitOS/ChangeSponsor.js+4−10
@@ -216,19 +216,13 @@ function CurrentEscape({ onDone }) {
216}
216}
217
217
218export default function UrbitOSChangeSponsor() {
218export default function UrbitOSChangeSponsor() {
219− const { pointCursor } = usePointCursor();
219+ const { point } = useRollerStore();
220−
221− const point = need.point(pointCursor);
222−
223− const { getDetails } = usePointCache();
224−
225− const details = need.details(getDetails(point));
226
220
227− const [requested, setRequested] = useState(details.escapeRequested);
221+ const [requested, setRequested] = useState(point.escapeRequested);
228
222
229 const onDone = useCallback(() => {
223 const onDone = useCallback(() => {
230− setRequested(details.escapeRequested);
224+ setRequested(point.escapeRequested);
231− }, [details, setRequested]);
225+ }, [point, setRequested]);
232
226
233 return requested ? (
227 return requested ? (
234 <CurrentEscape onDone={onDone} />
228 <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';
2import { Just } from 'folktale/maybe';
8import { Just } from 'folktale/maybe';
3import { Grid, ToggleInput, CheckboxInput, Button } from 'indigo-react';
9import { Grid, ToggleInput, CheckboxInput, Button } from 'indigo-react';
4import * as azimuth from 'azimuth-js';
10import * as azimuth from 'azimuth-js';
@@ -144,23 +150,15 @@ export default function UrbitOSNetworkingKeys({
144 setManualNetworkSeed,
150 setManualNetworkSeed,
145}) {
151}) {
146 const { pop } = useLocalRouter();
152 const { pop } = useLocalRouter();
147− const { pointCursor } = usePointCursor();
153+ const { point, setLoading } = useRollerStore();
148− const { getDetails } = usePointCache();
149 const {
154 const {
150− point: { isL2 },
151− } = useRollerStore();
152− const { configureNetworkingKeys, getPendingTransactions } = useRoller();
155+ configureNetworkingKeys,
156+ getPendingTransactions,
157+ checkForUpdates,
158+ } = useRoller();
153 const [breach, setBreach] = useState(false);
159 const [breach, setBreach] = useState(false);
154
160
155− const point = need.point(pointCursor);
161+ const hasKeys = point.networkKeysSet;
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− });
164
162
165 const {
163 const {
166 construct,
164 construct,
@@ -171,6 +169,15 @@ export default function UrbitOSNetworkingKeys({
171 keyfileBind,
169 keyfileBind,
172 } = useSetKeys(manualNetworkSeed, setManualNetworkSeed);
170 } = useSetKeys(manualNetworkSeed, setManualNetworkSeed);
173
171
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+
174 const validateForm = useCallback((values, errors) => {
181 const validateForm = useCallback((values, errors) => {
175 if (values.useNetworkSeed && errors.networkSeed) {
182 if (values.useNetworkSeed && errors.networkSeed) {
176 return errors;
183 return errors;
@@ -213,7 +220,7 @@ export default function UrbitOSNetworkingKeys({
213 );
220 );
214
221
215 const setNetworkingKeys = useCallback(async () => {
222 const setNetworkingKeys = useCallback(async () => {
216− // setLoading(true);
223+ setLoading(true);
217 try {
224 try {
218 await configureNetworkingKeys({
225 await configureNetworkingKeys({
219 breach,
226 breach,
@@ -228,11 +235,15 @@ export default function UrbitOSNetworkingKeys({
228 // set since they are in pending in the Roller...
235 // set since they are in pending in the Roller...
229 // we could inspect if there's a changeKeys in the local list of pending txs,...
236 // we could inspect if there's a changeKeys in the local list of pending txs,...
230 //
237 //
238+ checkForUpdates(
239+ point.value,
240+ `${point.patp}'s Networking Keys have been set!`
241+ );
231 pop();
242 pop();
232 } catch (error) {
243 } catch (error) {
233 // setError(error);
244 // setError(error);
234 } finally {
245 } finally {
235− // setLoading(false);
246+ setLoading(false);
236 }
247 }
237 }, [
248 }, [
238 breach,
249 breach,
@@ -241,6 +252,8 @@ export default function UrbitOSNetworkingKeys({
241 point,
252 point,
242 pop,
253 pop,
243 configureNetworkingKeys,
254 configureNetworkingKeys,
255+ setLoading,
256+ checkForUpdates,
244 ]);
257 ]);
245
258
246 const initialValues = useMemo(
259 const initialValues = useMemo(
@@ -330,7 +343,7 @@ export default function UrbitOSNetworkingKeys({
330 </>
343 </>
331 )}
344 )}
332
345
333− {isL2 ? (
346+ {point.isL2 ? (
334 <Grid.Item
347 <Grid.Item
335 as={Button}
348 as={Button}
336 full
349 full
@@ -346,7 +359,7 @@ export default function UrbitOSNetworkingKeys({
346 as={InlineEthereumTransaction}
359 as={InlineEthereumTransaction}
347 {...bind}
360 {...bind}
348 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
361 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
349− onReturn={() => pop()}
362+ onReturn={pop}
350 />
363 />
351 )}
364 )}
352 </Box>
365 </Box>
src/views/UrbitOS/UrbitOS.scss−1
@@ -9,7 +9,6 @@
9.show-keys-modal-content {
9.show-keys-modal-content {
10 width: 375px;
10 width: 375px;
11 background-color: white;
11 background-color: white;
12− padding: 1rem 1.5rem;
13 border-radius: $large-border-radius;
12 border-radius: $large-border-radius;
14 position: relative;
13 position: relative;
15}
14}