on bb22285 help sign in
bridge/src/lib/useRoller.ts
1039 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, 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';
54const ONE_SECOND = 1000;
56const inviteTemplate = (
57 planet: number,
58 ticket = '',
59 owner = '',
60 hash = ''
61): Invite => ({
62 planet,
63 ticket,
64 owner,
65 hash,
66});
68export default function useRoller() {
69 const {
70 wallet,
71 authToken,
72 authMnemonic,
73 urbitWallet,
74 walletType,
75 }: any = useWallet();
76 const { connector } = useWalletConnect();
77 const { pointCursor }: any = usePointCursor();
78 const { web3, contracts }: any = useNetwork();
79 const allPoints: any = usePointCache();
80 const getDetails = allPoints?.getDetails;
82 const { setNextRoll } = useTimerStore();
84 const {
85 nextBatchTime,
86 point,
87 points,
88 pointList,
89 nextQuotaTime,
90 removeInvite,
91 setNextBatchTime,
92 setNextQuotaTime,
93 setPendingTransactions,
94 setInvites,
95 setInviteGeneratingNum,
96 setInvitesLoading,
97 setModalText,
98 setPoints,
99 updateInvite,
100 updatePoint,
101 } = useRollerStore();
102 const [config, setConfig] = useState<Config | null>(null);
104 const options: Options = useMemo(() => {
105 const type = isRopsten || !isDevelopment ? 'https' : 'http';
106 const host = isRopsten
107 ? ROLLER_HOSTS.ROPSTEN
108 : isDevelopment
109 ? ROLLER_HOSTS.LOCAL
110 : ROLLER_HOSTS.MAINNET;
111 const port = isDevelopment && !isRopsten ? 8080 : 443;
112 const path = '/v1/roller';
114 return {
115 transport: {
116 type,
117 host,
118 port,
119 path,
120 },
121 };
122 }, []);
124 const api = useMemo(() => {
125 return new RollerRPCAPI(options);
126 }, [options]);
128 const ls = useMemo(
129 () =>
130 new SecureLS({
131 isCompression: false,
132 encryptionSecret: authToken.getOrElse('default'),
133 }),
134 [authToken]
135 );
137 const getNextQuotaTime = useCallback(
138 (nextSlice: number) => {
139 setTimeout(() => {
140 api
141 .whenNextSlice()
142 .then(next => {
143 setNextQuotaTime(next);
144 getNextQuotaTime(next);
145 })
146 .catch(err => console.warn(err));
147 }, nextSlice - new Date().getTime());
148 },
149 [api, setNextQuotaTime]
150 );
152 const fetchConfig = useCallback(async () => {
153 getNextQuotaTime(1);
154 api
155 .getRollerConfig()
156 .then(config => {
157 setConfig(config);
158 setNextBatchTime(config.nextBatch);
159 })
160 .catch(err => {
161 // TODO: more elegant error handling
162 console.warn(
163 '[fetchConfig:failed] check the roller connection?\n',
164 err
165 );
166 });
167 }, [api, getNextQuotaTime, setNextBatchTime]);
169 const quotaReached = useCallback(() => {
170 if (point.l2Quota <= 0) {
171 setModalText(
172 `You have reached your weekly L2 transaction limit. You will get another
173 ${config?.rollerQuota} transactions on ${ddmmmYYYY(nextQuotaTime)}.`
174 );
175 }
177 return point.l2Quota <= 0;
178 }, [config, nextQuotaTime, point, setModalText]);
180 const initPoint = useCallback(
181 async (point: string | number) => {
182 const _wallet = wallet.getOrElse(null);
184 if (!_wallet) {
185 return EMPTY_POINT;
186 }
188 const pointNum = Number(point);
189 try {
190 const rawDetails = await api.getPoint(pointNum);
191 const l2Quota = isL2Spawn(rawDetails?.dominion)
192 ? await api.getRemainingQuota(pointNum)
193 : 0;
194 const details = toL1Details(rawDetails);
196 return new Point({
197 value: pointNum,
198 details,
199 address: _wallet.address,
200 l2Quota,
201 });
202 } catch (e) {
203 console.warn(e);
205 // Try getting the details from L1
206 try {
207 const _contracts = need.contracts(contracts);
208 const details = await azimuth.azimuth.getPoint(_contracts, point);
209 return new Point({
210 value: pointNum,
211 details,
212 address: _wallet.address,
213 });
214 } catch (e) {
215 console.warn(e);
216 // Just return a placeholder Point
217 const details: L1Point = toL1Details();
218 return new Point({
219 value: pointNum,
220 details,
221 address: _wallet.address,
222 isPlaceholder: true,
223 });
224 }
225 }
226 },
227 [api, wallet, contracts]
228 );
230 const checkForUpdates = useCallback(
231 async (
232 point: number,
233 message?: string,
234 notify = true,
235 field?: PointField
236 ) => {
237 const interval = setInterval(async () => {
238 const updatedPoint = await initPoint(point);
239 const changedField = !points[point]
240 ? 'newPoint'
241 : updatedPoint.getChangedField(points[point], field);
243 if (isDevelopment) {
244 console.log(`CHECKING FOR ${changedField} UPDATES:`, updatedPoint);
245 }
247 if (!updatedPoint.isPlaceholder && changedField) {
248 updatePoint(updatedPoint);
249 clearInterval(interval);
250 if (notify) {
251 showNotification(
252 `${message || getUpdatedPointMessage(updatedPoint, changedField)}`
253 );
254 }
255 }
256 }, TEN_SECONDS);
257 },
258 [points, initPoint, updatePoint]
259 );
261 const spawnPoint = useCallback(
262 async (pointToSpawn: number, destinationAddress: string) => {
263 if (quotaReached()) {
264 return;
265 }
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);
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 }
278 const proxy = point.getSpawnProxy();
280 if (proxy === undefined)
281 throw new Error("Error: Address doesn't match proxy");
283 const nonce = await api.getNonce({
284 ship: _point,
285 proxy,
286 });
288 await submitL2Transaction({
289 api,
290 address: destinationAddress,
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 );
316 const generateInviteInfo = async (planet: number, _authToken: string) => {
317 const { ticket, inviteWallet } = await generateInviteWallet(
318 planet,
319 _authToken
320 );
322 return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
323 };
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);
332 if (_authToken && _contracts) {
333 const spawnedPoints = point.isL2Spawn
334 ? await api.getSpawned(curPoint)
335 : [];
337 const newPending = await api.getPendingByShip(curPoint);
338 setPendingTransactions(newPending);
339 const pendingSpawns = getPendingSpawns(newPending);
341 const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
342 _contracts,
343 curPoint
344 );
346 const outgoingPoints = pointList
347 .filter(
348 ({ value, isPlanet }) => isPlanet && availablePoints.includes(value)
349 )
350 .map(({ value }) => value);
352 const invitePlanets = spawnedPoints
353 .concat(
354 outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
355 )
356 .filter((p: number) => !pendingSpawns.has(p))
357 .sort();
359 setInvites(
360 curPoint,
361 invitePlanets.map(p => inviteTemplate(p))
362 );
364 const newInvites: Invite[] = [];
365 // Iterate over all of the stored invites, generating wallet info as necessary
366 const storedInvites = getStoredInvites(ls);
368 for (let i = 0; i < invitePlanets.length; i++) {
369 setInviteGeneratingNum(i + 1);
370 const planet = invitePlanets[i];
371 const storedInvite = storedInvites[planet];
372 const invite =
373 storedInvite || (await generateInviteInfo(planet, _authToken));
375 // TODO: check if the invite point's owner still matches the deterministic wallet address
376 const planetInfo = await api.getPoint(planet);
377 setStoredInvite(ls, invite);
379 if (
380 invite.owner.toLowerCase() ===
381 planetInfo.ownership?.owner?.address ||
382 planetInfo.ownership?.transferProxy?.address !== ETH_ZERO_ADDR
383 ) {
384 newInvites.push(invite);
385 updateInvite(curPoint, invite);
386 } else {
387 removeInvite(curPoint, invite.planet);
388 }
390 if (storedInvite) {
391 setStoredInvite(ls, invite);
392 }
393 }
394 setInvites(curPoint, newInvites);
395 setInvitesLoading(false);
396 return newInvites;
397 }
398 } catch (error) {
399 console.warn('ERROR GETTING INVITES', error);
400 }
401 }, [
402 api,
403 authToken,
404 contracts,
405 pointCursor,
406 ls,
407 point,
408 pointList,
409 removeInvite,
410 setInvites,
411 setInvitesLoading,
412 setInviteGeneratingNum,
413 setPendingTransactions,
414 updateInvite,
415 ]);
417 const generateInviteCodes = useCallback(
418 async (numInvites: number) => {
419 const _point = need.point(pointCursor);
420 const _contracts = contracts.getOrElse(null);
421 const _web3 = web3.getOrElse(null);
422 const _wallet = wallet.getOrElse(null);
423 const _authToken = authToken.getOrElse(null);
424 const _details = getDetails(_point);
426 if (!_contracts || !_web3 || !_wallet || !_authToken || !_details) {
427 // not using need because we want a custom error
428 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
429 }
431 // TODO: remove when UI disables management proxy from spawning invites
432 // if (Just.hasInstance(authMnemonic)) {
433 // throw new Error(
434 // "Auth key Error: A management mnemonic can't create invite codes"
435 // );
436 // }
437 let planets: UnspawnedPoints = await api.getUnspawned(_point);
439 const pendingTxs = await api.getPendingByShip(_point);
441 const pendingSpawns = getPendingSpawns(pendingTxs);
443 planets = planets.filter((point: number) => !pendingSpawns.has(point));
445 const invites: Invite[] = [];
447 const proxy = point.getSpawnProxy();
449 if (proxy === undefined)
450 throw new Error("Error: Address doesn't match proxy");
452 const nonce = await api.getNonce({
453 ship: _point,
454 proxy,
455 });
457 for (let i = 0; i < numInvites && planets[i]; i++) {
458 setInviteGeneratingNum(i + 1);
459 const planet = planets[i];
460 const nonceInc = i + nonce;
462 const { ticket, inviteWallet } = await generateInviteWallet(
463 planet,
464 _authToken
465 );
467 await submitL2Transaction({
468 api,
469 address: _wallet.address,
470 wallet: _wallet,
471 ship: _point,
472 proxy,
473 nonce: nonceInc,
474 pointToSpawn: planet,
475 type: 'spawn',
476 walletType,
477 web3: _web3,
478 connector,
479 });
481 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
482 inviteWallet,
483 1
484 );
485 await submitL2Transaction({
486 api,
487 wallet: _wallet,
488 ship: planet,
489 proxy: 'own',
490 nonce: 0,
491 networkSeed,
492 type: 'configureKeys',
493 walletType,
494 web3: _web3,
495 connector,
496 });
498 await registerProxyAddress(
499 api,
500 _wallet,
501 planet,
502 'own',
503 'manage',
504 1,
505 inviteWallet.management.keys.address,
506 walletType,
507 _web3,
508 connector
509 );
511 await submitL2Transaction({
512 api,
513 wallet: _wallet,
514 ship: planet,
515 proxy: 'own',
516 type: 'transferPoint',
517 nonce: 2,
518 address: inviteWallet.ownership.keys.address,
519 walletType,
520 web3: _web3,
521 connector,
522 });
524 invites.push({
525 hash: '',
526 ticket,
527 planet,
528 owner: inviteWallet.ownership.keys.address,
529 });
530 }
532 setStoredInvites(ls, invites);
533 getInvites();
534 showNotification(
535 `Your invite${invites.length > 1 ? 's have' : ' has'} been generated!`
536 );
537 },
538 [
539 api,
540 authToken,
541 connector,
542 contracts,
543 pointCursor,
544 point,
545 wallet,
546 walletType,
547 web3,
548 getDetails,
549 ls,
550 getInvites,
551 setInviteGeneratingNum,
552 ]
553 );
555 const getPoints = useCallback(
556 async (proxy: Proxy, address: EthAddress) => {
557 const points: Ship[] =
558 proxy === 'own'
559 ? await api.getOwnedPoints(address)
560 : proxy === 'manage'
561 ? await api.getManagerFor(address)
562 : proxy === 'vote'
563 ? await api.getVotingFor(address)
564 : proxy === 'transfer'
565 ? await api.getTransferringFor(address)
566 : proxy === 'spawn'
567 ? await api.getSpawningFor(address)
568 : [];
570 return points;
571 },
572 [api]
573 );
575 const getPendingTransactions = useCallback(async () => {
576 try {
577 if (!point.isDefault) {
578 const newPending = await api.getPendingByShip(point.value);
579 setPendingTransactions(newPending);
580 }
581 } catch (error) {
582 console.warn('ERROR GETTING PENDING', error);
583 }
584 }, [api, setPendingTransactions, point]);
586 interface ConfigureKeysParams {
587 breach: boolean;
588 customNetworkSeed?: string;
589 }
591 const configureNetworkKeys = useCallback(
592 async ({ breach, customNetworkSeed }: ConfigureKeysParams) => {
593 if (quotaReached()) {
594 return;
595 }
596 const _wallet = wallet.getOrElse(null);
597 const _web3 = web3.getOrElse(null);
598 const _details = getDetails(point.value);
599 if (!_wallet || !_web3 || !_details || point.isDefault) {
600 // not using need because we want a custom error
601 throw new Error('Internal Error: Missing Wallet/Details');
602 }
604 const proxy = point.getManagerProxy();
606 if (proxy === undefined)
607 throw new Error("Error: Address doesn't match proxy");
609 const nonce = await api.getNonce({
610 ship: point.value,
611 proxy,
612 });
614 const nextRevision = point.keyRevisionNumber + 1;
615 const networkSeed = customNetworkSeed
616 ? customNetworkSeed
617 : await attemptNetworkSeedDerivation({
618 urbitWallet,
619 wallet,
620 authMnemonic,
621 details: _details,
622 point: point.value,
623 authToken,
624 revision: nextRevision,
625 });
626 const txHash = await submitL2Transaction({
627 api,
628 wallet: _wallet,
629 ship: point.value,
630 proxy,
631 nonce,
632 networkSeed,
633 type: 'configureKeys',
634 walletType,
635 web3: _web3,
636 connector,
637 breach,
638 });
640 return api.getPendingTx(txHash);
641 },
642 [// eslint-disable-line
643 api,
644 authMnemonic,
645 authToken,
646 point,
647 urbitWallet,
648 wallet,
649 walletType,
650 web3,
651 getDetails,
652 quotaReached,
653 ]
654 );
656 const performL2Reticket = async ({
657 point,
658 to,
659 manager,
660 fromWallet,
661 toWallet,
662 }: ReticketParams) => {
663 const azimuthPoint = await api.getPoint(point);
664 const proxy = 'own';
665 const _web3 = web3.getOrElse(null);
666 let nonce = await api.getNonce({ ship: point, proxy });
668 let requests = [];
670 // 1. Update networking keys
671 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
672 const nextRevision = networkRevision + 1;
673 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
674 toWallet,
675 nextRevision
676 );
677 const configureKeysRequest = await submitL2Transaction({
678 api,
679 wallet: fromWallet,
680 ship: point,
681 proxy,
682 nonce,
683 networkSeed,
684 walletType,
685 type: 'configureKeys',
686 web3: _web3,
687 connector,
688 });
689 nonce = nonce + 1;
690 requests.push(configureKeysRequest);
692 // 2. Set Management Proxy
693 const registerMgmtRequest = await registerProxyAddress(
694 api,
695 fromWallet,
696 point,
697 proxy,
698 'manage',
699 nonce,
700 manager,
701 walletType,
702 _web3,
703 connector
704 );
705 nonce = nonce + 1;
706 requests.push(registerMgmtRequest);
708 // 3. Set Spawn Proxy
709 if (!isPlanet(Number(point))) {
710 const registerSpawnRequest = await registerProxyAddress(
711 api,
712 fromWallet,
713 point,
714 proxy,
715 'spawn',
716 nonce,
717 toWallet.spawn.keys.address,
718 walletType,
719 _web3,
720 connector
721 );
722 nonce = nonce + 1;
723 requests.push(registerSpawnRequest);
724 }
725 // 4. Transfer point
726 const transferTxRequest = await submitL2Transaction({
727 api,
728 wallet: fromWallet,
729 ship: point,
730 proxy: 'own',
731 type: 'transferPoint',
732 nonce,
733 address: to,
734 walletType,
735 web3: _web3,
736 connector,
737 });
738 requests.push(transferTxRequest);
740 const hashes = await Promise.all(requests);
741 return hashes;
742 };
744 const setProxyAddress = useCallback(
745 async (proxyType: Proxy, address: EthAddress) => {
746 if (quotaReached()) {
747 return;
748 }
750 const _wallet = wallet.getOrElse(null);
751 const _web3 = web3.getOrElse(null);
753 if (!_wallet) {
754 // not using need because we want a custom error
755 throw new Error('Internal Error: Missing Wallet');
756 }
758 if (point.isDefault) {
759 // not using need because we want a custom error
760 throw new Error('Internal Error: Invalid point');
761 }
763 const proxy = point.getAddressProxy(proxyType);
765 if (proxy === undefined)
766 throw new Error("Error: Address doesn't match expected proxy");
768 const nonce = await api.getNonce({ ship: point.value, proxy });
770 const txHash = await registerProxyAddress(
771 api,
772 _wallet,
773 point.value,
774 proxy,
775 proxyType,
776 nonce,
777 address,
778 walletType,
779 _web3,
780 connector
781 );
783 return api.getPendingTx(txHash);
784 },
785 [api, connector, point, wallet, web3, walletType, quotaReached]
786 );
788 const changeSponsorship = useCallback(
789 async (sponsee: Ship, type: 'adopt' | 'detach' | 'reject') => {
790 if (quotaReached()) {
791 return;
792 }
794 const _wallet = wallet.getOrElse(null);
795 const _web3 = web3.getOrElse(null);
796 const ship = point.value;
798 const proxy = point.getManagerProxy();
800 if (proxy === undefined)
801 throw new Error("Error: Address doesn't match expected proxy");
803 const nonce = await api.getNonce({ ship, proxy });
804 // const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
805 const txHash = await submitL2Transaction({
806 api,
807 wallet: _wallet,
808 ship,
809 sponsee,
810 proxy,
811 nonce,
812 address: _wallet.address,
813 type,
814 walletType,
815 web3: _web3,
816 connector,
817 });
819 return api.getPendingTx(txHash);
820 },
821 [api, point, wallet, walletType, web3, connector, quotaReached]
822 );
824 const transferPoint = useCallback(
825 async (address: EthAddress, reset?: boolean) => {
826 if (quotaReached()) {
827 return;
828 }
830 const _point = need.point(pointCursor);
831 const _wallet = wallet.getOrElse(null);
832 const _web3 = web3.getOrElse(null);
834 if (!_wallet) {
835 // not using need because we want a custom error
836 throw new Error('Internal Error: Missing Wallet');
837 }
839 const proxy = point.getTransferProxy();
841 if (proxy === undefined)
842 throw new Error("Error: Address doesn't match proxy");
844 const nonce = await api.getNonce({ ship: _point, proxy });
846 const txHash = await submitL2Transaction({
847 api,
848 wallet: _wallet,
849 ship: _point,
850 proxy,
851 nonce,
852 address,
853 type: 'transferPoint',
854 walletType,
855 web3: _web3,
856 connector,
857 reset,
858 });
860 return api.getPendingTx(txHash);
861 },
862 [api, connector, point, pointCursor, wallet, web3, walletType, quotaReached]
863 );
865 const sendL2Transaction = useCallback(
866 async (params: SendL2Params) => {
867 if (quotaReached()) {
868 return;
869 }
871 const _wallet = wallet.getOrElse(null);
872 const _web3 = web3.getOrElse(null);
874 if (!_wallet || point.isDefault) {
875 // not using need because we want a custom error
876 throw new Error('Internal Error: Missing Wallet or Point Info');
877 }
879 return submitL2Transaction({
880 ...params,
881 api,
882 wallet: _wallet,
883 walletType,
884 web3: _web3,
885 connector,
886 ship: point.value,
887 });
888 },
889 [api, connector, point, wallet, web3, walletType, quotaReached]
890 );
892 const changeSponsor = useCallback(
893 async (newSponsor: number) => {
894 if (point.isDefault) {
895 throw new Error('Internal Error: point is default');
896 }
898 const curPoint = point.value;
899 const _wallet = wallet.getOrElse(null);
901 if (!_wallet) {
902 // not using need because we want a custom error
903 throw new Error('Internal Error: Missing Wallet');
904 }
906 const proxy = point.getManagerProxy();
908 if (proxy === undefined)
909 throw new Error("Error: Address doesn't match proxy");
911 const nonce = await api.getNonce({ ship: curPoint, proxy });
913 return sendL2Transaction({
914 newSponsor,
915 nonce,
916 proxy,
917 type: 'escape',
918 ship: curPoint,
919 });
920 },
921 [api, point, sendL2Transaction, wallet]
922 );
924 const cancelEscape = useCallback(async () => {
925 if (point.isDefault) {
926 throw new Error('Internal Error: point is default');
927 }
929 const curPoint = point.value;
930 const _wallet = wallet.getOrElse(null);
932 if (!_wallet) {
933 // not using need because we want a custom error
934 throw new Error('Internal Error: Missing Wallet');
935 }
937 const proxy = point.getManagerProxy();
939 if (proxy === undefined)
940 throw new Error("Error: Address doesn't match proxy");
942 const nonce = await api.getNonce({ ship: curPoint, proxy });
944 return sendL2Transaction({
945 nonce,
946 proxy,
947 type: 'cancelEscape',
948 ship: curPoint,
949 });
950 }, [api, point, sendL2Transaction, wallet]);
952 const getPointsDetails = useCallback(
953 async (
954 ownedPoints: number[],
955 incomingPoints: number[],
956 managingPoints: number[],
957 votingPoints: number[],
958 spawningPoints: number[]
959 ) => {
960 try {
961 const allPoints = [
962 await Promise.all(ownedPoints.map(initPoint)),
963 await Promise.all(incomingPoints.map(initPoint)),
964 await Promise.all(managingPoints.map(initPoint)),
965 await Promise.all(votingPoints.map(initPoint)),
966 await Promise.all(spawningPoints.map(initPoint)),
967 ];
969 setPoints(
970 allPoints.reduce((acc: Points, cur: (Point | undefined)[]) => {
971 cur.forEach(p => {
972 if (p) {
973 acc[p.value] = p;
974 }
975 });
976 return acc;
977 }, {})
978 );
979 } catch (e) {
980 console.warn(e);
981 }
982 },
983 [setPoints, initPoint]
984 );
986 // On load, get initial config
987 useEffect(() => {
988 if (config) {
989 return;
990 }
992 fetchConfig();
993 }, [config, fetchConfig]);
995 useEffect(() => {
996 const time = ONE_SECOND;
998 const interval = setInterval(() => {
999 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
1000 setNextRoll(nextRoll);
1002 if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
1003 api.getRollerConfig().then(response => {
1004 setNextBatchTime(response.nextBatch);
1005 });
1007 getPendingTransactions();
1009 setTimeout(() => {
1010 if (!point.isDefault) {
1011 getInvites(); // This will also get pending txns
1013 }, TEN_SECONDS); // Should this be more like a minute?
1015 }, time);
1017 return () => clearInterval(interval);
1018 }, [point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
1020 return {
1021 api,
1022 changeSponsorship,
1023 changeSponsor,
1024 cancelEscape,
1025 checkForUpdates,
1026 config,
1027 configureNetworkKeys,
1028 generateInviteCodes,
1029 getInvites,
1030 getPoints,
1031 getPointsDetails,
1032 getPendingTransactions,
1033 ls,
1034 performL2Reticket,
1035 setProxyAddress,
1036 spawnPoint,
1037 transferPoint,
1038 };