on b6873da help sign in
bridge/src/lib/useRoller.ts
1047 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';
22import { getOutgoingPoints } from 'views/Points';
24import { getUpdatedPointMessage, isPlanet, toL1Details } from './utils/point';
25import { convertToInt } from './convertToInt';
26import { isDevelopment, isRopsten } from './flags';
27import {
28 generateInviteWallet,
29 getPendingSpawns,
30 submitL2Transaction,
31 getTimeToNextBatch,
32 registerProxyAddress,
33 adopt,
34 detach,
35 reject,
36 isL2Spawn,
37} from './utils/roller';
38import { ETH_ZERO_ADDR, ROLLER_HOSTS, TEN_SECONDS } from './constants';
40import {
41 Config,
42 Ship,
43 Proxy,
44 RollerRPCAPI,
45 Options,
46 EthAddress,
47 UnspawnedPoints,
48} from '@urbit/roller-api';
50import Point, { PointField, Points } from './types/Point';
51import { useTimerStore } from 'store/timerStore';
52import { ReticketParams, SendL2Params } from './types/L2Transaction';
53import { L1Point } from './types/L1Point';
54import { ddmmmYYYY } from './utils/date';
55import { showNotification } from './utils/notifications';
56import { useWalletConnect } from './useWalletConnect';
58const ONE_SECOND = 1000;
60const inviteTemplate = (
61 planet: number,
62 ticket = '',
63 owner = '',
64 hash = ''
65): Invite => ({
66 planet,
67 ticket,
68 owner,
69 hash,
70});
72export default function useRoller() {
73 const {
74 wallet,
75 authToken,
76 authMnemonic,
77 urbitWallet,
78 walletType,
79 }: any = useWallet();
80 const { connector } = useWalletConnect();
81 const { pointCursor }: any = usePointCursor();
82 const { web3, contracts }: any = useNetwork();
83 const allPoints: any = usePointCache();
84 const controlledPoints = allPoints?.controlledPoints;
85 const getDetails = allPoints?.getDetails;
87 const { setNextRoll } = useTimerStore();
89 const {
90 nextBatchTime,
91 point,
92 points,
93 nextQuotaTime,
94 removeInvite,
95 setNextBatchTime,
96 setNextQuotaTime,
97 setPendingTransactions,
98 setInvites,
99 setInviteGeneratingNum,
100 setInvitesLoading,
101 setModalText,
102 setPoints,
103 updateInvite,
104 updatePoint,
105 } = useRollerStore();
106 const [config, setConfig] = useState<Config | null>(null);
108 const options: Options = useMemo(() => {
109 const type = isRopsten || !isDevelopment ? 'https' : 'http';
110 const host = isRopsten
111 ? ROLLER_HOSTS.ROPSTEN
112 : isDevelopment
113 ? ROLLER_HOSTS.LOCAL
114 : ROLLER_HOSTS.MAINNET;
115 const port = isDevelopment && !isRopsten ? 8080 : 443;
116 const path = '/v1/roller';
118 return {
119 transport: {
120 type,
121 host,
122 port,
123 path,
124 },
125 };
126 }, []);
128 const api = useMemo(() => {
129 return new RollerRPCAPI(options);
130 }, [options]);
132 const ls = useMemo(
133 () =>
134 new SecureLS({
135 isCompression: false,
136 encryptionSecret: authToken.getOrElse('default'),
137 }),
138 [authToken]
139 );
141 const getNextQuotaTime = useCallback(
142 (nextSlice: number) => {
143 setTimeout(() => {
144 api
145 .whenNextSlice()
146 .then(next => {
147 setNextQuotaTime(next);
148 getNextQuotaTime(next);
149 })
150 .catch(err => console.warn(err));
151 }, nextSlice - new Date().getTime());
152 },
153 [api, setNextQuotaTime]
154 );
156 const fetchConfig = useCallback(async () => {
157 getNextQuotaTime(1);
158 api
159 .getRollerConfig()
160 .then(config => {
161 setConfig(config);
162 setNextBatchTime(config.nextBatch);
163 })
164 .catch(err => {
165 // TODO: more elegant error handling
166 console.warn(
167 '[fetchConfig:failed] check the roller connection?\n',
168 err
169 );
170 });
171 }, [api, getNextQuotaTime, setNextBatchTime]);
173 const quotaReached = useCallback(() => {
174 if (point.l2Quota <= 0) {
175 setModalText(
176 `You have reached your weekly L2 transaction limit. You will get another
177 ${config?.rollerQuota} transactions on ${ddmmmYYYY(nextQuotaTime)}.`
178 );
179 }
181 return point.l2Quota <= 0;
182 }, [config, nextQuotaTime, point, setModalText]);
184 const initPoint = useCallback(
185 async (point: string | number) => {
186 const _wallet = wallet.getOrElse(null);
188 if (!_wallet) {
189 return EMPTY_POINT;
190 }
192 const pointNum = Number(point);
193 try {
194 const rawDetails = await api.getPoint(pointNum);
195 const l2Quota = isL2Spawn(rawDetails?.dominion)
196 ? await api.getRemainingQuota(pointNum)
197 : 0;
198 const details = toL1Details(rawDetails);
200 return new Point({
201 value: pointNum,
202 details,
203 address: _wallet.address,
204 l2Quota,
205 });
206 } catch (e) {
207 console.warn(e);
209 // Try getting the details from L1
210 try {
211 const _contracts = need.contracts(contracts);
212 const details = await azimuth.azimuth.getPoint(_contracts, point);
213 return new Point({
214 value: pointNum,
215 details,
216 address: _wallet.address,
217 });
218 } catch (e) {
219 console.warn(e);
220 // Just return a placeholder Point
221 const details: L1Point = toL1Details();
222 return new Point({
223 value: pointNum,
224 details,
225 address: _wallet.address,
226 isPlaceholder: true,
227 });
228 }
229 }
230 },
231 [api, wallet, contracts]
232 );
234 const checkForUpdates = useCallback(
235 async (
236 point: number,
237 message?: string,
238 notify = true,
239 field?: PointField
240 ) => {
241 const interval = setInterval(async () => {
242 const updatedPoint = await initPoint(point);
243 const changedField = !points[point]
244 ? 'newPoint'
245 : updatedPoint.getChangedField(points[point], field);
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) => {
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: _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 );
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 = getOutgoingPoints(
347 controlledPoints,
348 getDetails
349 ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
351 const invitePlanets = spawnedPoints
352 .concat(
353 outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
354 )
355 .filter((p: number) => !pendingSpawns.has(p))
356 .sort();
358 setInvites(
359 curPoint,
360 invitePlanets.map(p => inviteTemplate(p))
361 );
363 const newInvites: Invite[] = [];
364 // Iterate over all of the stored invites, generating wallet info as necessary
365 const storedInvites = getStoredInvites(ls);
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));
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);
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 }
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 ]);
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 configureNetworkingKeys = 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 adoptPoint = useCallback(
789 async (ship: Ship) => {
790 if (quotaReached()) {
791 return;
792 }
794 const _wallet = wallet.getOrElse(null);
796 const proxy = point.getManagerProxy();
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 });
802 const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
804 return api.getPendingTx(txHash);
805 },
806 [api, point, wallet, quotaReached]
807 );
809 const kickPoint = useCallback(
810 async (ship: Ship) => {
811 if (quotaReached()) {
812 return;
813 }
815 if (point.isDefault) {
816 throw new Error('Internal Error: point is default');
817 }
819 const sponsor = point.value;
820 const _wallet = wallet.getOrElse(null);
821 const proxy = point.getManagerProxy();
823 if (proxy === undefined)
824 throw new Error("Error: Address doesn't match expected proxy");
826 const nonce = await api.getNonce({ ship: sponsor, proxy });
827 const txHash = await detach(api, _wallet, sponsor, proxy, nonce, ship);
829 return api.getPendingTx(txHash);
830 },
831 [api, point, wallet, quotaReached]
832 );
834 const rejectPoint = useCallback(
835 async (ship: Ship) => {
836 if (quotaReached()) {
837 return;
838 }
840 if (point.isDefault) {
841 throw new Error('Internal Error: point is default');
842 }
844 const sponsor = point.value;
845 const _wallet = wallet.getOrElse(null);
846 const proxy = point.getManagerProxy();
848 if (proxy === undefined)
849 throw new Error("Error: Address doesn't match expected proxy");
851 const nonce = await api.getNonce({ ship: sponsor, proxy });
852 const txHash = await reject(api, _wallet, sponsor, proxy, nonce, ship);
854 return api.getPendingTx(txHash);
855 },
856 [api, point, wallet, quotaReached]
857 );
859 const transferPoint = useCallback(
860 async (address: EthAddress, reset?: boolean) => {
861 if (quotaReached()) {
862 return;
863 }
865 const _point = need.point(pointCursor);
866 const _wallet = wallet.getOrElse(null);
867 const _web3 = web3.getOrElse(null);
869 if (!_wallet) {
870 // not using need because we want a custom error
871 throw new Error('Internal Error: Missing Wallet');
872 }
874 const proxy = point.getTransferProxy();
876 if (proxy === undefined)
877 throw new Error("Error: Address doesn't match proxy");
879 const nonce = await api.getNonce({ ship: _point, proxy });
881 const txHash = await submitL2Transaction({
882 api,
883 wallet: _wallet,
884 ship: _point,
885 proxy,
886 nonce,
887 address,
888 type: 'transferPoint',
889 walletType,
890 web3: _web3,
891 connector,
892 reset,
893 });
895 return api.getPendingTx(txHash);
896 },
897 [api, connector, point, pointCursor, wallet, web3, walletType, quotaReached]
898 );
900 const sendL2Transaction = useCallback(
901 async (params: SendL2Params) => {
902 if (quotaReached()) {
903 return;
904 }
906 const _wallet = wallet.getOrElse(null);
907 const _web3 = web3.getOrElse(null);
909 if (!_wallet || point.isDefault) {
910 // not using need because we want a custom error
911 throw new Error('Internal Error: Missing Wallet or Point Info');
912 }
914 return submitL2Transaction({
915 ...params,
916 api,
917 wallet: _wallet,
918 walletType,
919 web3: _web3,
920 connector,
921 ship: point.value,
922 });
923 },
924 [api, connector, point, wallet, web3, walletType, quotaReached]
925 );
927 const changeSponsor = useCallback(
928 async (newSponsor: number) => {
929 if (point.isDefault) {
930 throw new Error('Internal Error: point is default');
931 }
933 const curPoint = point.value;
934 const _wallet = wallet.getOrElse(null);
936 if (!_wallet) {
937 // not using need because we want a custom error
938 throw new Error('Internal Error: Missing Wallet');
939 }
941 const proxy = point.getManagerProxy();
943 if (proxy === undefined)
944 throw new Error("Error: Address doesn't match proxy");
946 const nonce = await api.getNonce({ ship: curPoint, proxy });
948 return sendL2Transaction({
949 newSponsor,
950 nonce,
951 proxy,
952 type: 'escape',
953 ship: curPoint,
954 });
955 },
956 [api, point, sendL2Transaction, wallet]
957 );
959 const getPointsDetails = useCallback(
960 async (
961 ownedPoints: number[],
962 incomingPoints: number[],
963 managingPoints: number[],
964 votingPoints: number[],
965 spawningPoints: number[]
966 ) => {
967 try {
968 const allPoints = [
969 await Promise.all(ownedPoints.map(initPoint)),
970 await Promise.all(incomingPoints.map(initPoint)),
971 await Promise.all(managingPoints.map(initPoint)),
972 await Promise.all(votingPoints.map(initPoint)),
973 await Promise.all(spawningPoints.map(initPoint)),
974 ];
976 setPoints(
977 allPoints.reduce((acc: Points, cur: (Point | undefined)[]) => {
978 cur.forEach(p => {
979 if (p) {
980 acc[p.value] = p;
981 }
982 });
983 return acc;
984 }, {})
985 );
986 } catch (e) {
987 console.warn(e);
988 }
989 },
990 [setPoints, initPoint]
991 );
993 // On load, get initial config
994 useEffect(() => {
995 if (config) {
996 return;
997 }
999 fetchConfig();
1000 }, [config, fetchConfig]);
1002 useEffect(() => {
1003 const time = ONE_SECOND;
1005 const interval = setInterval(() => {
1006 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
1007 setNextRoll(nextRoll);
1009 if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
1010 api.getRollerConfig().then(response => {
1011 setNextBatchTime(response.nextBatch);
1012 });
1014 getPendingTransactions();
1016 setTimeout(() => {
1017 if (!point.isDefault) {
1018 getInvites(); // This will also get pending txns
1020 }, TEN_SECONDS); // Should this be more like a minute?
1022 }, time);
1024 return () => clearInterval(interval);
1025 }, [point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
1027 return {
1028 adoptPoint,
1029 api,
1030 changeSponsor,
1031 checkForUpdates,
1032 config,
1033 configureNetworkingKeys,
1034 generateInviteCodes,
1035 getInvites,
1036 getPoints,
1037 getPointsDetails,
1038 getPendingTransactions,
1039 kickPoint,
1040 ls,
1041 performL2Reticket,
1042 rejectPoint,
1043 setProxyAddress,
1044 spawnPoint,
1045 transferPoint,
1046 };