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';
8 deriveNetworkSeedFromUrbitWallet,
9 attemptNetworkSeedDerivation,
12import { useNetwork } from 'store/network';
13import { usePointCursor } from 'store/pointCursor';
14import { EMPTY_POINT, useRollerStore } from 'store/rollerStore';
15import { useWallet } from 'store/wallet';
20} from 'store/storage/roller';
21import { usePointCache } from 'store/pointCache';
22import { getOutgoingPoints } from 'views/Points';
24import { getUpdatedPointMessage, isPlanet, toL1Details } from './utils/point';
25import { convertToInt } from './convertToInt';
26import { isDevelopment, isRopsten } from './flags';
37} from './utils/roller';
38import { ETH_ZERO_ADDR, ROLLER_HOSTS, TEN_SECONDS } from './constants';
48} from '@urbit/roller-api';
50import Point, { PointField, Points } from './types/Point';
51import { useTimerStore } from 'store/timerStore';
52import { ReticketParams, SendL2Params } from './types/L2Transaction';
53import { L1Point } from './types/L1Point';
54import { ddmmmYYYY } from './utils/date';
55import { showNotification } from './utils/notifications';
56import { useWalletConnect } from './useWalletConnect';
58const ONE_SECOND = 1000;
60const inviteTemplate = (
72export default function useRoller() {
80 const { connector } = useWalletConnect();
81 const { pointCursor }: any = usePointCursor();
82 const { web3, contracts }: any = useNetwork();
83 const allPoints: any = usePointCache();
84 const controlledPoints = allPoints?.controlledPoints;
85 const getDetails = allPoints?.getDetails;
87 const { setNextRoll } = useTimerStore();
97 setPendingTransactions,
99 setInviteGeneratingNum,
105 } = useRollerStore();
106 const [config, setConfig] = useState<Config | null>(null);
108 const options: Options = useMemo(() => {
109 const type = isRopsten || !isDevelopment ? 'https' : 'http';
110 const host = isRopsten
111 ? ROLLER_HOSTS.ROPSTEN
114 : ROLLER_HOSTS.MAINNET;
115 const port = isDevelopment && !isRopsten ? 8080 : 443;
116 const path = '/v1/roller';
128 const api = useMemo(() => {
129 return new RollerRPCAPI(options);
135 isCompression: false,
136 encryptionSecret: authToken.getOrElse('default'),
141 const getNextQuotaTime = useCallback(
142 (nextSlice: number) => {
147 setNextQuotaTime(next);
148 getNextQuotaTime(next);
150 .catch(err => console.warn(err));
151 }, nextSlice - new Date().getTime());
153 [api, setNextQuotaTime]
156 const fetchConfig = useCallback(async () => {
162 setNextBatchTime(config.nextBatch);
165 // TODO: more elegant error handling
167 '[fetchConfig:failed] check the roller connection?\n',
171 }, [api, getNextQuotaTime, setNextBatchTime]);
173 const quotaReached = useCallback(() => {
174 if (point.l2Quota <= 0) {
176 `You have reached your weekly L2 transaction limit. You will get another
177 ${config?.rollerQuota} transactions on ${ddmmmYYYY(nextQuotaTime)}.`
181 return point.l2Quota <= 0;
182 }, [config, nextQuotaTime, point, setModalText]);
184 const initPoint = useCallback(
185 async (point: string | number) => {
186 const _wallet = wallet.getOrElse(null);
192 const pointNum = Number(point);
194 const rawDetails = await api.getPoint(pointNum);
195 const l2Quota = isL2Spawn(rawDetails?.dominion)
196 ? await api.getRemainingQuota(pointNum)
198 const details = toL1Details(rawDetails);
203 address: _wallet.address,
209 // Try getting the details from L1
211 const _contracts = need.contracts(contracts);
212 const details = await azimuth.azimuth.getPoint(_contracts, point);
216 address: _wallet.address,
220 // Just return a placeholder Point
221 const details: L1Point = toL1Details();
225 address: _wallet.address,
231 [api, wallet, contracts]
234 const checkForUpdates = useCallback(
241 const interval = setInterval(async () => {
242 const updatedPoint = await initPoint(point);
243 const changedField = !points[point]
245 : updatedPoint.getChangedField(points[point], field);
247 if (!updatedPoint.isPlaceholder && changedField) {
248 updatePoint(updatedPoint);
249 clearInterval(interval);
252 `${message || getUpdatedPointMessage(updatedPoint, changedField)}`
258 [points, initPoint, updatePoint]
261 const spawnPoint = useCallback(
262 async (pointToSpawn: number) => {
263 if (quotaReached()) {
267 const _point = need.point(pointCursor);
268 const _contracts = contracts.getOrElse(null);
269 const _web3 = web3.getOrElse(null);
270 const _wallet = wallet.getOrElse(null);
271 const _authToken = authToken.getOrElse(null);
273 if (!_contracts || !_web3 || !_wallet || !_authToken) {
274 // not using need because we want a custom error
275 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
278 const proxy = point.getSpawnProxy();
280 if (proxy === undefined)
281 throw new Error("Error: Address doesn't match proxy");
283 const nonce = await api.getNonce({
288 await submitL2Transaction({
290 address: _wallet.address,
316 const generateInviteInfo = async (planet: number, _authToken: string) => {
317 const { ticket, inviteWallet } = await generateInviteWallet(
322 return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
325 const getInvites = useCallback(async () => {
327 setInvitesLoading(true);
328 const curPoint: number = Number(need.point(pointCursor));
329 const _authToken = authToken.getOrElse(null);
330 const _contracts = contracts.getOrElse(null);
332 if (_authToken && _contracts) {
333 const spawnedPoints = point.isL2Spawn
334 ? await api.getSpawned(curPoint)
337 const newPending = await api.getPendingByShip(curPoint);
338 setPendingTransactions(newPending);
339 const pendingSpawns = getPendingSpawns(newPending);
341 const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
346 const outgoingPoints = getOutgoingPoints(
349 ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
351 const invitePlanets = spawnedPoints
353 outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
355 .filter((p: number) => !pendingSpawns.has(p))
360 invitePlanets.map(p => inviteTemplate(p))
363 const newInvites: Invite[] = [];
364 // Iterate over all of the stored invites, generating wallet info as necessary
365 const storedInvites = getStoredInvites(ls);
367 for (let i = 0; i < invitePlanets.length; i++) {
368 setInviteGeneratingNum(i + 1);
369 const planet = invitePlanets[i];
370 const storedInvite = storedInvites[planet];
372 storedInvite || (await generateInviteInfo(planet, _authToken));
374 // TODO: check if the invite point's owner still matches the deterministic wallet address
375 const planetInfo = await api.getPoint(planet);
376 setStoredInvite(ls, invite);
379 invite.owner.toLowerCase() ===
380 planetInfo.ownership?.owner?.address ||
381 planetInfo.ownership?.transferProxy?.address !== ETH_ZERO_ADDR
383 newInvites.push(invite);
384 updateInvite(curPoint, invite);
386 removeInvite(curPoint, invite.planet);
390 setStoredInvite(ls, invite);
393 setInvites(curPoint, newInvites);
394 setInvitesLoading(false);
398 console.warn('ERROR GETTING INVITES', error);
412 setInviteGeneratingNum,
413 setPendingTransactions,
417 const generateInviteCodes = useCallback(
418 async (numInvites: number) => {
419 const _point = need.point(pointCursor);
420 const _contracts = contracts.getOrElse(null);
421 const _web3 = web3.getOrElse(null);
422 const _wallet = wallet.getOrElse(null);
423 const _authToken = authToken.getOrElse(null);
424 const _details = getDetails(_point);
426 if (!_contracts || !_web3 || !_wallet || !_authToken || !_details) {
427 // not using need because we want a custom error
428 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
431 // TODO: remove when UI disables management proxy from spawning invites
432 // if (Just.hasInstance(authMnemonic)) {
434 // "Auth key Error: A management mnemonic can't create invite codes"
437 let planets: UnspawnedPoints = await api.getUnspawned(_point);
439 const pendingTxs = await api.getPendingByShip(_point);
441 const pendingSpawns = getPendingSpawns(pendingTxs);
443 planets = planets.filter((point: number) => !pendingSpawns.has(point));
445 const invites: Invite[] = [];
447 const proxy = point.getSpawnProxy();
449 if (proxy === undefined)
450 throw new Error("Error: Address doesn't match proxy");
452 const nonce = await api.getNonce({
457 for (let i = 0; i < numInvites && planets[i]; i++) {
458 setInviteGeneratingNum(i + 1);
459 const planet = planets[i];
460 const nonceInc = i + nonce;
462 const { ticket, inviteWallet } = await generateInviteWallet(
467 await submitL2Transaction({
469 address: _wallet.address,
474 pointToSpawn: planet,
481 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
485 await submitL2Transaction({
492 type: 'configureKeys',
498 await registerProxyAddress(
505 inviteWallet.management.keys.address,
511 await submitL2Transaction({
516 type: 'transferPoint',
518 address: inviteWallet.ownership.keys.address,
528 owner: inviteWallet.ownership.keys.address,
532 setStoredInvites(ls, invites);
535 `Your invite${invites.length > 1 ? 's have' : ' has'} been generated!`
551 setInviteGeneratingNum,
555 const getPoints = useCallback(
556 async (proxy: Proxy, address: EthAddress) => {
557 const points: Ship[] =
559 ? await api.getOwnedPoints(address)
561 ? await api.getManagerFor(address)
563 ? await api.getVotingFor(address)
564 : proxy === 'transfer'
565 ? await api.getTransferringFor(address)
567 ? await api.getSpawningFor(address)
575 const getPendingTransactions = useCallback(async () => {
577 if (!point.isDefault) {
578 const newPending = await api.getPendingByShip(point.value);
579 setPendingTransactions(newPending);
582 console.warn('ERROR GETTING PENDING', error);
584 }, [api, setPendingTransactions, point]);
586 interface ConfigureKeysParams {
588 customNetworkSeed?: string;
591 const configureNetworkingKeys = useCallback(
592 async ({ breach, customNetworkSeed }: ConfigureKeysParams) => {
593 if (quotaReached()) {
596 const _wallet = wallet.getOrElse(null);
597 const _web3 = web3.getOrElse(null);
598 const _details = getDetails(point.value);
599 if (!_wallet || !_web3 || !_details || point.isDefault) {
600 // not using need because we want a custom error
601 throw new Error('Internal Error: Missing Wallet/Details');
604 const proxy = point.getManagerProxy();
606 if (proxy === undefined)
607 throw new Error("Error: Address doesn't match proxy");
609 const nonce = await api.getNonce({
614 const nextRevision = point.keyRevisionNumber + 1;
615 const networkSeed = customNetworkSeed
617 : await attemptNetworkSeedDerivation({
624 revision: nextRevision,
626 const txHash = await submitL2Transaction({
633 type: 'configureKeys',
640 return api.getPendingTx(txHash);
642 [// eslint-disable-line
656 const performL2Reticket = async ({
662 }: ReticketParams) => {
663 const azimuthPoint = await api.getPoint(point);
665 const _web3 = web3.getOrElse(null);
666 let nonce = await api.getNonce({ ship: point, proxy });
670 // 1. Update networking keys
671 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
672 const nextRevision = networkRevision + 1;
673 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
677 const configureKeysRequest = await submitL2Transaction({
685 type: 'configureKeys',
690 requests.push(configureKeysRequest);
692 // 2. Set Management Proxy
693 const registerMgmtRequest = await registerProxyAddress(
706 requests.push(registerMgmtRequest);
708 // 3. Set Spawn Proxy
709 if (!isPlanet(Number(point))) {
710 const registerSpawnRequest = await registerProxyAddress(
717 toWallet.spawn.keys.address,
723 requests.push(registerSpawnRequest);
726 const transferTxRequest = await submitL2Transaction({
731 type: 'transferPoint',
738 requests.push(transferTxRequest);
740 const hashes = await Promise.all(requests);
744 const setProxyAddress = useCallback(
745 async (proxyType: Proxy, address: EthAddress) => {
746 if (quotaReached()) {
750 const _wallet = wallet.getOrElse(null);
751 const _web3 = web3.getOrElse(null);
754 // not using need because we want a custom error
755 throw new Error('Internal Error: Missing Wallet');
758 if (point.isDefault) {
759 // not using need because we want a custom error
760 throw new Error('Internal Error: Invalid point');
763 const proxy = point.getAddressProxy(proxyType);
765 if (proxy === undefined)
766 throw new Error("Error: Address doesn't match expected proxy");
768 const nonce = await api.getNonce({ ship: point.value, proxy });
770 const txHash = await registerProxyAddress(
783 return api.getPendingTx(txHash);
785 [api, connector, point, wallet, web3, walletType, quotaReached]
788 const adoptPoint = useCallback(
789 async (ship: Ship) => {
790 if (quotaReached()) {
794 const _wallet = wallet.getOrElse(null);
796 const proxy = point.getManagerProxy();
798 if (proxy === undefined)
799 throw new Error("Error: Address doesn't match expected proxy");
801 const nonce = await api.getNonce({ ship: point.value, proxy });
802 const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
804 return api.getPendingTx(txHash);
806 [api, point, wallet, quotaReached]
809 const kickPoint = useCallback(
810 async (ship: Ship) => {
811 if (quotaReached()) {
815 if (point.isDefault) {
816 throw new Error('Internal Error: point is default');
819 const sponsor = point.value;
820 const _wallet = wallet.getOrElse(null);
821 const proxy = point.getManagerProxy();
823 if (proxy === undefined)
824 throw new Error("Error: Address doesn't match expected proxy");
826 const nonce = await api.getNonce({ ship: sponsor, proxy });
827 const txHash = await detach(api, _wallet, sponsor, proxy, nonce, ship);
829 return api.getPendingTx(txHash);
831 [api, point, wallet, quotaReached]
834 const rejectPoint = useCallback(
835 async (ship: Ship) => {
836 if (quotaReached()) {
840 if (point.isDefault) {
841 throw new Error('Internal Error: point is default');
844 const sponsor = point.value;
845 const _wallet = wallet.getOrElse(null);
846 const proxy = point.getManagerProxy();
848 if (proxy === undefined)
849 throw new Error("Error: Address doesn't match expected proxy");
851 const nonce = await api.getNonce({ ship: sponsor, proxy });
852 const txHash = await reject(api, _wallet, sponsor, proxy, nonce, ship);
854 return api.getPendingTx(txHash);
856 [api, point, wallet, quotaReached]
859 const transferPoint = useCallback(
860 async (address: EthAddress, reset?: boolean) => {
861 if (quotaReached()) {
865 const _point = need.point(pointCursor);
866 const _wallet = wallet.getOrElse(null);
867 const _web3 = web3.getOrElse(null);
870 // not using need because we want a custom error
871 throw new Error('Internal Error: Missing Wallet');
874 const proxy = point.getTransferProxy();
876 if (proxy === undefined)
877 throw new Error("Error: Address doesn't match proxy");
879 const nonce = await api.getNonce({ ship: _point, proxy });
881 const txHash = await submitL2Transaction({
888 type: 'transferPoint',
895 return api.getPendingTx(txHash);
897 [api, connector, point, pointCursor, wallet, web3, walletType, quotaReached]
900 const sendL2Transaction = useCallback(
901 async (params: SendL2Params) => {
902 if (quotaReached()) {
906 const _wallet = wallet.getOrElse(null);
907 const _web3 = web3.getOrElse(null);
909 if (!_wallet || point.isDefault) {
910 // not using need because we want a custom error
911 throw new Error('Internal Error: Missing Wallet or Point Info');
914 return submitL2Transaction({
924 [api, connector, point, wallet, web3, walletType, quotaReached]
927 const changeSponsor = useCallback(
928 async (newSponsor: number) => {
929 if (point.isDefault) {
930 throw new Error('Internal Error: point is default');
933 const curPoint = point.value;
934 const _wallet = wallet.getOrElse(null);
937 // not using need because we want a custom error
938 throw new Error('Internal Error: Missing Wallet');
941 const proxy = point.getManagerProxy();
943 if (proxy === undefined)
944 throw new Error("Error: Address doesn't match proxy");
946 const nonce = await api.getNonce({ ship: curPoint, proxy });
948 return sendL2Transaction({
956 [api, point, sendL2Transaction, wallet]
959 const getPointsDetails = useCallback(
961 ownedPoints: number[],
962 incomingPoints: number[],
963 managingPoints: number[],
964 votingPoints: number[],
965 spawningPoints: number[]
969 await Promise.all(ownedPoints.map(initPoint)),
970 await Promise.all(incomingPoints.map(initPoint)),
971 await Promise.all(managingPoints.map(initPoint)),
972 await Promise.all(votingPoints.map(initPoint)),
973 await Promise.all(spawningPoints.map(initPoint)),
977 allPoints.reduce((acc: Points, cur: (Point | undefined)[]) => {
990 [setPoints, initPoint]
993 // On load, get initial config
1000 }, [config, fetchConfig]);
1003 const time = ONE_SECOND;
1005 const interval = setInterval(() => {
1006 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
1007 setNextRoll(nextRoll);
1009 if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
1010 api.getRollerConfig().then(response => {
1011 setNextBatchTime(response.nextBatch);
1014 getPendingTransactions();
1017 if (!point.isDefault) {
1018 getInvites(); // This will also get pending txns
1020 }, TEN_SECONDS); // Should this be more like a minute?
1024 return () => clearInterval(interval);
1025 }, [point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
1033 configureNetworkingKeys,
1034 generateInviteCodes,
1038 getPendingTransactions,
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list