on 2ae620c help sign in
bridge/src/lib/useRoller.ts
1095 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 * as ob from 'urbit-ob';
5import SecureLS from 'secure-ls';
7import { Invite } from 'lib/types/Invite';
8import {
9 deriveNetworkSeedFromUrbitWallet,
10 attemptNetworkSeedDerivation,
11} from 'lib/keys';
13import { useNetwork } from 'store/network';
14import { usePointCursor } from 'store/pointCursor';
15import { EMPTY_POINT, useRollerStore } from 'store/rollerStore';
16import { useWallet } from 'store/wallet';
17import {
18 getStoredInvites,
19 setStoredInvite,
20 setStoredInvites,
21} from 'store/storage/roller';
22import { usePointCache } from 'store/pointCache';
24import { getUpdatedPointMessage, isPlanet, toL1Details } from './utils/point';
25import { convertToInt } from './convertToInt';
26import { isDevelopment, isMainnet, isRopsten } from './flags';
27import {
28 generateInviteWallet,
29 getPendingSpawns,
30 submitL2Transaction,
31 getTimeToNextBatch,
32 registerProxyAddress,
33 isL2Spawn,
34 isL2,
35} from './utils/roller';
36import { ETH_ZERO_ADDR, ROLLER_HOSTS, TEN_SECONDS } from './constants';
38import {
39 Config,
40 Ship,
41 Proxy,
42 RollerRPCAPI,
43 Options,
44 EthAddress,
45 UnspawnedPoints,
46} from '@urbit/roller-api';
48import Point, { PointField, Points } from './types/Point';
49import { useTimerStore } from 'store/timerStore';
50import { ReticketParams, SendL2Params } from './types/L2Transaction';
51import { L1Point } from './types/L1Point';
52import { ddmmmYYYY } from './utils/date';
53import { showNotification } from './utils/notifications';
54import { useWalletConnect } from './useWalletConnect';
55import { PendingL1Txn } from './types/PendingL1Transaction';
57const ONE_SECOND = 1000;
59interface UpdateParams {
60 point: number;
61 message?: string;
62 notify?: boolean;
63 field?: PointField;
64 l1Txn?: PendingL1Txn;
65 intervalTime?: number;
68const inviteTemplate = (
69 planet: number,
70 ticket = '',
71 owner = '',
72 hash = ''
73): Invite => ({
74 planet,
75 ticket,
76 owner,
77 hash,
78});
80export default function useRoller() {
81 const {
82 wallet,
83 authToken,
84 authMnemonic,
85 urbitWallet,
86 walletType,
87 }: any = useWallet();
88 const { connector } = useWalletConnect();
89 const { pointCursor }: any = usePointCursor();
90 const { web3, contracts }: any = useNetwork();
91 const allPoints: any = usePointCache();
92 const getDetails = allPoints?.getDetails;
94 const { setNextRoll } = useTimerStore();
96 const {
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 const interval = setInterval(async () => {
268 const updatedPoint = await initPoint(point);
269 const changedField = !points[point]
270 ? 'newPoint'
271 : updatedPoint.getChangedField(points[point], field);
273 if (isDevelopment) {
274 console.log(`CHECKING FOR ${changedField} UPDATES:`, updatedPoint);
275 }
277 if (!updatedPoint.isPlaceholder && changedField) {
278 updatePoint(updatedPoint);
279 clearInterval(interval);
280 if (l1Txn) deletePendingL1Txn(l1Txn);
281 if (notify) {
282 showNotification(
283 `${message || getUpdatedPointMessage(updatedPoint, changedField)}`
284 );
285 }
286 }
287 }, intervalTime);
288 },
289 [points, initPoint, updatePoint, storePendingL1Txn, deletePendingL1Txn]
290 );
292 const spawnPoint = useCallback(
293 async (pointToSpawn: number, destinationAddress: string) => {
294 if (quotaReached()) {
295 return;
296 }
298 const _point = need.point(pointCursor);
299 const _contracts = contracts.getOrElse(null);
300 const _web3 = web3.getOrElse(null);
301 const _wallet = wallet.getOrElse(null);
302 const _authToken = authToken.getOrElse(null);
304 if (!_contracts || !_web3 || !_wallet || !_authToken) {
305 // not using need because we want a custom error
306 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
307 }
309 const proxy = point.getSpawnProxy();
311 if (proxy === undefined)
312 throw new Error("Error: Address doesn't match proxy");
314 const nonce = await api.getNonce({
315 ship: _point,
316 proxy,
317 });
319 await submitL2Transaction({
320 api,
321 address: destinationAddress,
322 wallet: _wallet,
323 ship: _point,
324 proxy,
325 nonce,
326 pointToSpawn,
327 type: 'spawn',
328 walletType,
329 web3: _web3,
330 connector,
331 });
332 },
333 [
334 api,
335 authToken,
336 connector,
337 contracts,
338 pointCursor,
339 point,
340 wallet,
341 walletType,
342 web3,
343 quotaReached,
344 ]
345 );
347 const generateInviteInfo = async (planet: number, _authToken: string) => {
348 const { ticket, inviteWallet } = await generateInviteWallet(
349 planet,
350 _authToken
351 );
353 return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
354 };
356 const getInvites = useCallback(async () => {
357 try {
358 setInvitesLoading(true);
359 const curPoint: number = Number(need.point(pointCursor));
360 const _authToken = authToken.getOrElse(null);
361 const _contracts = contracts.getOrElse(null);
363 if (_authToken && _contracts) {
364 const spawnedPoints = point.isL2Spawn
365 ? await api.getSpawned(curPoint)
366 : [];
368 const newPending = await api.getPendingByShip(curPoint);
369 setPendingTransactions(newPending);
370 const pendingSpawns = getPendingSpawns(newPending);
372 const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
373 _contracts,
374 curPoint
375 );
377 const outgoingPoints = pointList
378 .filter(
379 ({ value, isPlanet }) => isPlanet && availablePoints.includes(value)
380 )
381 .map(({ value }) => value);
383 const invitePlanets = spawnedPoints
384 .concat(
385 outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
386 )
387 .filter((p: number) => !pendingSpawns.has(p))
388 .sort();
390 setInvites(
391 curPoint,
392 invitePlanets.map(p => inviteTemplate(p))
393 );
395 const newInvites: Invite[] = [];
396 // Iterate over all of the stored invites, generating wallet info as necessary
397 const storedInvites = getStoredInvites(ls);
399 for (let i = 0; i < invitePlanets.length; i++) {
400 setInviteGeneratingNum(i + 1);
401 const planet = invitePlanets[i];
402 const storedInvite = storedInvites[planet];
403 const invite =
404 storedInvite || (await generateInviteInfo(planet, _authToken));
406 // TODO: check if the invite point's owner still matches the deterministic wallet address
407 const planetInfo = await api.getPoint(planet);
408 setStoredInvite(ls, invite);
410 if (isDevelopment) {
411 console.log(
412 `planet: ${planet} dominion: ${planetInfo.dominion} (${ob.patp(
413 planet
414 )})`
415 );
416 console.log(`invite owner: ${invite.owner.toLowerCase()}`);
417 console.log(
418 `planetInfo owner: ${planetInfo.ownership?.owner?.address}`
419 );
420 console.log(
421 `planetInfo transfer: ${planetInfo.ownership?.transferProxy?.address}`
422 );
423 console.log(
424 `invite unclaimed: ${invite.owner.toLowerCase() ===
425 planetInfo.ownership?.owner?.address ||
426 planetInfo.ownership?.transferProxy?.address !== ETH_ZERO_ADDR}`
427 );
428 }
430 if (
431 invite.owner.toLowerCase() ===
432 planetInfo.ownership?.owner?.address ||
433 planetInfo.ownership?.transferProxy?.address !== ETH_ZERO_ADDR
434 ) {
435 newInvites.push(invite);
436 updateInvite(curPoint, invite);
437 } else {
438 removeInvite(curPoint, invite.planet);
439 }
441 if (storedInvite) {
442 setStoredInvite(ls, invite);
443 }
444 }
445 setInvites(curPoint, newInvites);
446 setInvitesLoading(false);
447 return newInvites;
448 }
449 } catch (error) {
450 console.warn('ERROR GETTING INVITES', error);
451 }
452 }, [
453 api,
454 authToken,
455 contracts,
456 pointCursor,
457 ls,
458 point,
459 pointList,
460 removeInvite,
461 setInvites,
462 setInvitesLoading,
463 setInviteGeneratingNum,
464 setPendingTransactions,
465 updateInvite,
466 ]);
468 const generateInviteCodes = useCallback(
469 async (numInvites: number) => {
470 const _point = need.point(pointCursor);
471 const _contracts = contracts.getOrElse(null);
472 const _web3 = web3.getOrElse(null);
473 const _wallet = wallet.getOrElse(null);
474 const _authToken = authToken.getOrElse(null);
475 const _details = getDetails(_point);
477 if (!_contracts || !_web3 || !_wallet || !_authToken || !_details) {
478 // not using need because we want a custom error
479 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
480 }
482 // TODO: remove when UI disables management proxy from spawning invites
483 // if (Just.hasInstance(authMnemonic)) {
484 // throw new Error(
485 // "Auth key Error: A management mnemonic can't create invite codes"
486 // );
487 // }
488 let planets: UnspawnedPoints = await api.getUnspawned(_point);
490 const pendingTxs = await api.getPendingByShip(_point);
492 const pendingSpawns = getPendingSpawns(pendingTxs);
494 planets = planets.filter((point: number) => !pendingSpawns.has(point));
496 const invites: Invite[] = [];
498 const proxy = point.getSpawnProxy();
500 if (proxy === undefined)
501 throw new Error("Error: Address doesn't match proxy");
503 const nonce = await api.getNonce({
504 ship: _point,
505 proxy,
506 });
508 for (let i = 0; i < numInvites && planets[i]; i++) {
509 setInviteGeneratingNum(i + 1);
510 const planet = planets[i];
511 const nonceInc = i + nonce;
513 const { ticket, inviteWallet } = await generateInviteWallet(
514 planet,
515 _authToken
516 );
518 await submitL2Transaction({
519 api,
520 address: _wallet.address,
521 wallet: _wallet,
522 ship: _point,
523 proxy,
524 nonce: nonceInc,
525 pointToSpawn: planet,
526 type: 'spawn',
527 walletType,
528 web3: _web3,
529 connector,
530 });
532 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
533 inviteWallet,
534 1
535 );
536 await submitL2Transaction({
537 api,
538 wallet: _wallet,
539 ship: planet,
540 proxy: 'own',
541 nonce: 0,
542 networkSeed,
543 type: 'configureKeys',
544 walletType,
545 web3: _web3,
546 connector,
547 });
549 await registerProxyAddress(
550 api,
551 _wallet,
552 planet,
553 'own',
554 'manage',
555 1,
556 inviteWallet.management.keys.address,
557 walletType,
558 _web3,
559 connector
560 );
562 await submitL2Transaction({
563 api,
564 wallet: _wallet,
565 ship: planet,
566 proxy: 'own',
567 type: 'transferPoint',
568 nonce: 2,
569 address: inviteWallet.ownership.keys.address,
570 walletType,
571 web3: _web3,
572 connector,
573 });
575 invites.push({
576 hash: '',
577 ticket,
578 planet,
579 owner: inviteWallet.ownership.keys.address,
580 });
581 }
583 setStoredInvites(ls, invites);
584 getInvites();
585 showNotification(
586 `Your invite${invites.length > 1 ? 's have' : ' has'} been generated!`
587 );
588 },
589 [
590 api,
591 authToken,
592 connector,
593 contracts,
594 pointCursor,
595 point,
596 wallet,
597 walletType,
598 web3,
599 getDetails,
600 ls,
601 getInvites,
602 setInviteGeneratingNum,
603 ]
604 );
606 const getPoints = useCallback(
607 async (proxy: Proxy, address: EthAddress) => {
608 const points: Ship[] =
609 proxy === 'own'
610 ? await api.getOwnedPoints(address)
611 : proxy === 'manage'
612 ? await api.getManagerFor(address)
613 : proxy === 'vote'
614 ? await api.getVotingFor(address)
615 : proxy === 'transfer'
616 ? await api.getTransferringFor(address)
617 : proxy === 'spawn'
618 ? await api.getSpawningFor(address)
619 : [];
621 return points;
622 },
623 [api]
624 );
626 const getPendingTransactions = useCallback(async () => {
627 try {
628 if (!point.isDefault) {
629 const newPending = await api.getPendingByShip(point.value);
630 setPendingTransactions(newPending);
631 }
632 } catch (error) {
633 console.warn('ERROR GETTING PENDING', error);
634 }
635 }, [api, setPendingTransactions, point]);
637 interface ConfigureKeysParams {
638 breach: boolean;
639 customNetworkSeed?: string;
640 }
642 const configureNetworkKeys = useCallback(
643 async ({ breach, customNetworkSeed }: ConfigureKeysParams) => {
644 if (quotaReached()) {
645 return;
646 }
647 const _wallet = wallet.getOrElse(null);
648 const _web3 = web3.getOrElse(null);
649 if (!_wallet || !_web3 || point.isDefault) {
650 // not using need because we want a custom error
651 throw new Error('Internal Error: Missing Wallet/Details');
652 }
654 const proxy = point.getManagerProxy();
656 if (proxy === undefined)
657 throw new Error("Error: Address doesn't match proxy");
659 const nonce = await api.getNonce({
660 ship: point.value,
661 proxy,
662 });
664 const nextRevision = point.keyRevisionNumber + 1;
665 const networkSeed = customNetworkSeed
666 ? customNetworkSeed
667 : await attemptNetworkSeedDerivation({
668 urbitWallet,
669 wallet,
670 authMnemonic,
671 details: point,
672 authToken,
673 point: point.value,
674 revision: nextRevision,
675 });
676 const txHash = await submitL2Transaction({
677 api,
678 wallet: _wallet,
679 ship: point.value,
680 proxy,
681 nonce,
682 networkSeed,
683 type: 'configureKeys',
684 walletType,
685 web3: _web3,
686 connector,
687 breach,
688 });
690 return api.getPendingTx(txHash);
691 },
692 [// eslint-disable-line
693 api,
694 authMnemonic,
695 authToken,
696 point,
697 urbitWallet,
698 wallet,
699 walletType,
700 web3,
701 getDetails,
702 quotaReached,
703 ]
704 );
706 const performL2Reticket = async ({
707 point,
708 to,
709 manager,
710 fromWallet,
711 toWallet,
712 }: ReticketParams) => {
713 const azimuthPoint = await api.getPoint(point);
714 const proxy = 'own';
715 const _web3 = web3.getOrElse(null);
716 let nonce = await api.getNonce({ ship: point, proxy });
718 let requests = [];
720 // 1. Update networking keys
721 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
722 const nextRevision = networkRevision + 1;
723 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
724 toWallet,
725 nextRevision
726 );
727 const configureKeysRequest = await submitL2Transaction({
728 api,
729 wallet: fromWallet,
730 ship: point,
731 proxy,
732 nonce,
733 networkSeed,
734 walletType,
735 type: 'configureKeys',
736 web3: _web3,
737 connector,
738 });
739 nonce = nonce + 1;
740 requests.push(configureKeysRequest);
742 // 2. Set Management Proxy
743 const registerMgmtRequest = await registerProxyAddress(
744 api,
745 fromWallet,
746 point,
747 proxy,
748 'manage',
749 nonce,
750 manager,
751 walletType,
752 _web3,
753 connector
754 );
755 nonce = nonce + 1;
756 requests.push(registerMgmtRequest);
758 // 3. Set Spawn Proxy
759 if (!isPlanet(Number(point))) {
760 const registerSpawnRequest = await registerProxyAddress(
761 api,
762 fromWallet,
763 point,
764 proxy,
765 'spawn',
766 nonce,
767 toWallet?.spawn?.keys?.address || to,
768 walletType,
769 _web3,
770 connector
771 );
772 nonce = nonce + 1;
773 requests.push(registerSpawnRequest);
774 }
775 // 4. Transfer point
776 const transferTxRequest = await submitL2Transaction({
777 api,
778 wallet: fromWallet,
779 ship: point,
780 proxy: 'own',
781 type: 'transferPoint',
782 nonce,
783 address: to,
784 walletType,
785 web3: _web3,
786 connector,
787 });
788 requests.push(transferTxRequest);
790 const hashes = await Promise.all(requests);
791 return hashes;
792 };
794 const setProxyAddress = useCallback(
795 async (proxyType: Proxy, address: EthAddress) => {
796 if (quotaReached()) {
797 return;
798 }
800 const _wallet = wallet.getOrElse(null);
801 const _web3 = web3.getOrElse(null);
803 if (!_wallet) {
804 // not using need because we want a custom error
805 throw new Error('Internal Error: Missing Wallet');
806 }
808 if (point.isDefault) {
809 // not using need because we want a custom error
810 throw new Error('Internal Error: Invalid point');
811 }
813 const proxy = point.getAddressProxy(proxyType);
815 if (proxy === undefined)
816 throw new Error("Error: Address doesn't match expected proxy");
818 const nonce = await api.getNonce({ ship: point.value, proxy });
820 const txHash = await registerProxyAddress(
821 api,
822 _wallet,
823 point.value,
824 proxy,
825 proxyType,
826 nonce,
827 address,
828 walletType,
829 _web3,
830 connector
831 );
833 return api.getPendingTx(txHash);
834 },
835 [api, connector, point, wallet, web3, walletType, quotaReached]
836 );
838 const changeSponsorship = useCallback(
839 async (
840 sponsee: Ship,
841 type: 'adopt' | 'detach' | 'reject',
842 skipQuotaCheck = false
843 ) => {
844 if (!skipQuotaCheck && quotaReached()) {
845 return;
846 }
848 const _wallet = wallet.getOrElse(null);
849 const _web3 = web3.getOrElse(null);
850 const ship = point.value;
852 const proxy = point.getManagerProxy();
854 if (proxy === undefined)
855 throw new Error("Error: Address doesn't match expected proxy");
857 const nonce = await api.getNonce({ ship, proxy });
858 // const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
859 const txHash = await submitL2Transaction({
860 api,
861 wallet: _wallet,
862 ship,
863 sponsee,
864 proxy,
865 nonce,
866 address: _wallet.address,
867 type,
868 walletType,
869 web3: _web3,
870 connector,
871 });
873 return api.getPendingTx(txHash);
874 },
875 [api, point, wallet, walletType, web3, connector, quotaReached]
876 );
878 const transferPoint = useCallback(
879 async (address: EthAddress, reset?: boolean) => {
880 if (quotaReached()) {
881 return;
882 }
884 const _point = need.point(pointCursor);
885 const _wallet = wallet.getOrElse(null);
886 const _web3 = web3.getOrElse(null);
888 if (!_wallet) {
889 // not using need because we want a custom error
890 throw new Error('Internal Error: Missing Wallet');
891 }
893 const proxy = point.getTransferProxy();
895 if (proxy === undefined)
896 throw new Error("Error: Address doesn't match proxy");
898 const nonce = await api.getNonce({ ship: _point, proxy });
900 const txHash = await submitL2Transaction({
901 api,
902 wallet: _wallet,
903 ship: _point,
904 proxy,
905 nonce,
906 address,
907 type: 'transferPoint',
908 walletType,
909 web3: _web3,
910 connector,
911 reset,
912 });
914 return api.getPendingTx(txHash);
915 },
916 [api, connector, point, pointCursor, wallet, web3, walletType, quotaReached]
917 );
919 const sendL2Transaction = useCallback(
920 async (params: SendL2Params) => {
921 if (quotaReached()) {
922 return;
923 }
925 const _wallet = wallet.getOrElse(null);
926 const _web3 = web3.getOrElse(null);
928 if (!_wallet || point.isDefault) {
929 // not using need because we want a custom error
930 throw new Error('Internal Error: Missing Wallet or Point Info');
931 }
933 return submitL2Transaction({
934 ...params,
935 api,
936 wallet: _wallet,
937 walletType,
938 web3: _web3,
939 connector,
940 ship: point.value,
941 });
942 },
943 [api, connector, point, wallet, web3, walletType, quotaReached]
944 );
946 const changeSponsor = useCallback(
947 async (newSponsor: number) => {
948 if (point.isDefault) {
949 throw new Error('Internal Error: point is default');
950 }
952 const curPoint = point.value;
953 const _wallet = wallet.getOrElse(null);
955 if (!_wallet) {
956 // not using need because we want a custom error
957 throw new Error('Internal Error: Missing Wallet');
958 }
960 const proxy = point.getManagerProxy();
962 if (proxy === undefined)
963 throw new Error("Error: Address doesn't match proxy");
965 const nonce = await api.getNonce({ ship: curPoint, proxy });
967 return sendL2Transaction({
968 newSponsor,
969 nonce,
970 proxy,
971 type: 'escape',
972 ship: curPoint,
973 });
974 },
975 [api, point, sendL2Transaction, wallet]
976 );
978 const cancelEscape = useCallback(async () => {
979 if (point.isDefault) {
980 throw new Error('Internal Error: point is default');
981 }
983 const curPoint = point.value;
984 const _wallet = wallet.getOrElse(null);
986 if (!_wallet) {
987 // not using need because we want a custom error
988 throw new Error('Internal Error: Missing Wallet');
989 }
991 const proxy = point.getManagerProxy();
993 if (proxy === undefined)
994 throw new Error("Error: Address doesn't match proxy");
996 const nonce = await api.getNonce({ ship: curPoint, proxy });
998 return sendL2Transaction({
999 nonce,
1000 proxy,
1001 type: 'cancelEscape',
1002 ship: curPoint,
1003 });
1004 }, [api, point, sendL2Transaction, wallet]);
1006 const getPointsDetails = useCallback(
1007 async (
1008 ownedPoints: number[],
1009 incomingPoints: number[],
1010 managingPoints: number[],
1011 votingPoints: number[],
1012 spawningPoints: number[]
1013 ) => {
1014 try {
1015 const allPoints = [
1016 await Promise.all(ownedPoints.map(initPoint)),
1017 await Promise.all(incomingPoints.map(initPoint)),
1018 await Promise.all(managingPoints.map(initPoint)),
1019 await Promise.all(votingPoints.map(initPoint)),
1020 await Promise.all(spawningPoints.map(initPoint)),
1021 ];
1023 setPoints(
1024 allPoints.reduce((acc: Points, cur: (Point | undefined)[]) => {
1025 cur.forEach(p => {
1026 if (p) {
1027 acc[p.value] = p;
1029 });
1030 return acc;
1031 }, {})
1032 );
1033 } catch (e) {
1034 console.warn(e);
1036 },
1037 [setPoints, initPoint]
1038 );
1040 // On load, get initial config
1041 useEffect(() => {
1042 if (config) {
1043 return;
1046 fetchConfig();
1047 }, [config, fetchConfig]);
1049 useEffect(() => {
1050 const time = ONE_SECOND;
1052 const interval = setInterval(() => {
1053 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
1054 setNextRoll(nextRoll);
1056 if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
1057 api.getRollerConfig().then(response => {
1058 setNextBatchTime(response.nextBatch);
1059 });
1061 getPendingTransactions();
1063 setTimeout(() => {
1064 if (!point.isDefault) {
1065 getInvites(); // This will also get pending txns
1067 }, TEN_SECONDS); // Should this be more like a minute?
1069 }, time);
1071 return () => clearInterval(interval);
1072 }, [point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
1074 return {
1075 api,
1076 changeSponsorship,
1077 changeSponsor,
1078 cancelEscape,
1079 checkForUpdates,
1080 config,
1081 configureNetworkKeys,
1082 generateInviteCodes,
1083 getAndUpdatePoint,
1084 getInvites,
1085 getPoints,
1086 getPointsDetails,
1087 getPendingTransactions,
1088 initPoint,
1089 ls,
1090 performL2Reticket,
1091 setProxyAddress,
1092 spawnPoint,
1093 transferPoint,
1094 };