on d2ca91e help sign in
bridge/src/lib/useRoller.ts
1069 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 = isL2(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 (sponsee: Ship, type: 'adopt' | 'detach' | 'reject') => {
819 if (quotaReached()) {
820 return;
821 }
823 const _wallet = wallet.getOrElse(null);
824 const _web3 = web3.getOrElse(null);
825 const ship = point.value;
827 const proxy = point.getManagerProxy();
829 if (proxy === undefined)
830 throw new Error("Error: Address doesn't match expected proxy");
832 const nonce = await api.getNonce({ ship, proxy });
833 // const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
834 const txHash = await submitL2Transaction({
835 api,
836 wallet: _wallet,
837 ship,
838 sponsee,
839 proxy,
840 nonce,
841 address: _wallet.address,
842 type,
843 walletType,
844 web3: _web3,
845 connector,
846 });
848 return api.getPendingTx(txHash);
849 },
850 [api, point, wallet, walletType, web3, connector, quotaReached]
851 );
853 const transferPoint = useCallback(
854 async (address: EthAddress, reset?: boolean) => {
855 if (quotaReached()) {
856 return;
857 }
859 const _point = need.point(pointCursor);
860 const _wallet = wallet.getOrElse(null);
861 const _web3 = web3.getOrElse(null);
863 if (!_wallet) {
864 // not using need because we want a custom error
865 throw new Error('Internal Error: Missing Wallet');
866 }
868 const proxy = point.getTransferProxy();
870 if (proxy === undefined)
871 throw new Error("Error: Address doesn't match proxy");
873 const nonce = await api.getNonce({ ship: _point, proxy });
875 const txHash = await submitL2Transaction({
876 api,
877 wallet: _wallet,
878 ship: _point,
879 proxy,
880 nonce,
881 address,
882 type: 'transferPoint',
883 walletType,
884 web3: _web3,
885 connector,
886 reset,
887 });
889 return api.getPendingTx(txHash);
890 },
891 [api, connector, point, pointCursor, wallet, web3, walletType, quotaReached]
892 );
894 const sendL2Transaction = useCallback(
895 async (params: SendL2Params) => {
896 if (quotaReached()) {
897 return;
898 }
900 const _wallet = wallet.getOrElse(null);
901 const _web3 = web3.getOrElse(null);
903 if (!_wallet || point.isDefault) {
904 // not using need because we want a custom error
905 throw new Error('Internal Error: Missing Wallet or Point Info');
906 }
908 return submitL2Transaction({
909 ...params,
910 api,
911 wallet: _wallet,
912 walletType,
913 web3: _web3,
914 connector,
915 ship: point.value,
916 });
917 },
918 [api, connector, point, wallet, web3, walletType, quotaReached]
919 );
921 const changeSponsor = useCallback(
922 async (newSponsor: number) => {
923 if (point.isDefault) {
924 throw new Error('Internal Error: point is default');
925 }
927 const curPoint = point.value;
928 const _wallet = wallet.getOrElse(null);
930 if (!_wallet) {
931 // not using need because we want a custom error
932 throw new Error('Internal Error: Missing Wallet');
933 }
935 const proxy = point.getManagerProxy();
937 if (proxy === undefined)
938 throw new Error("Error: Address doesn't match proxy");
940 const nonce = await api.getNonce({ ship: curPoint, proxy });
942 return sendL2Transaction({
943 newSponsor,
944 nonce,
945 proxy,
946 type: 'escape',
947 ship: curPoint,
948 });
949 },
950 [api, point, sendL2Transaction, wallet]
951 );
953 const cancelEscape = useCallback(async () => {
954 if (point.isDefault) {
955 throw new Error('Internal Error: point is default');
956 }
958 const curPoint = point.value;
959 const _wallet = wallet.getOrElse(null);
961 if (!_wallet) {
962 // not using need because we want a custom error
963 throw new Error('Internal Error: Missing Wallet');
964 }
966 const proxy = point.getManagerProxy();
968 if (proxy === undefined)
969 throw new Error("Error: Address doesn't match proxy");
971 const nonce = await api.getNonce({ ship: curPoint, proxy });
973 return sendL2Transaction({
974 nonce,
975 proxy,
976 type: 'cancelEscape',
977 ship: curPoint,
978 });
979 }, [api, point, sendL2Transaction, wallet]);
981 const getPointsDetails = useCallback(
982 async (
983 ownedPoints: number[],
984 incomingPoints: number[],
985 managingPoints: number[],
986 votingPoints: number[],
987 spawningPoints: number[]
988 ) => {
989 try {
990 const allPoints = [
991 await Promise.all(ownedPoints.map(initPoint)),
992 await Promise.all(incomingPoints.map(initPoint)),
993 await Promise.all(managingPoints.map(initPoint)),
994 await Promise.all(votingPoints.map(initPoint)),
995 await Promise.all(spawningPoints.map(initPoint)),
996 ];
998 setPoints(
999 allPoints.reduce((acc: Points, cur: (Point | undefined)[]) => {
1000 cur.forEach(p => {
1001 if (p) {
1002 acc[p.value] = p;
1004 });
1005 return acc;
1006 }, {})
1007 );
1008 } catch (e) {
1009 console.warn(e);
1011 },
1012 [setPoints, initPoint]
1013 );
1015 // On load, get initial config
1016 useEffect(() => {
1017 if (config) {
1018 return;
1021 fetchConfig();
1022 }, [config, fetchConfig]);
1024 useEffect(() => {
1025 const time = ONE_SECOND;
1027 const interval = setInterval(() => {
1028 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
1029 setNextRoll(nextRoll);
1031 if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
1032 api.getRollerConfig().then(response => {
1033 setNextBatchTime(response.nextBatch);
1034 });
1036 getPendingTransactions();
1038 setTimeout(() => {
1039 if (!point.isDefault) {
1040 getInvites(); // This will also get pending txns
1042 }, TEN_SECONDS); // Should this be more like a minute?
1044 }, time);
1046 return () => clearInterval(interval);
1047 }, [point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
1049 return {
1050 api,
1051 changeSponsorship,
1052 changeSponsor,
1053 cancelEscape,
1054 checkForUpdates,
1055 config,
1056 configureNetworkKeys,
1057 generateInviteCodes,
1058 getAndUpdatePoint,
1059 getInvites,
1060 getPoints,
1061 getPointsDetails,
1062 getPendingTransactions,
1063 ls,
1064 performL2Reticket,
1065 setProxyAddress,
1066 spawnPoint,
1067 transferPoint,
1068 };