@@ -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}