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';
29} from './utils/proxy';
30import { isPlanet, toL1Details } from './utils/point';
31import { convertToInt } from './convertToInt';
32import { isDevelopment, isRopsten } from './flags';
42} from './utils/roller';
43import { ROLLER_HOSTS } from './constants';
53} from '@urbit/roller-api';
55import Point, { Points, Relationship } from './types/Point';
56import { useTimerStore } from 'store/timerStore';
57import { ReticketParams, SendL2Params } from './types/L2Transaction';
59const ONE_SECOND = 1000;
61const inviteTemplate = (
73export default function useRoller() {
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();
95 setPendingTransactions,
98 setInviteGeneratingNum,
101 } = useRollerStore();
102 const [config, setConfig] = useState<Config | null>(null);
104 const options: Options = useMemo(() => {
105 const type = isRopsten || !isDevelopment ? 'https' : 'http';
106 const host = isRopsten
107 ? ROLLER_HOSTS.ROPSTEN
110 : ROLLER_HOSTS.MAINNET;
111 const port = isDevelopment && !isRopsten ? 8080 : 443;
112 const path = '/v1/roller';
124 const api = useMemo(() => {
125 return new RollerRPCAPI(options);
131 isCompression: false,
132 encryptionSecret: authToken.getOrElse('default'),
137 const fetchConfig = useCallback(async () => {
142 setNextBatchTime(response.nextBatch);
145 // TODO: more elegant error handling
147 '[fetchConfig:failed] check the roller connection?\n',
151 }, [api]); // eslint-disable-line react-hooks/exhaustive-deps
153 const generateInviteCodes = useCallback(
154 async (numInvites: number) => {
155 const _point = need.point(pointCursor);
156 const _contracts = contracts.getOrElse(null);
157 const _web3 = web3.getOrElse(null);
158 const _wallet = wallet.getOrElse(null);
159 const _authToken = authToken.getOrElse(null);
160 const _details = getDetails(_point);
162 if (!_contracts || !_web3 || !_wallet || !_authToken || !_details) {
163 // not using need because we want a custom error
164 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
167 // TODO: remove when UI disables management proxy from spawning invites
168 // if (Just.hasInstance(authMnemonic)) {
170 // "Auth key Error: A management mnemonic can't create invite codes"
173 let planets: UnspawnedPoints = await api.getUnspawned(_point);
175 const pendingTxs = await api.getPendingByShip(_point);
177 const pendingSpawns = getPendingSpawns(pendingTxs);
179 planets = planets.filter((point: number) => !pendingSpawns.has(point));
181 const starInfo = await api.getPoint(_point);
182 const invites: Invite[] = [];
184 const proxy = getSpawnProxy(starInfo.ownership!, _wallet.address);
186 if (proxy === undefined)
187 throw new Error("Error: Address doesn't match proxy");
189 const nonce = await api.getNonce({
194 for (let i = 0; i < numInvites && planets[i]; i++) {
195 setInviteGeneratingNum(i + 1);
196 const planet = planets[i];
197 const nonceInc = i + nonce;
199 const { ticket, inviteWallet } = await generateInviteWallet(
204 await submitL2Transaction({
206 address: _wallet.address,
211 pointToSpawn: planet,
217 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
221 await submitL2Transaction({
228 type: 'configureKeys',
233 await registerProxyAddress(
240 inviteWallet.management.keys.address,
245 await submitL2Transaction({
250 type: 'transferPoint',
252 address: inviteWallet.ownership.keys.address,
261 owner: inviteWallet.ownership.keys.address,
265 setStoredInvites(ls, invites);
277 setInviteGeneratingNum,
281 const getPoints = useCallback(
282 async (proxy: Proxy, address: EthAddress) => {
283 const points: Ship[] =
285 ? await api.getOwnedPoints(address)
287 ? await api.getManagerFor(address)
289 ? await api.getVotingFor(address)
290 : proxy === 'transfer'
291 ? await api.getTransferringFor(address)
293 ? await api.getSpawningFor(address)
301 const getPendingTransactions = useCallback(async () => {
303 const curPoint = Number(need.point(pointCursor));
304 const newPending = await api.getPendingByShip(curPoint);
306 setPendingTransactions(newPending);
308 console.warn('ERROR GETTING PENDING', error);
310 }, [api, setPendingTransactions, pointCursor]);
312 const generateInviteInfo = async (planet: number, _authToken: string) => {
313 const { ticket, inviteWallet } = await generateInviteWallet(
318 return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
321 const getInvites = useCallback(
322 async (isL2: boolean, getAll = false) => {
324 const curPoint: number = Number(need.point(pointCursor));
325 setInvites(invitePoints.map(p => inviteTemplate(p)));
327 const _authToken = authToken.getOrElse(null);
328 const _contracts = contracts.getOrElse(null);
330 if (_authToken && _contracts) {
331 const spawnedPoints = isL2 ? await api.getSpawned(curPoint) : [];
333 const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
338 const outgoingPoints = getOutgoingPoints(
341 ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
343 const invitePlanets = spawnedPoints.concat(
344 outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
347 const newInvites: Invite[] = [];
348 // Iterate over all of the stored invites, generating wallet info as necessary
349 const storedInvites = getStoredInvites(ls);
351 for (let i = 0; i < invitePlanets.length; i++) {
352 setInviteGeneratingNum(i + 1);
353 const planet = invitePlanets[i];
355 storedInvites[planet] ||
356 (await generateInviteInfo(planet, _authToken));
358 // TODO: check if the invite point's owner still matches the deterministic wallet address
359 const planetInfo = await api.getPoint(planet);
360 if (invite.owner === planetInfo.ownership?.transferProxy?.address) {
361 newInvites.push(invite);
362 updateInvite(invite);
363 setStoredInvite(ls, invite);
365 removeInvite(planet);
368 setStoredInvites(ls, newInvites);
369 setInvites(newInvites);
374 console.warn('ERROR GETTING INVITES', error);
388 setInviteGeneratingNum,
393 const getNumInvites = useCallback(
394 async (isL2: boolean) => {
396 const curPoint: number = Number(need.point(pointCursor));
397 const _authToken = authToken.getOrElse(null);
398 const _contracts = contracts.getOrElse(null);
400 if (_authToken && _contracts) {
401 let spawnedPoints = isL2 ? await api.getSpawned(curPoint) : [];
403 const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
408 const newPending = await api.getPendingByShip(curPoint);
409 const pendingSpawns = getPendingSpawns(newPending);
411 const outgoingPoints = getOutgoingPoints(
414 ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
416 const invitePoints = spawnedPoints
418 outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
420 .filter((p: number) => !pendingSpawns.has(p));
422 setPendingTransactions(newPending);
423 setInvitePoints(invitePoints);
441 setPendingTransactions,
445 interface ConfigureKeysParams {
447 customNetworkSeed?: string;
450 const configureNetworkingKeys = async ({
453 }: ConfigureKeysParams) => {
454 const _point = need.point(pointCursor);
455 const azimuthPoint = await api.getPoint(_point);
456 const _wallet = wallet.getOrElse(null);
457 const _web3 = web3.getOrElse(null);
458 const _details = getDetails(_point);
459 if (!_wallet || !_details || !_web3) {
460 // not using need because we want a custom error
461 throw new Error('Internal Error: Missing Wallet/Details');
464 const proxy = getManagerProxy(azimuthPoint.ownership!, _wallet.address);
466 if (proxy === undefined)
467 throw new Error("Error: Address doesn't match proxy");
469 const nonce = await api.getNonce({
474 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
475 const nextRevision = networkRevision + 1;
476 const networkSeed = customNetworkSeed
478 : await attemptNetworkSeedDerivation({
485 revision: nextRevision,
487 const txHash = await submitL2Transaction({
494 type: 'configureKeys',
499 const pendingTx = await api.getPendingTx(txHash);
504 const performL2Reticket = async ({
510 }: ReticketParams) => {
511 const azimuthPoint = await api.getPoint(point);
513 const _web3 = web3.getOrElse(null);
514 let nonce = await api.getNonce({ ship: point, proxy });
518 // 1. Update networking keys
519 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
520 const nextRevision = networkRevision + 1;
521 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
525 const configureKeysRequest = await submitL2Transaction({
533 type: 'configureKeys',
537 requests.push(configureKeysRequest);
539 // 2. Set Management Proxy
540 const registerMgmtRequest = await registerProxyAddress(
552 requests.push(registerMgmtRequest);
554 // 3. Set Spawn Proxy
555 if (!isPlanet(Number(point))) {
556 const registerSpawnRequest = await registerProxyAddress(
563 toWallet.spawn.keys.address,
568 requests.push(registerSpawnRequest);
571 const transferTxRequest = await submitL2Transaction({
576 type: 'transferPoint',
582 requests.push(transferTxRequest);
584 const hashes = await Promise.all(requests);
588 const setProxyAddress = useCallback(
589 async (proxyType: Proxy, address: EthAddress) => {
590 const _point = need.point(pointCursor);
591 const _wallet = wallet.getOrElse(null);
592 const _web3 = web3.getOrElse(null);
595 // not using need because we want a custom error
596 throw new Error('Internal Error: Missing Wallet');
599 const pointDetails = await api.getPoint(_point);
600 const ownership = pointDetails.ownership!;
601 const proxy = getAddressProxy(ownership, _wallet.address, proxyType);
603 if (proxy === undefined)
604 throw new Error("Error: Address doesn't match expected proxy");
606 const nonce = await api.getNonce({ ship: _point, proxy });
608 const txHash = await registerProxyAddress(
620 const pendingTx = await api.getPendingTx(txHash);
624 [api, pointCursor, wallet, web3, walletType]
627 const adoptPoint = useCallback(
628 async (point: Ship) => {
629 const sponsor = need.point(pointCursor);
630 const _wallet = wallet.getOrElse(null);
632 const sponsorInfo = await api.getPoint(sponsor);
633 const ownership = sponsorInfo.ownership!;
634 const proxy = getManagerProxy(ownership, _wallet.address);
636 if (proxy === undefined)
637 throw new Error("Error: Address doesn't match expected proxy");
639 const nonce = await api.getNonce({ ship: sponsor, proxy });
640 const txHash = await adopt(api, _wallet, sponsor, proxy, nonce, point);
641 const pendingTx = await api.getPendingTx(txHash);
645 [api, pointCursor, wallet]
648 const kickPoint = useCallback(
649 async (point: Ship) => {
650 const sponsor = need.point(pointCursor);
651 const _wallet = wallet.getOrElse(null);
653 const sponsorInfo = await api.getPoint(sponsor);
654 const ownership = sponsorInfo.ownership!;
655 const proxy = getManagerProxy(ownership, _wallet.address);
657 if (proxy === undefined)
658 throw new Error("Error: Address doesn't match expected proxy");
660 const nonce = await api.getNonce({ ship: sponsor, proxy });
661 const txHash = await detach(api, _wallet, sponsor, proxy, nonce, point);
662 const pendingTx = await api.getPendingTx(txHash);
666 [api, pointCursor, wallet]
669 const rejectPoint = useCallback(
670 async (point: Ship) => {
671 const sponsor = need.point(pointCursor);
672 const _wallet = wallet.getOrElse(null);
674 const sponsorInfo = await api.getPoint(sponsor);
675 const ownership = sponsorInfo.ownership!;
676 const proxy = getManagerProxy(ownership, _wallet.address);
678 if (proxy === undefined)
679 throw new Error("Error: Address doesn't match expected proxy");
681 const nonce = await api.getNonce({ ship: sponsor, proxy });
682 const txHash = await reject(api, _wallet, sponsor, proxy, nonce, point);
683 const pendingTx = await api.getPendingTx(txHash);
687 [api, pointCursor, wallet]
690 const transferPoint = useCallback(
691 async (address: EthAddress, reset?: boolean) => {
692 const _point = need.point(pointCursor);
693 const _wallet = wallet.getOrElse(null);
694 const _web3 = web3.getOrElse(null);
697 // not using need because we want a custom error
698 throw new Error('Internal Error: Missing Wallet');
701 const pointDetails = await api.getPoint(_point);
702 const proxy = getTransferProxy(pointDetails.ownership!, _wallet.address);
704 if (proxy === undefined)
705 throw new Error("Error: Address doesn't match proxy");
707 const nonce = await api.getNonce({ ship: _point, proxy });
709 const txHash = await submitL2Transaction({
716 type: 'transferPoint',
722 const pendingTx = await api.getPendingTx(txHash);
726 [api, pointCursor, wallet, web3, walletType]
729 const sendL2Transaction = useCallback(
730 async (params: SendL2Params) => {
731 const _point = need.point(pointCursor);
732 const _wallet = wallet.getOrElse(null);
733 const _web3 = web3.getOrElse(null);
736 // not using need because we want a custom error
737 throw new Error('Internal Error: Missing Wallet');
740 return submitL2Transaction({
749 [api, pointCursor, wallet, web3, walletType]
752 const changeSponsor = useCallback(
753 async (newSponsor: number) => {
754 const _wallet = wallet.getOrElse(null);
755 const _point = need.point(pointCursor);
758 // not using need because we want a custom error
759 throw new Error('Internal Error: Missing Wallet');
762 const pointDetails = await api.getPoint(_point);
763 const proxy = getManagerProxy(pointDetails.ownership!, _wallet.address);
765 if (proxy === undefined)
766 throw new Error("Error: Address doesn't match proxy");
768 const nonce = await api.getNonce({ ship: _point, proxy });
778 [api, sendL2Transaction, wallet, pointCursor]
781 const initPoint = useCallback(
782 (relationship: Relationship) => async (point: string | number) => {
783 const _wallet = wallet.getOrElse(null);
789 const pointNum = Number(point);
790 const rawDetails = await api.getPoint(pointNum);
791 const details = toL1Details(rawDetails);
793 return new Point(pointNum, relationship, details, _wallet.address);
801 const getPointDetails = useCallback(
803 ownedPoints: number[],
804 incomingPoints: number[],
805 managingPoints: number[],
806 votingPoints: number[],
807 spawningPoints: number[]
811 await Promise.all(ownedPoints.map(initPoint(Relationship.own))),
813 incomingPoints.map(initPoint(Relationship.transfer))
815 await Promise.all(managingPoints.map(initPoint(Relationship.manage))),
816 await Promise.all(votingPoints.map(initPoint(Relationship.vote))),
817 await Promise.all(spawningPoints.map(initPoint(Relationship.spawn))),
821 allPoints.reduce((acc: Points, cur: (Point | undefined)[]) => {
834 [setPoints, initPoint]
837 // On load, get initial config
844 }, [config, fetchConfig]);
847 const interval = setInterval(() => {
848 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
849 setNextRoll(nextRoll);
851 if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
852 api.getRollerConfig().then(response => {
853 setNextBatchTime(response.nextBatch);
854 getNumInvites(!!point?.isL2);
859 return () => clearInterval(interval);
860 }, [nextBatchTime, getPendingTransactions, point]); // eslint-disable-line react-hooks/exhaustive-deps
867 configureNetworkingKeys,
872 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