on 73ef2e5 help sign in
bridge/src/lib/useRoller.ts
1099 lines 28 kB history raw
1import { useCallback, useEffect, useMemo, useState } from 'react';
2import * as azimuth from 'azimuth-js';
3import * as need from 'lib/need';
4import SecureLS from 'secure-ls';
6import { Invite } from 'lib/types/Invite';
7import {
8 deriveNetworkSeedFromUrbitWallet,
9 attemptNetworkSeedDerivation,
10} from 'lib/keys';
12import { useNetwork } from 'store/network';
13import { usePointCursor } from 'store/pointCursor';
14import { EMPTY_POINT, useRollerStore } from 'store/rollerStore';
15import { useWallet } from 'store/wallet';
16import {
17 getStoredInvites,
18 setStoredInvite,
19 setStoredInvites,
20} from 'store/storage/roller';
21import { usePointCache } from 'store/pointCache';
23import { getUpdatedPointMessage, isPlanet, toL1Details } from './utils/point';
24import { convertToInt } from './convertToInt';
25import { isDevelopment, isMainnet, isRopsten } from './flags';
26import {
27 generateInviteWallet,
28 getPendingSpawns,
29 submitL2Transaction,
30 getTimeToNextBatch,
31 registerProxyAddress,
32 isL2Spawn,
33} from './utils/roller';
34import { POINT_DOMINIONS, 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';
53import { PendingL1Txn } from './types/PendingL1Transaction';
54import { TRANSACTION_PROGRESS } from './reticket';
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 invitesLoading,
97 nextBatchTime,
98 point,
99 points,
100 pointList,
101 nextQuotaTime,
102 removeInvite,
103 setNextBatchTime,
104 setNextQuotaTime,
105 setPendingTransactions,
106 setInvites,
107 setInviteGeneratingNum,
108 setInvitesLoading,
109 setModalText,
110 setPoints,
111 updateInvite,
112 updatePoint,
113 storePendingL1Txn,
114 deletePendingL1Txn,
115 } = useRollerStore();
116 const [config, setConfig] = useState<Config | null>(null);
118 const options: Options = useMemo(() => {
119 const type = isMainnet || isRopsten ? 'https' : 'http';
120 const host = isMainnet
121 ? ROLLER_HOSTS.MAINNET
122 : isRopsten
123 ? ROLLER_HOSTS.ROPSTEN
124 : ROLLER_HOSTS.LOCAL;
125 const port = isMainnet || isRopsten ? 443 : 8080;
126 const path = '/v1/roller';
128 return {
129 transport: {
130 type,
131 host,
132 port,
133 path,
134 },
135 };
136 }, []);
138 const api = useMemo(() => {
139 return new RollerRPCAPI(options);
140 }, [options]);
142 const ls = useMemo(
143 () =>
144 new SecureLS({
145 isCompression: false,
146 encryptionSecret: authToken.getOrElse('default'),
147 }),
148 [authToken]
149 );
151 const getNextQuotaTime = useCallback(
152 (nextSlice: number) => {
153 setTimeout(() => {
154 api
155 .whenNextSlice()
156 .then(next => {
157 setNextQuotaTime(next);
158 getNextQuotaTime(next);
159 })
160 .catch(err => console.warn(err));
161 }, nextSlice - new Date().getTime());
162 },
163 [api, setNextQuotaTime]
164 );
166 const fetchConfig = useCallback(async () => {
167 getNextQuotaTime(1);
168 api
169 .getRollerConfig()
170 .then(config => {
171 setConfig(config);
172 setNextBatchTime(config.nextBatch);
173 })
174 .catch(err => {
175 // TODO: more elegant error handling
176 console.warn(
177 '[fetchConfig:failed] check the roller connection?\n',
178 err
179 );
180 });
181 }, [api, getNextQuotaTime, setNextBatchTime]);
183 const quotaReached = useCallback(() => {
184 if (point.l2Quota <= 0) {
185 setModalText(
186 `You have reached your weekly L2 transaction limit. You will get another
187 ${config?.rollerQuota} transactions on ${ddmmmYYYY(nextQuotaTime)}.`
188 );
189 }
191 return point.l2Quota <= 0;
192 }, [config, nextQuotaTime, point, setModalText]);
194 const initPoint = useCallback(
195 async (point: string | number) => {
196 const _wallet = wallet.getOrElse(null);
197 const _contracts = contracts.getOrElse(null);
199 if (!_wallet || !_contracts) {
200 return EMPTY_POINT;
201 }
203 const pointNum = Number(point);
204 try {
205 const rawDetails = await api.getPoint(pointNum);
206 const l2Quota = isL2Spawn(rawDetails?.dominion)
207 ? await api.getRemainingQuota(pointNum)
208 : 0;
210 const details = isL2Spawn(rawDetails?.dominion)
211 ? toL1Details(rawDetails)
212 : await azimuth.azimuth.getPoint(_contracts, point);
214 return new Point({
215 value: pointNum,
216 details,
217 address: _wallet.address,
218 l2Quota,
219 });
220 } catch (e) {
221 console.warn(e);
223 // Try getting the details from L1
224 try {
225 const _contracts = need.contracts(contracts);
226 const details = await azimuth.azimuth.getPoint(_contracts, point);
227 return new Point({
228 value: pointNum,
229 details,
230 address: _wallet.address,
231 });
232 } catch (e) {
233 console.warn(e);
234 // Just return a placeholder Point
235 const details: L1Point = toL1Details();
236 return new Point({
237 value: pointNum,
238 details,
239 address: _wallet.address,
240 isPlaceholder: true,
241 });
242 }
243 }
244 },
245 [api, wallet, contracts]
246 );
248 const getAndUpdatePoint = useCallback(
249 async (point: number) => {
250 const updatedPoint = await initPoint(point);
251 updatePoint(updatedPoint);
252 },
253 [initPoint, updatePoint]
254 );
256 const checkForUpdates = useCallback(
257 async ({
258 point,
259 message,
260 notify = true,
261 field,
262 l1Txn,
263 intervalTime = TEN_SECONDS,
264 }: UpdateParams) => {
265 if (l1Txn) storePendingL1Txn(l1Txn);
267 let interval: any;
268 const check = async () => {
269 const updatedPoint = await initPoint(point);
270 const changedField = !points[point]
271 ? 'newPoint'
272 : updatedPoint.getChangedField(points[point], field);
274 if (isDevelopment) {
275 console.log(`CHECKING FOR ${changedField} UPDATES:`, updatedPoint);
276 }
278 if (!updatedPoint.isPlaceholder && changedField) {
279 updatePoint(updatedPoint);
280 if (interval) {
281 clearInterval(interval);
282 }
284 if (l1Txn) deletePendingL1Txn(l1Txn);
285 if (notify) {
286 showNotification(
287 `${message || getUpdatedPointMessage(updatedPoint, changedField)}`
288 );
289 }
290 }
291 };
293 interval = setInterval(check, intervalTime);
294 check();
295 },
296 [points, initPoint, updatePoint, storePendingL1Txn, deletePendingL1Txn]
297 );
299 const spawnPoint = useCallback(
300 async (pointToSpawn: number, destinationAddress: string) => {
301 if (quotaReached()) {
302 return;
303 }
305 const _point = need.point(pointCursor);
306 const _contracts = contracts.getOrElse(null);
307 const _web3 = web3.getOrElse(null);
308 const _wallet = wallet.getOrElse(null);
309 const _authToken = authToken.getOrElse(null);
311 if (!_contracts || !_web3 || !_wallet || !_authToken) {
312 // not using need because we want a custom error
313 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
314 }
316 const proxy = point.getSpawnProxy();
318 if (proxy === undefined)
319 throw new Error("Error: Address doesn't match proxy");
321 const nonce = await api.getNonce({
322 ship: _point,
323 proxy,
324 });
326 await submitL2Transaction({
327 api,
328 address: destinationAddress,
329 wallet: _wallet,
330 ship: _point,
331 proxy,
332 nonce,
333 pointToSpawn,
334 type: 'spawn',
335 walletType,
336 web3: _web3,
337 connector,
338 });
339 },
340 [
341 api,
342 authToken,
343 connector,
344 contracts,
345 pointCursor,
346 point,
347 wallet,
348 walletType,
349 web3,
350 quotaReached,
351 ]
352 );
354 const generateInviteInfo = async (planet: number, _authToken: string) => {
355 const { ticket, inviteWallet } = await generateInviteWallet(
356 planet,
357 _authToken
358 );
360 return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
361 };
363 const getInvites = useCallback(async () => {
364 if (invitesLoading) {
365 return;
366 }
368 try {
369 setInvitesLoading(true);
370 const curPoint: number = Number(need.point(pointCursor));
371 const _authToken = authToken.getOrElse(null);
372 const _contracts = contracts.getOrElse(null);
374 if (_authToken && _contracts) {
375 const spawnedPoints = point.isL2Spawn
376 ? await api.getSpawned(curPoint)
377 : [];
379 const newPending = await api.getPendingByShip(curPoint);
380 setPendingTransactions(newPending);
381 const pendingSpawns = getPendingSpawns(newPending);
383 const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
384 _contracts,
385 curPoint
386 );
388 const outgoingPoints = pointList
389 .filter(
390 ({ value, isPlanet }) => isPlanet && availablePoints.includes(value)
391 )
392 .map(({ value }) => value);
394 const invitePlanets = spawnedPoints
395 .concat(
396 outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
397 )
398 .filter((p: number) => !pendingSpawns.has(p))
399 .sort();
401 setInvites(
402 curPoint,
403 invitePlanets.map(p => inviteTemplate(p))
404 );
406 const newInvites: Invite[] = [];
407 // Iterate over all of the stored invites, generating wallet info as necessary
408 const storedInvites = getStoredInvites(ls);
410 for (let i = 0; i < invitePlanets.length; i++) {
411 setInviteGeneratingNum(i + 1);
412 const planet = invitePlanets[i];
413 const storedInvite = storedInvites[planet];
414 const invite =
415 storedInvite || (await generateInviteInfo(planet, _authToken));
417 // TODO: check if the invite point's owner still matches the deterministic wallet address
418 const planetInfo = await api.getPoint(planet);
419 setStoredInvite(ls, invite);
421 if (
422 invite.owner.toLowerCase() ===
423 planetInfo.ownership?.owner?.address &&
424 planetInfo.dominion === POINT_DOMINIONS.L2
425 ) {
426 newInvites.push(invite);
427 updateInvite(curPoint, invite);
428 } else {
429 removeInvite(curPoint, invite.planet);
430 }
432 if (storedInvite) {
433 setStoredInvite(ls, invite);
434 }
435 }
436 setInvites(curPoint, newInvites);
437 setInvitesLoading(false);
438 return newInvites;
439 }
440 } catch (error) {
441 console.warn('ERROR GETTING INVITES', error);
442 }
443 }, [
444 api,
445 authToken,
446 contracts,
447 pointCursor,
448 ls,
449 point,
450 pointList,
451 removeInvite,
452 setInvites,
453 setInvitesLoading,
454 setInviteGeneratingNum,
455 setPendingTransactions,
456 updateInvite,
457 ]);
459 const generateInviteCodes = useCallback(
460 async (numInvites: number) => {
461 const _point = need.point(pointCursor);
462 const _contracts = contracts.getOrElse(null);
463 const _web3 = web3.getOrElse(null);
464 const _wallet = wallet.getOrElse(null);
465 const _authToken = authToken.getOrElse(null);
466 const _details = getDetails(_point);
468 if (!_contracts || !_web3 || !_wallet || !_authToken || !_details) {
469 // not using need because we want a custom error
470 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
471 }
473 // TODO: remove when UI disables management proxy from spawning invites
474 // if (Just.hasInstance(authMnemonic)) {
475 // throw new Error(
476 // "Auth key Error: A management mnemonic can't create invite codes"
477 // );
478 // }
479 let planets: UnspawnedPoints = await api.getUnspawned(_point);
481 const pendingTxs = await api.getPendingByShip(_point);
483 const pendingSpawns = getPendingSpawns(pendingTxs);
485 planets = planets.filter((point: number) => !pendingSpawns.has(point));
487 const invites: Invite[] = [];
489 const proxy = point.getSpawnProxy();
491 if (proxy === undefined)
492 throw new Error("Error: Address doesn't match proxy");
494 const nonce = await api.getNonce({
495 ship: _point,
496 proxy,
497 });
499 for (let i = 0; i < numInvites && planets[i]; i++) {
500 setInviteGeneratingNum(i + 1);
501 const planet = planets[i];
502 const nonceInc = i + nonce;
504 const { ticket, inviteWallet } = await generateInviteWallet(
505 planet,
506 _authToken
507 );
509 await submitL2Transaction({
510 api,
511 address: _wallet.address,
512 wallet: _wallet,
513 ship: _point,
514 proxy,
515 nonce: nonceInc,
516 pointToSpawn: planet,
517 type: 'spawn',
518 walletType,
519 web3: _web3,
520 connector,
521 });
523 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
524 inviteWallet,
525 1
526 );
527 await submitL2Transaction({
528 api,
529 wallet: _wallet,
530 ship: planet,
531 proxy: 'own',
532 nonce: 0,
533 networkSeed,
534 type: 'configureKeys',
535 walletType,
536 web3: _web3,
537 connector,
538 });
540 await registerProxyAddress(
541 api,
542 _wallet,
543 planet,
544 'own',
545 'manage',
546 1,
547 inviteWallet.management.keys.address,
548 walletType,
549 _web3,
550 connector
551 );
553 await submitL2Transaction({
554 api,
555 wallet: _wallet,
556 ship: planet,
557 proxy: 'own',
558 type: 'transferPoint',
559 nonce: 2,
560 address: inviteWallet.ownership.keys.address,
561 walletType,
562 web3: _web3,
563 connector,
564 });
566 invites.push({
567 hash: '',
568 ticket,
569 planet,
570 owner: inviteWallet.ownership.keys.address,
571 });
572 }
574 setStoredInvites(ls, invites);
575 getInvites();
576 showNotification(
577 `Your invite${invites.length > 1 ? 's have' : ' has'} been generated!`
578 );
579 },
580 [
581 api,
582 authToken,
583 connector,
584 contracts,
585 pointCursor,
586 point,
587 wallet,
588 walletType,
589 web3,
590 getDetails,
591 ls,
592 getInvites,
593 setInviteGeneratingNum,
594 ]
595 );
597 const getPoints = useCallback(
598 async (proxy: Proxy, address: EthAddress) => {
599 const points: Ship[] =
600 proxy === 'own'
601 ? await api.getOwnedPoints(address)
602 : proxy === 'manage'
603 ? await api.getManagerFor(address)
604 : proxy === 'vote'
605 ? await api.getVotingFor(address)
606 : proxy === 'transfer'
607 ? await api.getTransferringFor(address)
608 : proxy === 'spawn'
609 ? await api.getSpawningFor(address)
610 : [];
612 return points;
613 },
614 [api]
615 );
617 const getPendingTransactions = useCallback(async () => {
618 try {
619 if (!point.isDefault) {
620 const newPending = await api.getPendingByShip(point.value);
621 setPendingTransactions(newPending);
622 }
623 } catch (error) {
624 console.warn('ERROR GETTING PENDING', error);
625 }
626 }, [api, setPendingTransactions, point]);
628 interface ConfigureKeysParams {
629 breach: boolean;
630 customNetworkSeed?: string;
631 }
633 const configureNetworkKeys = useCallback(
634 async ({ breach, customNetworkSeed }: ConfigureKeysParams) => {
635 if (quotaReached()) {
636 return;
637 }
638 const _wallet = wallet.getOrElse(null);
639 const _web3 = web3.getOrElse(null);
640 if (!_wallet || !_web3 || point.isDefault) {
641 // not using need because we want a custom error
642 throw new Error('Internal Error: Missing Wallet/Details');
643 }
645 const proxy = point.getManagerProxy();
647 if (proxy === undefined)
648 throw new Error("Error: Address doesn't match proxy");
650 const nonce = await api.getNonce({
651 ship: point.value,
652 proxy,
653 });
655 const nextRevision = point.keyRevisionNumber + 1;
656 const networkSeed = customNetworkSeed
657 ? customNetworkSeed
658 : await attemptNetworkSeedDerivation({
659 urbitWallet,
660 wallet,
661 authMnemonic,
662 details: point,
663 authToken,
664 point: point.value,
665 revision: nextRevision,
666 });
667 const txHash = await submitL2Transaction({
668 api,
669 wallet: _wallet,
670 ship: point.value,
671 proxy,
672 nonce,
673 networkSeed,
674 type: 'configureKeys',
675 walletType,
676 web3: _web3,
677 connector,
678 breach,
679 });
681 return api.getPendingTx(txHash);
682 },
683 [
684 // eslint-disable-line
685 api,
686 authMnemonic,
687 authToken,
688 point,
689 urbitWallet,
690 wallet,
691 walletType,
692 web3,
693 getDetails,
694 quotaReached,
695 ]
696 );
698 const performL2Reticket = async ({
699 point,
700 to,
701 manager,
702 fromWallet,
703 toWallet,
704 onUpdate,
705 }: ReticketParams) => {
706 const azimuthPoint = await api.getPoint(point);
707 const proxy = 'own';
708 const _web3 = web3.getOrElse(null);
709 let nonce = await api.getNonce({ ship: point, proxy });
710 const progress = onUpdate
711 ? (state: number) => onUpdate({ type: 'progress', state })
712 : () => {};
714 let requests = [];
716 // 1. Update networking keys
717 progress(TRANSACTION_PROGRESS.GENERATING);
718 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
719 const nextRevision = networkRevision + 1;
720 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
721 toWallet,
722 nextRevision
723 );
724 const configureKeysRequest = await submitL2Transaction({
725 api,
726 wallet: fromWallet,
727 ship: point,
728 proxy,
729 nonce,
730 networkSeed,
731 walletType,
732 type: 'configureKeys',
733 web3: _web3,
734 connector,
735 });
736 nonce = nonce + 1;
737 requests.push(configureKeysRequest);
739 // 2. Set Management Proxy
740 progress(TRANSACTION_PROGRESS.SIGNING);
741 const registerMgmtRequest = await registerProxyAddress(
742 api,
743 fromWallet,
744 point,
745 proxy,
746 'manage',
747 nonce,
748 manager,
749 walletType,
750 _web3,
751 connector
752 );
753 nonce = nonce + 1;
754 requests.push(registerMgmtRequest);
756 // 3. Set Spawn Proxy
757 progress(TRANSACTION_PROGRESS.FUNDING);
758 if (!isPlanet(Number(point))) {
759 const registerSpawnRequest = await registerProxyAddress(
760 api,
761 fromWallet,
762 point,
763 proxy,
764 'spawn',
765 nonce,
766 toWallet?.spawn?.keys?.address || to,
767 walletType,
768 _web3,
769 connector
770 );
771 nonce = nonce + 1;
772 requests.push(registerSpawnRequest);
773 }
775 // 4. Transfer point
776 progress(TRANSACTION_PROGRESS.TRANSFERRING);
777 const transferTxRequest = await submitL2Transaction({
778 api,
779 wallet: fromWallet,
780 ship: point,
781 proxy: 'own',
782 type: 'transferPoint',
783 nonce,
784 address: to,
785 walletType,
786 web3: _web3,
787 connector,
788 });
789 requests.push(transferTxRequest);
791 progress(TRANSACTION_PROGRESS.CLEANING);
792 const hashes = await Promise.all(requests);
794 progress(TRANSACTION_PROGRESS.DONE);
795 return hashes;
796 };
798 const setProxyAddress = useCallback(
799 async (proxyType: Proxy, address: EthAddress) => {
800 if (quotaReached()) {
801 return;
802 }
804 const _wallet = wallet.getOrElse(null);
805 const _web3 = web3.getOrElse(null);
807 if (!_wallet) {
808 // not using need because we want a custom error
809 throw new Error('Internal Error: Missing Wallet');
810 }
812 if (point.isDefault) {
813 // not using need because we want a custom error
814 throw new Error('Internal Error: Invalid point');
815 }
817 const proxy = point.getAddressProxy(proxyType);
819 if (proxy === undefined)
820 throw new Error("Error: Address doesn't match expected proxy");
822 const nonce = await api.getNonce({ ship: point.value, proxy });
824 const txHash = await registerProxyAddress(
825 api,
826 _wallet,
827 point.value,
828 proxy,
829 proxyType,
830 nonce,
831 address,
832 walletType,
833 _web3,
834 connector
835 );
837 return api.getPendingTx(txHash);
838 },
839 [api, connector, point, wallet, web3, walletType, quotaReached]
840 );
842 const changeSponsorship = useCallback(
843 async (
844 sponsee: Ship,
845 type: 'adopt' | 'detach' | 'reject',
846 skipQuotaCheck = false
847 ) => {
848 if (!skipQuotaCheck && quotaReached()) {
849 return;
850 }
852 const _wallet = wallet.getOrElse(null);
853 const _web3 = web3.getOrElse(null);
854 const ship = point.value;
856 const proxy = point.getManagerProxy();
858 if (proxy === undefined)
859 throw new Error("Error: Address doesn't match expected proxy");
861 const nonce = await api.getNonce({ ship, proxy });
862 // const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
863 const txHash = await submitL2Transaction({
864 api,
865 wallet: _wallet,
866 ship,
867 sponsee,
868 proxy,
869 nonce,
870 address: _wallet.address,
871 type,
872 walletType,
873 web3: _web3,
874 connector,
875 });
877 return api.getPendingTx(txHash);
878 },
879 [api, point, wallet, walletType, web3, connector, quotaReached]
880 );
882 const transferPoint = useCallback(
883 async (address: EthAddress, reset?: boolean) => {
884 if (quotaReached()) {
885 return;
886 }
888 const _point = need.point(pointCursor);
889 const _wallet = wallet.getOrElse(null);
890 const _web3 = web3.getOrElse(null);
892 if (!_wallet) {
893 // not using need because we want a custom error
894 throw new Error('Internal Error: Missing Wallet');
895 }
897 const proxy = point.getTransferProxy();
899 if (proxy === undefined)
900 throw new Error("Error: Address doesn't match proxy");
902 const nonce = await api.getNonce({ ship: _point, proxy });
904 const txHash = await submitL2Transaction({
905 api,
906 wallet: _wallet,
907 ship: _point,
908 proxy,
909 nonce,
910 address,
911 type: 'transferPoint',
912 walletType,
913 web3: _web3,
914 connector,
915 reset,
916 });
918 return api.getPendingTx(txHash);
919 },
920 [api, connector, point, pointCursor, wallet, web3, walletType, quotaReached]
921 );
923 const sendL2Transaction = useCallback(
924 async (params: SendL2Params) => {
925 if (quotaReached()) {
926 return;
927 }
929 const _wallet = wallet.getOrElse(null);
930 const _web3 = web3.getOrElse(null);
932 if (!_wallet || point.isDefault) {
933 // not using need because we want a custom error
934 throw new Error('Internal Error: Missing Wallet or Point Info');
935 }
937 return submitL2Transaction({
938 ...params,
939 api,
940 wallet: _wallet,
941 walletType,
942 web3: _web3,
943 connector,
944 ship: point.value,
945 });
946 },
947 [api, connector, point, wallet, web3, walletType, quotaReached]
948 );
950 const changeSponsor = useCallback(
951 async (newSponsor: number) => {
952 if (point.isDefault) {
953 throw new Error('Internal Error: point is default');
954 }
956 const curPoint = point.value;
957 const _wallet = wallet.getOrElse(null);
959 if (!_wallet) {
960 // not using need because we want a custom error
961 throw new Error('Internal Error: Missing Wallet');
962 }
964 const proxy = point.getManagerProxy();
966 if (proxy === undefined)
967 throw new Error("Error: Address doesn't match proxy");
969 const nonce = await api.getNonce({ ship: curPoint, proxy });
971 return sendL2Transaction({
972 newSponsor,
973 nonce,
974 proxy,
975 type: 'escape',
976 ship: curPoint,
977 });
978 },
979 [api, point, sendL2Transaction, wallet]
980 );
982 const cancelEscape = useCallback(async () => {
983 if (point.isDefault) {
984 throw new Error('Internal Error: point is default');
985 }
987 const curPoint = point.value;
988 const _wallet = wallet.getOrElse(null);
990 if (!_wallet) {
991 // not using need because we want a custom error
992 throw new Error('Internal Error: Missing Wallet');
993 }
995 const proxy = point.getManagerProxy();
997 if (proxy === undefined)
998 throw new Error("Error: Address doesn't match proxy");
1000 const nonce = await api.getNonce({ ship: curPoint, proxy });
1002 return sendL2Transaction({
1003 nonce,
1004 proxy,
1005 type: 'cancelEscape',
1006 ship: curPoint,
1007 });
1008 }, [api, point, sendL2Transaction, wallet]);
1010 const getPointsDetails = useCallback(
1011 async (
1012 ownedPoints: number[],
1013 incomingPoints: number[],
1014 managingPoints: number[],
1015 votingPoints: number[],
1016 spawningPoints: number[]
1017 ) => {
1018 try {
1019 const allPoints = [
1020 await Promise.all(ownedPoints.map(initPoint)),
1021 await Promise.all(incomingPoints.map(initPoint)),
1022 await Promise.all(managingPoints.map(initPoint)),
1023 await Promise.all(votingPoints.map(initPoint)),
1024 await Promise.all(spawningPoints.map(initPoint)),
1025 ];
1027 setPoints(
1028 allPoints.reduce((acc: Points, cur: (Point | undefined)[]) => {
1029 cur.forEach(p => {
1030 if (p) {
1031 acc[p.value] = p;
1033 });
1034 return acc;
1035 }, {})
1036 );
1037 } catch (e) {
1038 console.warn(e);
1040 },
1041 [setPoints, initPoint]
1042 );
1044 // On load, get initial config
1045 useEffect(() => {
1046 if (config) {
1047 return;
1050 fetchConfig();
1051 }, [config, fetchConfig]);
1053 useEffect(() => {
1054 const time = ONE_SECOND;
1056 const interval = setInterval(() => {
1057 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
1058 setNextRoll(nextRoll);
1060 if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
1061 api.getRollerConfig().then(response => {
1062 setNextBatchTime(response.nextBatch);
1063 });
1065 getPendingTransactions();
1067 setTimeout(() => {
1068 if (!point.isDefault) {
1069 getInvites(); // This will also get pending txns
1071 }, TEN_SECONDS); // Should this be more like a minute?
1073 }, time);
1075 return () => clearInterval(interval);
1076 }, [point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
1078 return {
1079 api,
1080 changeSponsorship,
1081 changeSponsor,
1082 cancelEscape,
1083 checkForUpdates,
1084 config,
1085 configureNetworkKeys,
1086 generateInviteCodes,
1087 getAndUpdatePoint,
1088 getInvites,
1089 getPoints,
1090 getPointsDetails,
1091 getPendingTransactions,
1092 initPoint,
1093 ls,
1094 performL2Reticket,
1095 setProxyAddress,
1096 spawnPoint,
1097 transferPoint,
1098 };