on cb07dcc help sign in
bridge/src/lib/useRoller.ts
1078 lines 28 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 {
35 ETH_ZERO_ADDR,
36 POINT_DOMINIONS,
37 ROLLER_HOSTS,
38 TEN_SECONDS,
39} from './constants';
41import {
42 Config,
43 Ship,
44 Proxy,
45 RollerRPCAPI,
46 Options,
47 EthAddress,
48 UnspawnedPoints,
49} from '@urbit/roller-api';
51import Point, { PointField, Points } from './types/Point';
52import { useTimerStore } from 'store/timerStore';
53import { ReticketParams, SendL2Params } from './types/L2Transaction';
54import { L1Point } from './types/L1Point';
55import { ddmmmYYYY } from './utils/date';
56import { showNotification } from './utils/notifications';
57import { useWalletConnect } from './useWalletConnect';
58import { PendingL1Txn } from './types/PendingL1Transaction';
60const ONE_SECOND = 1000;
62interface UpdateParams {
63 point: number;
64 message?: string;
65 notify?: boolean;
66 field?: PointField;
67 l1Txn?: PendingL1Txn;
68 intervalTime?: number;
71const inviteTemplate = (
72 planet: number,
73 ticket = '',
74 owner = '',
75 hash = ''
76): Invite => ({
77 planet,
78 ticket,
79 owner,
80 hash,
81});
83export default function useRoller() {
84 const {
85 wallet,
86 authToken,
87 authMnemonic,
88 urbitWallet,
89 walletType,
90 }: any = useWallet();
91 const { connector } = useWalletConnect();
92 const { pointCursor }: any = usePointCursor();
93 const { web3, contracts }: any = useNetwork();
94 const allPoints: any = usePointCache();
95 const getDetails = allPoints?.getDetails;
97 const { setNextRoll } = useTimerStore();
99 const {
100 nextBatchTime,
101 point,
102 points,
103 pointList,
104 nextQuotaTime,
105 removeInvite,
106 setNextBatchTime,
107 setNextQuotaTime,
108 setPendingTransactions,
109 setInvites,
110 setInviteGeneratingNum,
111 setInvitesLoading,
112 setModalText,
113 setPoints,
114 updateInvite,
115 updatePoint,
116 storePendingL1Txn,
117 deletePendingL1Txn,
118 } = useRollerStore();
119 const [config, setConfig] = useState<Config | null>(null);
121 const options: Options = useMemo(() => {
122 const type = isMainnet || isRopsten ? 'https' : 'http';
123 const host = isMainnet
124 ? ROLLER_HOSTS.MAINNET
125 : isRopsten
126 ? ROLLER_HOSTS.ROPSTEN
127 : ROLLER_HOSTS.LOCAL;
128 const port = isMainnet || isRopsten ? 443 : 8080;
129 const path = '/v1/roller';
131 return {
132 transport: {
133 type,
134 host,
135 port,
136 path,
137 },
138 };
139 }, []);
141 const api = useMemo(() => {
142 return new RollerRPCAPI(options);
143 }, [options]);
145 const ls = useMemo(
146 () =>
147 new SecureLS({
148 isCompression: false,
149 encryptionSecret: authToken.getOrElse('default'),
150 }),
151 [authToken]
152 );
154 const getNextQuotaTime = useCallback(
155 (nextSlice: number) => {
156 setTimeout(() => {
157 api
158 .whenNextSlice()
159 .then(next => {
160 setNextQuotaTime(next);
161 getNextQuotaTime(next);
162 })
163 .catch(err => console.warn(err));
164 }, nextSlice - new Date().getTime());
165 },
166 [api, setNextQuotaTime]
167 );
169 const fetchConfig = useCallback(async () => {
170 getNextQuotaTime(1);
171 api
172 .getRollerConfig()
173 .then(config => {
174 setConfig(config);
175 setNextBatchTime(config.nextBatch);
176 })
177 .catch(err => {
178 // TODO: more elegant error handling
179 console.warn(
180 '[fetchConfig:failed] check the roller connection?\n',
181 err
182 );
183 });
184 }, [api, getNextQuotaTime, setNextBatchTime]);
186 const quotaReached = useCallback(() => {
187 if (point.l2Quota <= 0) {
188 setModalText(
189 `You have reached your weekly L2 transaction limit. You will get another
190 ${config?.rollerQuota} transactions on ${ddmmmYYYY(nextQuotaTime)}.`
191 );
192 }
194 return point.l2Quota <= 0;
195 }, [config, nextQuotaTime, point, setModalText]);
197 const initPoint = useCallback(
198 async (point: string | number) => {
199 const _wallet = wallet.getOrElse(null);
200 const _contracts = contracts.getOrElse(null);
202 if (!_wallet || !_contracts) {
203 return EMPTY_POINT;
204 }
206 const pointNum = Number(point);
207 try {
208 const rawDetails = await api.getPoint(pointNum);
209 const l2Quota = isL2Spawn(rawDetails?.dominion)
210 ? await api.getRemainingQuota(pointNum)
211 : 0;
213 const details = isL2Spawn(rawDetails?.dominion)
214 ? toL1Details(rawDetails)
215 : await azimuth.azimuth.getPoint(_contracts, point);
217 return new Point({
218 value: pointNum,
219 details,
220 address: _wallet.address,
221 l2Quota,
222 });
223 } catch (e) {
224 console.warn(e);
226 // Try getting the details from L1
227 try {
228 const _contracts = need.contracts(contracts);
229 const details = await azimuth.azimuth.getPoint(_contracts, point);
230 return new Point({
231 value: pointNum,
232 details,
233 address: _wallet.address,
234 });
235 } catch (e) {
236 console.warn(e);
237 // Just return a placeholder Point
238 const details: L1Point = toL1Details();
239 return new Point({
240 value: pointNum,
241 details,
242 address: _wallet.address,
243 isPlaceholder: true,
244 });
245 }
246 }
247 },
248 [api, wallet, contracts]
249 );
251 const getAndUpdatePoint = useCallback(
252 async (point: number) => {
253 const updatedPoint = await initPoint(point);
254 updatePoint(updatedPoint);
255 },
256 [initPoint, updatePoint]
257 );
259 const checkForUpdates = useCallback(
260 async ({
261 point,
262 message,
263 notify = true,
264 field,
265 l1Txn,
266 intervalTime = TEN_SECONDS,
267 }: UpdateParams) => {
268 if (l1Txn) storePendingL1Txn(l1Txn);
270 const interval = setInterval(async () => {
271 const updatedPoint = await initPoint(point);
272 const changedField = !points[point]
273 ? 'newPoint'
274 : updatedPoint.getChangedField(points[point], field);
276 if (isDevelopment) {
277 console.log(`CHECKING FOR ${changedField} UPDATES:`, updatedPoint);
278 }
280 if (!updatedPoint.isPlaceholder && changedField) {
281 updatePoint(updatedPoint);
282 clearInterval(interval);
283 if (l1Txn) deletePendingL1Txn(l1Txn);
284 if (notify) {
285 showNotification(
286 `${message || getUpdatedPointMessage(updatedPoint, changedField)}`
287 );
288 }
289 }
290 }, intervalTime);
291 },
292 [points, initPoint, updatePoint, storePendingL1Txn, deletePendingL1Txn]
293 );
295 const spawnPoint = useCallback(
296 async (pointToSpawn: number, destinationAddress: string) => {
297 if (quotaReached()) {
298 return;
299 }
301 const _point = need.point(pointCursor);
302 const _contracts = contracts.getOrElse(null);
303 const _web3 = web3.getOrElse(null);
304 const _wallet = wallet.getOrElse(null);
305 const _authToken = authToken.getOrElse(null);
307 if (!_contracts || !_web3 || !_wallet || !_authToken) {
308 // not using need because we want a custom error
309 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
310 }
312 const proxy = point.getSpawnProxy();
314 if (proxy === undefined)
315 throw new Error("Error: Address doesn't match proxy");
317 const nonce = await api.getNonce({
318 ship: _point,
319 proxy,
320 });
322 await submitL2Transaction({
323 api,
324 address: destinationAddress,
325 wallet: _wallet,
326 ship: _point,
327 proxy,
328 nonce,
329 pointToSpawn,
330 type: 'spawn',
331 walletType,
332 web3: _web3,
333 connector,
334 });
335 },
336 [
337 api,
338 authToken,
339 connector,
340 contracts,
341 pointCursor,
342 point,
343 wallet,
344 walletType,
345 web3,
346 quotaReached,
347 ]
348 );
350 const generateInviteInfo = async (planet: number, _authToken: string) => {
351 const { ticket, inviteWallet } = await generateInviteWallet(
352 planet,
353 _authToken
354 );
356 return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
357 };
359 const getInvites = useCallback(async () => {
360 try {
361 setInvitesLoading(true);
362 const curPoint: number = Number(need.point(pointCursor));
363 const _authToken = authToken.getOrElse(null);
364 const _contracts = contracts.getOrElse(null);
366 if (_authToken && _contracts) {
367 const spawnedPoints = point.isL2Spawn
368 ? await api.getSpawned(curPoint)
369 : [];
371 const newPending = await api.getPendingByShip(curPoint);
372 setPendingTransactions(newPending);
373 const pendingSpawns = getPendingSpawns(newPending);
375 const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
376 _contracts,
377 curPoint
378 );
380 const outgoingPoints = pointList
381 .filter(
382 ({ value, isPlanet }) => isPlanet && availablePoints.includes(value)
383 )
384 .map(({ value }) => value);
386 const invitePlanets = spawnedPoints
387 .concat(
388 outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
389 )
390 .filter((p: number) => !pendingSpawns.has(p))
391 .sort();
393 setInvites(
394 curPoint,
395 invitePlanets.map(p => inviteTemplate(p))
396 );
398 const newInvites: Invite[] = [];
399 // Iterate over all of the stored invites, generating wallet info as necessary
400 const storedInvites = getStoredInvites(ls);
402 for (let i = 0; i < invitePlanets.length; i++) {
403 setInviteGeneratingNum(i + 1);
404 const planet = invitePlanets[i];
405 const storedInvite = storedInvites[planet];
406 const invite =
407 storedInvite || (await generateInviteInfo(planet, _authToken));
409 // TODO: check if the invite point's owner still matches the deterministic wallet address
410 const planetInfo = await api.getPoint(planet);
411 setStoredInvite(ls, invite);
413 if (
414 invite.owner.toLowerCase() ===
415 planetInfo.ownership?.owner?.address &&
416 planetInfo.dominion === POINT_DOMINIONS.L2
417 ) {
418 newInvites.push(invite);
419 updateInvite(curPoint, invite);
420 } else {
421 removeInvite(curPoint, invite.planet);
422 }
424 if (storedInvite) {
425 setStoredInvite(ls, invite);
426 }
427 }
428 setInvites(curPoint, newInvites);
429 setInvitesLoading(false);
430 return newInvites;
431 }
432 } catch (error) {
433 console.warn('ERROR GETTING INVITES', error);
434 }
435 }, [
436 api,
437 authToken,
438 contracts,
439 pointCursor,
440 ls,
441 point,
442 pointList,
443 removeInvite,
444 setInvites,
445 setInvitesLoading,
446 setInviteGeneratingNum,
447 setPendingTransactions,
448 updateInvite,
449 ]);
451 const generateInviteCodes = useCallback(
452 async (numInvites: number) => {
453 const _point = need.point(pointCursor);
454 const _contracts = contracts.getOrElse(null);
455 const _web3 = web3.getOrElse(null);
456 const _wallet = wallet.getOrElse(null);
457 const _authToken = authToken.getOrElse(null);
458 const _details = getDetails(_point);
460 if (!_contracts || !_web3 || !_wallet || !_authToken || !_details) {
461 // not using need because we want a custom error
462 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
463 }
465 // TODO: remove when UI disables management proxy from spawning invites
466 // if (Just.hasInstance(authMnemonic)) {
467 // throw new Error(
468 // "Auth key Error: A management mnemonic can't create invite codes"
469 // );
470 // }
471 let planets: UnspawnedPoints = await api.getUnspawned(_point);
473 const pendingTxs = await api.getPendingByShip(_point);
475 const pendingSpawns = getPendingSpawns(pendingTxs);
477 planets = planets.filter((point: number) => !pendingSpawns.has(point));
479 const invites: Invite[] = [];
481 const proxy = point.getSpawnProxy();
483 if (proxy === undefined)
484 throw new Error("Error: Address doesn't match proxy");
486 const nonce = await api.getNonce({
487 ship: _point,
488 proxy,
489 });
491 for (let i = 0; i < numInvites && planets[i]; i++) {
492 setInviteGeneratingNum(i + 1);
493 const planet = planets[i];
494 const nonceInc = i + nonce;
496 const { ticket, inviteWallet } = await generateInviteWallet(
497 planet,
498 _authToken
499 );
501 await submitL2Transaction({
502 api,
503 address: _wallet.address,
504 wallet: _wallet,
505 ship: _point,
506 proxy,
507 nonce: nonceInc,
508 pointToSpawn: planet,
509 type: 'spawn',
510 walletType,
511 web3: _web3,
512 connector,
513 });
515 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
516 inviteWallet,
517 1
518 );
519 await submitL2Transaction({
520 api,
521 wallet: _wallet,
522 ship: planet,
523 proxy: 'own',
524 nonce: 0,
525 networkSeed,
526 type: 'configureKeys',
527 walletType,
528 web3: _web3,
529 connector,
530 });
532 await registerProxyAddress(
533 api,
534 _wallet,
535 planet,
536 'own',
537 'manage',
538 1,
539 inviteWallet.management.keys.address,
540 walletType,
541 _web3,
542 connector
543 );
545 await submitL2Transaction({
546 api,
547 wallet: _wallet,
548 ship: planet,
549 proxy: 'own',
550 type: 'transferPoint',
551 nonce: 2,
552 address: inviteWallet.ownership.keys.address,
553 walletType,
554 web3: _web3,
555 connector,
556 });
558 invites.push({
559 hash: '',
560 ticket,
561 planet,
562 owner: inviteWallet.ownership.keys.address,
563 });
564 }
566 setStoredInvites(ls, invites);
567 getInvites();
568 showNotification(
569 `Your invite${invites.length > 1 ? 's have' : ' has'} been generated!`
570 );
571 },
572 [
573 api,
574 authToken,
575 connector,
576 contracts,
577 pointCursor,
578 point,
579 wallet,
580 walletType,
581 web3,
582 getDetails,
583 ls,
584 getInvites,
585 setInviteGeneratingNum,
586 ]
587 );
589 const getPoints = useCallback(
590 async (proxy: Proxy, address: EthAddress) => {
591 const points: Ship[] =
592 proxy === 'own'
593 ? await api.getOwnedPoints(address)
594 : proxy === 'manage'
595 ? await api.getManagerFor(address)
596 : proxy === 'vote'
597 ? await api.getVotingFor(address)
598 : proxy === 'transfer'
599 ? await api.getTransferringFor(address)
600 : proxy === 'spawn'
601 ? await api.getSpawningFor(address)
602 : [];
604 return points;
605 },
606 [api]
607 );
609 const getPendingTransactions = useCallback(async () => {
610 try {
611 if (!point.isDefault) {
612 const newPending = await api.getPendingByShip(point.value);
613 setPendingTransactions(newPending);
614 }
615 } catch (error) {
616 console.warn('ERROR GETTING PENDING', error);
617 }
618 }, [api, setPendingTransactions, point]);
620 interface ConfigureKeysParams {
621 breach: boolean;
622 customNetworkSeed?: string;
623 }
625 const configureNetworkKeys = useCallback(
626 async ({ breach, customNetworkSeed }: ConfigureKeysParams) => {
627 if (quotaReached()) {
628 return;
629 }
630 const _wallet = wallet.getOrElse(null);
631 const _web3 = web3.getOrElse(null);
632 if (!_wallet || !_web3 || point.isDefault) {
633 // not using need because we want a custom error
634 throw new Error('Internal Error: Missing Wallet/Details');
635 }
637 const proxy = point.getManagerProxy();
639 if (proxy === undefined)
640 throw new Error("Error: Address doesn't match proxy");
642 const nonce = await api.getNonce({
643 ship: point.value,
644 proxy,
645 });
647 const nextRevision = point.keyRevisionNumber + 1;
648 const networkSeed = customNetworkSeed
649 ? customNetworkSeed
650 : await attemptNetworkSeedDerivation({
651 urbitWallet,
652 wallet,
653 authMnemonic,
654 details: point,
655 authToken,
656 point: point.value,
657 revision: nextRevision,
658 });
659 const txHash = await submitL2Transaction({
660 api,
661 wallet: _wallet,
662 ship: point.value,
663 proxy,
664 nonce,
665 networkSeed,
666 type: 'configureKeys',
667 walletType,
668 web3: _web3,
669 connector,
670 breach,
671 });
673 return api.getPendingTx(txHash);
674 },
675 [// eslint-disable-line
676 api,
677 authMnemonic,
678 authToken,
679 point,
680 urbitWallet,
681 wallet,
682 walletType,
683 web3,
684 getDetails,
685 quotaReached,
686 ]
687 );
689 const performL2Reticket = async ({
690 point,
691 to,
692 manager,
693 fromWallet,
694 toWallet,
695 }: ReticketParams) => {
696 const azimuthPoint = await api.getPoint(point);
697 const proxy = 'own';
698 const _web3 = web3.getOrElse(null);
699 let nonce = await api.getNonce({ ship: point, proxy });
701 let requests = [];
703 // 1. Update networking keys
704 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
705 const nextRevision = networkRevision + 1;
706 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
707 toWallet,
708 nextRevision
709 );
710 const configureKeysRequest = await submitL2Transaction({
711 api,
712 wallet: fromWallet,
713 ship: point,
714 proxy,
715 nonce,
716 networkSeed,
717 walletType,
718 type: 'configureKeys',
719 web3: _web3,
720 connector,
721 });
722 nonce = nonce + 1;
723 requests.push(configureKeysRequest);
725 // 2. Set Management Proxy
726 const registerMgmtRequest = await registerProxyAddress(
727 api,
728 fromWallet,
729 point,
730 proxy,
731 'manage',
732 nonce,
733 manager,
734 walletType,
735 _web3,
736 connector
737 );
738 nonce = nonce + 1;
739 requests.push(registerMgmtRequest);
741 // 3. Set Spawn Proxy
742 if (!isPlanet(Number(point))) {
743 const registerSpawnRequest = await registerProxyAddress(
744 api,
745 fromWallet,
746 point,
747 proxy,
748 'spawn',
749 nonce,
750 toWallet?.spawn?.keys?.address || to,
751 walletType,
752 _web3,
753 connector
754 );
755 nonce = nonce + 1;
756 requests.push(registerSpawnRequest);
757 }
758 // 4. Transfer point
759 const transferTxRequest = await submitL2Transaction({
760 api,
761 wallet: fromWallet,
762 ship: point,
763 proxy: 'own',
764 type: 'transferPoint',
765 nonce,
766 address: to,
767 walletType,
768 web3: _web3,
769 connector,
770 });
771 requests.push(transferTxRequest);
773 const hashes = await Promise.all(requests);
774 return hashes;
775 };
777 const setProxyAddress = useCallback(
778 async (proxyType: Proxy, address: EthAddress) => {
779 if (quotaReached()) {
780 return;
781 }
783 const _wallet = wallet.getOrElse(null);
784 const _web3 = web3.getOrElse(null);
786 if (!_wallet) {
787 // not using need because we want a custom error
788 throw new Error('Internal Error: Missing Wallet');
789 }
791 if (point.isDefault) {
792 // not using need because we want a custom error
793 throw new Error('Internal Error: Invalid point');
794 }
796 const proxy = point.getAddressProxy(proxyType);
798 if (proxy === undefined)
799 throw new Error("Error: Address doesn't match expected proxy");
801 const nonce = await api.getNonce({ ship: point.value, proxy });
803 const txHash = await registerProxyAddress(
804 api,
805 _wallet,
806 point.value,
807 proxy,
808 proxyType,
809 nonce,
810 address,
811 walletType,
812 _web3,
813 connector
814 );
816 return api.getPendingTx(txHash);
817 },
818 [api, connector, point, wallet, web3, walletType, quotaReached]
819 );
821 const changeSponsorship = useCallback(
822 async (
823 sponsee: Ship,
824 type: 'adopt' | 'detach' | 'reject',
825 skipQuotaCheck = false
826 ) => {
827 if (!skipQuotaCheck && quotaReached()) {
828 return;
829 }
831 const _wallet = wallet.getOrElse(null);
832 const _web3 = web3.getOrElse(null);
833 const ship = point.value;
835 const proxy = point.getManagerProxy();
837 if (proxy === undefined)
838 throw new Error("Error: Address doesn't match expected proxy");
840 const nonce = await api.getNonce({ ship, proxy });
841 // const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
842 const txHash = await submitL2Transaction({
843 api,
844 wallet: _wallet,
845 ship,
846 sponsee,
847 proxy,
848 nonce,
849 address: _wallet.address,
850 type,
851 walletType,
852 web3: _web3,
853 connector,
854 });
856 return api.getPendingTx(txHash);
857 },
858 [api, point, wallet, walletType, web3, connector, quotaReached]
859 );
861 const transferPoint = useCallback(
862 async (address: EthAddress, reset?: boolean) => {
863 if (quotaReached()) {
864 return;
865 }
867 const _point = need.point(pointCursor);
868 const _wallet = wallet.getOrElse(null);
869 const _web3 = web3.getOrElse(null);
871 if (!_wallet) {
872 // not using need because we want a custom error
873 throw new Error('Internal Error: Missing Wallet');
874 }
876 const proxy = point.getTransferProxy();
878 if (proxy === undefined)
879 throw new Error("Error: Address doesn't match proxy");
881 const nonce = await api.getNonce({ ship: _point, proxy });
883 const txHash = await submitL2Transaction({
884 api,
885 wallet: _wallet,
886 ship: _point,
887 proxy,
888 nonce,
889 address,
890 type: 'transferPoint',
891 walletType,
892 web3: _web3,
893 connector,
894 reset,
895 });
897 return api.getPendingTx(txHash);
898 },
899 [api, connector, point, pointCursor, wallet, web3, walletType, quotaReached]
900 );
902 const sendL2Transaction = useCallback(
903 async (params: SendL2Params) => {
904 if (quotaReached()) {
905 return;
906 }
908 const _wallet = wallet.getOrElse(null);
909 const _web3 = web3.getOrElse(null);
911 if (!_wallet || point.isDefault) {
912 // not using need because we want a custom error
913 throw new Error('Internal Error: Missing Wallet or Point Info');
914 }
916 return submitL2Transaction({
917 ...params,
918 api,
919 wallet: _wallet,
920 walletType,
921 web3: _web3,
922 connector,
923 ship: point.value,
924 });
925 },
926 [api, connector, point, wallet, web3, walletType, quotaReached]
927 );
929 const changeSponsor = useCallback(
930 async (newSponsor: number) => {
931 if (point.isDefault) {
932 throw new Error('Internal Error: point is default');
933 }
935 const curPoint = point.value;
936 const _wallet = wallet.getOrElse(null);
938 if (!_wallet) {
939 // not using need because we want a custom error
940 throw new Error('Internal Error: Missing Wallet');
941 }
943 const proxy = point.getManagerProxy();
945 if (proxy === undefined)
946 throw new Error("Error: Address doesn't match proxy");
948 const nonce = await api.getNonce({ ship: curPoint, proxy });
950 return sendL2Transaction({
951 newSponsor,
952 nonce,
953 proxy,
954 type: 'escape',
955 ship: curPoint,
956 });
957 },
958 [api, point, sendL2Transaction, wallet]
959 );
961 const cancelEscape = useCallback(async () => {
962 if (point.isDefault) {
963 throw new Error('Internal Error: point is default');
964 }
966 const curPoint = point.value;
967 const _wallet = wallet.getOrElse(null);
969 if (!_wallet) {
970 // not using need because we want a custom error
971 throw new Error('Internal Error: Missing Wallet');
972 }
974 const proxy = point.getManagerProxy();
976 if (proxy === undefined)
977 throw new Error("Error: Address doesn't match proxy");
979 const nonce = await api.getNonce({ ship: curPoint, proxy });
981 return sendL2Transaction({
982 nonce,
983 proxy,
984 type: 'cancelEscape',
985 ship: curPoint,
986 });
987 }, [api, point, sendL2Transaction, wallet]);
989 const getPointsDetails = useCallback(
990 async (
991 ownedPoints: number[],
992 incomingPoints: number[],
993 managingPoints: number[],
994 votingPoints: number[],
995 spawningPoints: number[]
996 ) => {
997 try {
998 const allPoints = [
999 await Promise.all(ownedPoints.map(initPoint)),
1000 await Promise.all(incomingPoints.map(initPoint)),
1001 await Promise.all(managingPoints.map(initPoint)),
1002 await Promise.all(votingPoints.map(initPoint)),
1003 await Promise.all(spawningPoints.map(initPoint)),
1004 ];
1006 setPoints(
1007 allPoints.reduce((acc: Points, cur: (Point | undefined)[]) => {
1008 cur.forEach(p => {
1009 if (p) {
1010 acc[p.value] = p;
1012 });
1013 return acc;
1014 }, {})
1015 );
1016 } catch (e) {
1017 console.warn(e);
1019 },
1020 [setPoints, initPoint]
1021 );
1023 // On load, get initial config
1024 useEffect(() => {
1025 if (config) {
1026 return;
1029 fetchConfig();
1030 }, [config, fetchConfig]);
1032 useEffect(() => {
1033 const time = ONE_SECOND;
1035 const interval = setInterval(() => {
1036 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
1037 setNextRoll(nextRoll);
1039 if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
1040 api.getRollerConfig().then(response => {
1041 setNextBatchTime(response.nextBatch);
1042 });
1044 getPendingTransactions();
1046 setTimeout(() => {
1047 if (!point.isDefault) {
1048 getInvites(); // This will also get pending txns
1050 }, TEN_SECONDS); // Should this be more like a minute?
1052 }, time);
1054 return () => clearInterval(interval);
1055 }, [point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
1057 return {
1058 api,
1059 changeSponsorship,
1060 changeSponsor,
1061 cancelEscape,
1062 checkForUpdates,
1063 config,
1064 configureNetworkKeys,
1065 generateInviteCodes,
1066 getAndUpdatePoint,
1067 getInvites,
1068 getPoints,
1069 getPointsDetails,
1070 getPendingTransactions,
1071 initPoint,
1072 ls,
1073 performL2Reticket,
1074 setProxyAddress,
1075 spawnPoint,
1076 transferPoint,
1077 };