on f3fddfe help sign in
bridge/src/lib/useRoller.ts
1048 lines 27 kB history raw
1import { useCallback, useEffect, useMemo, useState } from 'react';
2import * as azimuth from 'azimuth-js';
3import * as need from 'lib/need';
4import SecureLS from 'secure-ls';
6import { Invite } from 'lib/types/Invite';
7import {
8 deriveNetworkSeedFromUrbitWallet,
9 attemptNetworkSeedDerivation,
10} from 'lib/keys';
12import { useNetwork } from 'store/network';
13import { usePointCursor } from 'store/pointCursor';
14import { EMPTY_POINT, useRollerStore } from 'store/rollerStore';
15import { useWallet } from 'store/wallet';
16import {
17 getStoredInvites,
18 setStoredInvite,
19 setStoredInvites,
20} from 'store/storage/roller';
21import { usePointCache } from 'store/pointCache';
23import { getUpdatedPointMessage, isPlanet, toL1Details } from './utils/point';
24import { convertToInt } from './convertToInt';
25import { isDevelopment, isMainnet, isRopsten } from './flags';
26import {
27 generateInviteWallet,
28 getPendingSpawns,
29 submitL2Transaction,
30 getTimeToNextBatch,
31 registerProxyAddress,
32 isL2Spawn,
33} from './utils/roller';
34import { ETH_ZERO_ADDR, ROLLER_HOSTS, TEN_SECONDS } from './constants';
36import {
37 Config,
38 Ship,
39 Proxy,
40 RollerRPCAPI,
41 Options,
42 EthAddress,
43 UnspawnedPoints,
44} from '@urbit/roller-api';
46import Point, { PointField, Points } from './types/Point';
47import { useTimerStore } from 'store/timerStore';
48import { ReticketParams, SendL2Params } from './types/L2Transaction';
49import { L1Point } from './types/L1Point';
50import { ddmmmYYYY } from './utils/date';
51import { showNotification } from './utils/notifications';
52import { useWalletConnect } from './useWalletConnect';
53import { PendingL1Txn } from './types/PendingL1Transaction';
55const ONE_SECOND = 1000;
57interface UpdateParams {
58 point: number;
59 message?: string;
60 notify?: boolean;
61 field?: PointField;
62 l1Txn?: PendingL1Txn;
65const inviteTemplate = (
66 planet: number,
67 ticket = '',
68 owner = '',
69 hash = ''
70): Invite => ({
71 planet,
72 ticket,
73 owner,
74 hash,
75});
77export default function useRoller() {
78 const {
79 wallet,
80 authToken,
81 authMnemonic,
82 urbitWallet,
83 walletType,
84 }: any = useWallet();
85 const { connector } = useWalletConnect();
86 const { pointCursor }: any = usePointCursor();
87 const { web3, contracts }: any = useNetwork();
88 const allPoints: any = usePointCache();
89 const getDetails = allPoints?.getDetails;
91 const { setNextRoll } = useTimerStore();
93 const {
94 nextBatchTime,
95 point,
96 points,
97 pointList,
98 nextQuotaTime,
99 removeInvite,
100 setNextBatchTime,
101 setNextQuotaTime,
102 setPendingTransactions,
103 setInvites,
104 setInviteGeneratingNum,
105 setInvitesLoading,
106 setModalText,
107 setPoints,
108 updateInvite,
109 updatePoint,
110 storePendingL1Txn,
111 deletePendingL1Txn,
112 } = useRollerStore();
113 const [config, setConfig] = useState<Config | null>(null);
115 const options: Options = useMemo(() => {
116 const type = isMainnet || isRopsten ? 'https' : 'http';
117 const host = isMainnet
118 ? ROLLER_HOSTS.MAINNET
119 : isRopsten
120 ? ROLLER_HOSTS.ROPSTEN
121 : ROLLER_HOSTS.LOCAL;
122 const port = isMainnet || isRopsten ? 443 : 8080;
123 const path = '/v1/roller';
125 return {
126 transport: {
127 type,
128 host,
129 port,
130 path,
131 },
132 };
133 }, []);
135 const api = useMemo(() => {
136 return new RollerRPCAPI(options);
137 }, [options]);
139 const ls = useMemo(
140 () =>
141 new SecureLS({
142 isCompression: false,
143 encryptionSecret: authToken.getOrElse('default'),
144 }),
145 [authToken]
146 );
148 const getNextQuotaTime = useCallback(
149 (nextSlice: number) => {
150 setTimeout(() => {
151 api
152 .whenNextSlice()
153 .then(next => {
154 setNextQuotaTime(next);
155 getNextQuotaTime(next);
156 })
157 .catch(err => console.warn(err));
158 }, nextSlice - new Date().getTime());
159 },
160 [api, setNextQuotaTime]
161 );
163 const fetchConfig = useCallback(async () => {
164 getNextQuotaTime(1);
165 api
166 .getRollerConfig()
167 .then(config => {
168 setConfig(config);
169 setNextBatchTime(config.nextBatch);
170 })
171 .catch(err => {
172 // TODO: more elegant error handling
173 console.warn(
174 '[fetchConfig:failed] check the roller connection?\n',
175 err
176 );
177 });
178 }, [api, getNextQuotaTime, setNextBatchTime]);
180 const quotaReached = useCallback(() => {
181 if (point.l2Quota <= 0) {
182 setModalText(
183 `You have reached your weekly L2 transaction limit. You will get another
184 ${config?.rollerQuota} transactions on ${ddmmmYYYY(nextQuotaTime)}.`
185 );
186 }
188 return point.l2Quota <= 0;
189 }, [config, nextQuotaTime, point, setModalText]);
191 const initPoint = useCallback(
192 async (point: string | number) => {
193 const _wallet = wallet.getOrElse(null);
195 if (!_wallet) {
196 return EMPTY_POINT;
197 }
199 const pointNum = Number(point);
200 try {
201 const rawDetails = await api.getPoint(pointNum);
202 const l2Quota = isL2Spawn(rawDetails?.dominion)
203 ? await api.getRemainingQuota(pointNum)
204 : 0;
205 const details = toL1Details(rawDetails);
207 return new Point({
208 value: pointNum,
209 details,
210 address: _wallet.address,
211 l2Quota,
212 });
213 } catch (e) {
214 console.warn(e);
216 // Try getting the details from L1
217 try {
218 const _contracts = need.contracts(contracts);
219 const details = await azimuth.azimuth.getPoint(_contracts, point);
220 return new Point({
221 value: pointNum,
222 details,
223 address: _wallet.address,
224 });
225 } catch (e) {
226 console.warn(e);
227 // Just return a placeholder Point
228 const details: L1Point = toL1Details();
229 return new Point({
230 value: pointNum,
231 details,
232 address: _wallet.address,
233 isPlaceholder: true,
234 });
235 }
236 }
237 },
238 [api, wallet, contracts]
239 );
241 const checkForUpdates = useCallback(
242 async ({ point, message, notify = true, field, l1Txn }: UpdateParams) => {
243 if (l1Txn) storePendingL1Txn(l1Txn);
245 const interval = setInterval(async () => {
246 const updatedPoint = await initPoint(point);
247 const changedField = !points[point]
248 ? 'newPoint'
249 : updatedPoint.getChangedField(points[point], field);
251 if (isDevelopment) {
252 console.log(`CHECKING FOR ${changedField} UPDATES:`, updatedPoint);
253 }
255 if (!updatedPoint.isPlaceholder && changedField) {
256 updatePoint(updatedPoint);
257 clearInterval(interval);
258 if (l1Txn) deletePendingL1Txn(l1Txn);
259 if (notify) {
260 showNotification(
261 `${message || getUpdatedPointMessage(updatedPoint, changedField)}`
262 );
263 }
264 }
265 }, TEN_SECONDS);
266 },
267 [points, initPoint, updatePoint, storePendingL1Txn, deletePendingL1Txn]
268 );
270 const spawnPoint = useCallback(
271 async (pointToSpawn: number, destinationAddress: string) => {
272 if (quotaReached()) {
273 return;
274 }
276 const _point = need.point(pointCursor);
277 const _contracts = contracts.getOrElse(null);
278 const _web3 = web3.getOrElse(null);
279 const _wallet = wallet.getOrElse(null);
280 const _authToken = authToken.getOrElse(null);
282 if (!_contracts || !_web3 || !_wallet || !_authToken) {
283 // not using need because we want a custom error
284 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
285 }
287 const proxy = point.getSpawnProxy();
289 if (proxy === undefined)
290 throw new Error("Error: Address doesn't match proxy");
292 const nonce = await api.getNonce({
293 ship: _point,
294 proxy,
295 });
297 await submitL2Transaction({
298 api,
299 address: destinationAddress,
300 wallet: _wallet,
301 ship: _point,
302 proxy,
303 nonce,
304 pointToSpawn,
305 type: 'spawn',
306 walletType,
307 web3: _web3,
308 connector,
309 });
310 },
311 [
312 api,
313 authToken,
314 connector,
315 contracts,
316 pointCursor,
317 point,
318 wallet,
319 walletType,
320 web3,
321 quotaReached,
322 ]
323 );
325 const generateInviteInfo = async (planet: number, _authToken: string) => {
326 const { ticket, inviteWallet } = await generateInviteWallet(
327 planet,
328 _authToken
329 );
331 return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
332 };
334 const getInvites = useCallback(async () => {
335 try {
336 setInvitesLoading(true);
337 const curPoint: number = Number(need.point(pointCursor));
338 const _authToken = authToken.getOrElse(null);
339 const _contracts = contracts.getOrElse(null);
341 if (_authToken && _contracts) {
342 const spawnedPoints = point.isL2Spawn
343 ? await api.getSpawned(curPoint)
344 : [];
346 const newPending = await api.getPendingByShip(curPoint);
347 setPendingTransactions(newPending);
348 const pendingSpawns = getPendingSpawns(newPending);
350 const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
351 _contracts,
352 curPoint
353 );
355 const outgoingPoints = pointList
356 .filter(
357 ({ value, isPlanet }) => isPlanet && availablePoints.includes(value)
358 )
359 .map(({ value }) => value);
361 const invitePlanets = spawnedPoints
362 .concat(
363 outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
364 )
365 .filter((p: number) => !pendingSpawns.has(p))
366 .sort();
368 setInvites(
369 curPoint,
370 invitePlanets.map(p => inviteTemplate(p))
371 );
373 const newInvites: Invite[] = [];
374 // Iterate over all of the stored invites, generating wallet info as necessary
375 const storedInvites = getStoredInvites(ls);
377 for (let i = 0; i < invitePlanets.length; i++) {
378 setInviteGeneratingNum(i + 1);
379 const planet = invitePlanets[i];
380 const storedInvite = storedInvites[planet];
381 const invite =
382 storedInvite || (await generateInviteInfo(planet, _authToken));
384 // TODO: check if the invite point's owner still matches the deterministic wallet address
385 const planetInfo = await api.getPoint(planet);
386 setStoredInvite(ls, invite);
388 if (
389 invite.owner.toLowerCase() ===
390 planetInfo.ownership?.owner?.address ||
391 planetInfo.ownership?.transferProxy?.address !== ETH_ZERO_ADDR
392 ) {
393 newInvites.push(invite);
394 updateInvite(curPoint, invite);
395 } else {
396 removeInvite(curPoint, invite.planet);
397 }
399 if (storedInvite) {
400 setStoredInvite(ls, invite);
401 }
402 }
403 setInvites(curPoint, newInvites);
404 setInvitesLoading(false);
405 return newInvites;
406 }
407 } catch (error) {
408 console.warn('ERROR GETTING INVITES', error);
409 }
410 }, [
411 api,
412 authToken,
413 contracts,
414 pointCursor,
415 ls,
416 point,
417 pointList,
418 removeInvite,
419 setInvites,
420 setInvitesLoading,
421 setInviteGeneratingNum,
422 setPendingTransactions,
423 updateInvite,
424 ]);
426 const generateInviteCodes = useCallback(
427 async (numInvites: number) => {
428 const _point = need.point(pointCursor);
429 const _contracts = contracts.getOrElse(null);
430 const _web3 = web3.getOrElse(null);
431 const _wallet = wallet.getOrElse(null);
432 const _authToken = authToken.getOrElse(null);
433 const _details = getDetails(_point);
435 if (!_contracts || !_web3 || !_wallet || !_authToken || !_details) {
436 // not using need because we want a custom error
437 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
438 }
440 // TODO: remove when UI disables management proxy from spawning invites
441 // if (Just.hasInstance(authMnemonic)) {
442 // throw new Error(
443 // "Auth key Error: A management mnemonic can't create invite codes"
444 // );
445 // }
446 let planets: UnspawnedPoints = await api.getUnspawned(_point);
448 const pendingTxs = await api.getPendingByShip(_point);
450 const pendingSpawns = getPendingSpawns(pendingTxs);
452 planets = planets.filter((point: number) => !pendingSpawns.has(point));
454 const invites: Invite[] = [];
456 const proxy = point.getSpawnProxy();
458 if (proxy === undefined)
459 throw new Error("Error: Address doesn't match proxy");
461 const nonce = await api.getNonce({
462 ship: _point,
463 proxy,
464 });
466 for (let i = 0; i < numInvites && planets[i]; i++) {
467 setInviteGeneratingNum(i + 1);
468 const planet = planets[i];
469 const nonceInc = i + nonce;
471 const { ticket, inviteWallet } = await generateInviteWallet(
472 planet,
473 _authToken
474 );
476 await submitL2Transaction({
477 api,
478 address: _wallet.address,
479 wallet: _wallet,
480 ship: _point,
481 proxy,
482 nonce: nonceInc,
483 pointToSpawn: planet,
484 type: 'spawn',
485 walletType,
486 web3: _web3,
487 connector,
488 });
490 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
491 inviteWallet,
492 1
493 );
494 await submitL2Transaction({
495 api,
496 wallet: _wallet,
497 ship: planet,
498 proxy: 'own',
499 nonce: 0,
500 networkSeed,
501 type: 'configureKeys',
502 walletType,
503 web3: _web3,
504 connector,
505 });
507 await registerProxyAddress(
508 api,
509 _wallet,
510 planet,
511 'own',
512 'manage',
513 1,
514 inviteWallet.management.keys.address,
515 walletType,
516 _web3,
517 connector
518 );
520 await submitL2Transaction({
521 api,
522 wallet: _wallet,
523 ship: planet,
524 proxy: 'own',
525 type: 'transferPoint',
526 nonce: 2,
527 address: inviteWallet.ownership.keys.address,
528 walletType,
529 web3: _web3,
530 connector,
531 });
533 invites.push({
534 hash: '',
535 ticket,
536 planet,
537 owner: inviteWallet.ownership.keys.address,
538 });
539 }
541 setStoredInvites(ls, invites);
542 getInvites();
543 showNotification(
544 `Your invite${invites.length > 1 ? 's have' : ' has'} been generated!`
545 );
546 },
547 [
548 api,
549 authToken,
550 connector,
551 contracts,
552 pointCursor,
553 point,
554 wallet,
555 walletType,
556 web3,
557 getDetails,
558 ls,
559 getInvites,
560 setInviteGeneratingNum,
561 ]
562 );
564 const getPoints = useCallback(
565 async (proxy: Proxy, address: EthAddress) => {
566 const points: Ship[] =
567 proxy === 'own'
568 ? await api.getOwnedPoints(address)
569 : proxy === 'manage'
570 ? await api.getManagerFor(address)
571 : proxy === 'vote'
572 ? await api.getVotingFor(address)
573 : proxy === 'transfer'
574 ? await api.getTransferringFor(address)
575 : proxy === 'spawn'
576 ? await api.getSpawningFor(address)
577 : [];
579 return points;
580 },
581 [api]
582 );
584 const getPendingTransactions = useCallback(async () => {
585 try {
586 if (!point.isDefault) {
587 const newPending = await api.getPendingByShip(point.value);
588 setPendingTransactions(newPending);
589 }
590 } catch (error) {
591 console.warn('ERROR GETTING PENDING', error);
592 }
593 }, [api, setPendingTransactions, point]);
595 interface ConfigureKeysParams {
596 breach: boolean;
597 customNetworkSeed?: string;
598 }
600 const configureNetworkKeys = useCallback(
601 async ({ breach, customNetworkSeed }: ConfigureKeysParams) => {
602 if (quotaReached()) {
603 return;
604 }
605 const _wallet = wallet.getOrElse(null);
606 const _web3 = web3.getOrElse(null);
607 const _details = getDetails(point.value);
608 if (!_wallet || !_web3 || !_details || point.isDefault) {
609 // not using need because we want a custom error
610 throw new Error('Internal Error: Missing Wallet/Details');
611 }
613 const proxy = point.getManagerProxy();
615 if (proxy === undefined)
616 throw new Error("Error: Address doesn't match proxy");
618 const nonce = await api.getNonce({
619 ship: point.value,
620 proxy,
621 });
623 const nextRevision = point.keyRevisionNumber + 1;
624 const networkSeed = customNetworkSeed
625 ? customNetworkSeed
626 : await attemptNetworkSeedDerivation({
627 urbitWallet,
628 wallet,
629 authMnemonic,
630 details: _details,
631 point: point.value,
632 authToken,
633 revision: nextRevision,
634 });
635 const txHash = await submitL2Transaction({
636 api,
637 wallet: _wallet,
638 ship: point.value,
639 proxy,
640 nonce,
641 networkSeed,
642 type: 'configureKeys',
643 walletType,
644 web3: _web3,
645 connector,
646 breach,
647 });
649 return api.getPendingTx(txHash);
650 },
651 [// eslint-disable-line
652 api,
653 authMnemonic,
654 authToken,
655 point,
656 urbitWallet,
657 wallet,
658 walletType,
659 web3,
660 getDetails,
661 quotaReached,
662 ]
663 );
665 const performL2Reticket = async ({
666 point,
667 to,
668 manager,
669 fromWallet,
670 toWallet,
671 }: ReticketParams) => {
672 const azimuthPoint = await api.getPoint(point);
673 const proxy = 'own';
674 const _web3 = web3.getOrElse(null);
675 let nonce = await api.getNonce({ ship: point, proxy });
677 let requests = [];
679 // 1. Update networking keys
680 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
681 const nextRevision = networkRevision + 1;
682 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
683 toWallet,
684 nextRevision
685 );
686 const configureKeysRequest = await submitL2Transaction({
687 api,
688 wallet: fromWallet,
689 ship: point,
690 proxy,
691 nonce,
692 networkSeed,
693 walletType,
694 type: 'configureKeys',
695 web3: _web3,
696 connector,
697 });
698 nonce = nonce + 1;
699 requests.push(configureKeysRequest);
701 // 2. Set Management Proxy
702 const registerMgmtRequest = await registerProxyAddress(
703 api,
704 fromWallet,
705 point,
706 proxy,
707 'manage',
708 nonce,
709 manager,
710 walletType,
711 _web3,
712 connector
713 );
714 nonce = nonce + 1;
715 requests.push(registerMgmtRequest);
717 // 3. Set Spawn Proxy
718 if (!isPlanet(Number(point))) {
719 const registerSpawnRequest = await registerProxyAddress(
720 api,
721 fromWallet,
722 point,
723 proxy,
724 'spawn',
725 nonce,
726 toWallet.spawn.keys.address,
727 walletType,
728 _web3,
729 connector
730 );
731 nonce = nonce + 1;
732 requests.push(registerSpawnRequest);
733 }
734 // 4. Transfer point
735 const transferTxRequest = await submitL2Transaction({
736 api,
737 wallet: fromWallet,
738 ship: point,
739 proxy: 'own',
740 type: 'transferPoint',
741 nonce,
742 address: to,
743 walletType,
744 web3: _web3,
745 connector,
746 });
747 requests.push(transferTxRequest);
749 const hashes = await Promise.all(requests);
750 return hashes;
751 };
753 const setProxyAddress = useCallback(
754 async (proxyType: Proxy, address: EthAddress) => {
755 if (quotaReached()) {
756 return;
757 }
759 const _wallet = wallet.getOrElse(null);
760 const _web3 = web3.getOrElse(null);
762 if (!_wallet) {
763 // not using need because we want a custom error
764 throw new Error('Internal Error: Missing Wallet');
765 }
767 if (point.isDefault) {
768 // not using need because we want a custom error
769 throw new Error('Internal Error: Invalid point');
770 }
772 const proxy = point.getAddressProxy(proxyType);
774 if (proxy === undefined)
775 throw new Error("Error: Address doesn't match expected proxy");
777 const nonce = await api.getNonce({ ship: point.value, proxy });
779 const txHash = await registerProxyAddress(
780 api,
781 _wallet,
782 point.value,
783 proxy,
784 proxyType,
785 nonce,
786 address,
787 walletType,
788 _web3,
789 connector
790 );
792 return api.getPendingTx(txHash);
793 },
794 [api, connector, point, wallet, web3, walletType, quotaReached]
795 );
797 const changeSponsorship = useCallback(
798 async (sponsee: Ship, type: 'adopt' | 'detach' | 'reject') => {
799 if (quotaReached()) {
800 return;
801 }
803 const _wallet = wallet.getOrElse(null);
804 const _web3 = web3.getOrElse(null);
805 const ship = point.value;
807 const proxy = point.getManagerProxy();
809 if (proxy === undefined)
810 throw new Error("Error: Address doesn't match expected proxy");
812 const nonce = await api.getNonce({ ship, proxy });
813 // const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
814 const txHash = await submitL2Transaction({
815 api,
816 wallet: _wallet,
817 ship,
818 sponsee,
819 proxy,
820 nonce,
821 address: _wallet.address,
822 type,
823 walletType,
824 web3: _web3,
825 connector,
826 });
828 return api.getPendingTx(txHash);
829 },
830 [api, point, wallet, walletType, web3, connector, quotaReached]
831 );
833 const transferPoint = useCallback(
834 async (address: EthAddress, reset?: boolean) => {
835 if (quotaReached()) {
836 return;
837 }
839 const _point = need.point(pointCursor);
840 const _wallet = wallet.getOrElse(null);
841 const _web3 = web3.getOrElse(null);
843 if (!_wallet) {
844 // not using need because we want a custom error
845 throw new Error('Internal Error: Missing Wallet');
846 }
848 const proxy = point.getTransferProxy();
850 if (proxy === undefined)
851 throw new Error("Error: Address doesn't match proxy");
853 const nonce = await api.getNonce({ ship: _point, proxy });
855 const txHash = await submitL2Transaction({
856 api,
857 wallet: _wallet,
858 ship: _point,
859 proxy,
860 nonce,
861 address,
862 type: 'transferPoint',
863 walletType,
864 web3: _web3,
865 connector,
866 reset,
867 });
869 return api.getPendingTx(txHash);
870 },
871 [api, connector, point, pointCursor, wallet, web3, walletType, quotaReached]
872 );
874 const sendL2Transaction = useCallback(
875 async (params: SendL2Params) => {
876 if (quotaReached()) {
877 return;
878 }
880 const _wallet = wallet.getOrElse(null);
881 const _web3 = web3.getOrElse(null);
883 if (!_wallet || point.isDefault) {
884 // not using need because we want a custom error
885 throw new Error('Internal Error: Missing Wallet or Point Info');
886 }
888 return submitL2Transaction({
889 ...params,
890 api,
891 wallet: _wallet,
892 walletType,
893 web3: _web3,
894 connector,
895 ship: point.value,
896 });
897 },
898 [api, connector, point, wallet, web3, walletType, quotaReached]
899 );
901 const changeSponsor = useCallback(
902 async (newSponsor: number) => {
903 if (point.isDefault) {
904 throw new Error('Internal Error: point is default');
905 }
907 const curPoint = point.value;
908 const _wallet = wallet.getOrElse(null);
910 if (!_wallet) {
911 // not using need because we want a custom error
912 throw new Error('Internal Error: Missing Wallet');
913 }
915 const proxy = point.getManagerProxy();
917 if (proxy === undefined)
918 throw new Error("Error: Address doesn't match proxy");
920 const nonce = await api.getNonce({ ship: curPoint, proxy });
922 return sendL2Transaction({
923 newSponsor,
924 nonce,
925 proxy,
926 type: 'escape',
927 ship: curPoint,
928 });
929 },
930 [api, point, sendL2Transaction, wallet]
931 );
933 const cancelEscape = useCallback(async () => {
934 if (point.isDefault) {
935 throw new Error('Internal Error: point is default');
936 }
938 const curPoint = point.value;
939 const _wallet = wallet.getOrElse(null);
941 if (!_wallet) {
942 // not using need because we want a custom error
943 throw new Error('Internal Error: Missing Wallet');
944 }
946 const proxy = point.getManagerProxy();
948 if (proxy === undefined)
949 throw new Error("Error: Address doesn't match proxy");
951 const nonce = await api.getNonce({ ship: curPoint, proxy });
953 return sendL2Transaction({
954 nonce,
955 proxy,
956 type: 'cancelEscape',
957 ship: curPoint,
958 });
959 }, [api, point, sendL2Transaction, wallet]);
961 const getPointsDetails = useCallback(
962 async (
963 ownedPoints: number[],
964 incomingPoints: number[],
965 managingPoints: number[],
966 votingPoints: number[],
967 spawningPoints: number[]
968 ) => {
969 try {
970 const allPoints = [
971 await Promise.all(ownedPoints.map(initPoint)),
972 await Promise.all(incomingPoints.map(initPoint)),
973 await Promise.all(managingPoints.map(initPoint)),
974 await Promise.all(votingPoints.map(initPoint)),
975 await Promise.all(spawningPoints.map(initPoint)),
976 ];
978 setPoints(
979 allPoints.reduce((acc: Points, cur: (Point | undefined)[]) => {
980 cur.forEach(p => {
981 if (p) {
982 acc[p.value] = p;
983 }
984 });
985 return acc;
986 }, {})
987 );
988 } catch (e) {
989 console.warn(e);
990 }
991 },
992 [setPoints, initPoint]
993 );
995 // On load, get initial config
996 useEffect(() => {
997 if (config) {
998 return;
999 }
1001 fetchConfig();
1002 }, [config, fetchConfig]);
1004 useEffect(() => {
1005 const time = ONE_SECOND;
1007 const interval = setInterval(() => {
1008 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
1009 setNextRoll(nextRoll);
1011 if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
1012 api.getRollerConfig().then(response => {
1013 setNextBatchTime(response.nextBatch);
1014 });
1016 getPendingTransactions();
1018 setTimeout(() => {
1019 if (!point.isDefault) {
1020 getInvites(); // This will also get pending txns
1022 }, TEN_SECONDS); // Should this be more like a minute?
1024 }, time);
1026 return () => clearInterval(interval);
1027 }, [point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
1029 return {
1030 api,
1031 changeSponsorship,
1032 changeSponsor,
1033 cancelEscape,
1034 checkForUpdates,
1035 config,
1036 configureNetworkKeys,
1037 generateInviteCodes,
1038 getInvites,
1039 getPoints,
1040 getPointsDetails,
1041 getPendingTransactions,
1042 ls,
1043 performL2Reticket,
1044 setProxyAddress,
1045 spawnPoint,
1046 transferPoint,
1047 };