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