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 { ETH_ZERO_ADDR, 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';
58import { L1Point } from './types/L1Point';
60const ONE_SECOND = 1000;
62const inviteTemplate = (
74export default function useRoller() {
82 const { pointCursor }: any = usePointCursor();
83 const { web3, contracts }: any = useNetwork();
84 const allPoints: any = usePointCache();
85 const controlledPoints = allPoints?.controlledPoints;
86 const getDetails = allPoints?.getDetails;
88 const { setNextRoll } = useTimerStore();
95 setPendingTransactions,
97 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 () => {
141 // console.log('CONFIG', response)
143 setNextBatchTime(response.nextBatch);
146 // TODO: more elegant error handling
148 '[fetchConfig:failed] check the roller connection?\n',
152 }, [api]); // eslint-disable-line react-hooks/exhaustive-deps
154 const generateInviteCodes = useCallback(
155 async (numInvites: number) => {
156 const _point = need.point(pointCursor);
157 const _contracts = contracts.getOrElse(null);
158 const _web3 = web3.getOrElse(null);
159 const _wallet = wallet.getOrElse(null);
160 const _authToken = authToken.getOrElse(null);
161 const _details = getDetails(_point);
163 if (!_contracts || !_web3 || !_wallet || !_authToken || !_details) {
164 // not using need because we want a custom error
165 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
168 // TODO: remove when UI disables management proxy from spawning invites
169 // if (Just.hasInstance(authMnemonic)) {
171 // "Auth key Error: A management mnemonic can't create invite codes"
174 let planets: UnspawnedPoints = await api.getUnspawned(_point);
176 const pendingTxs = await api.getPendingByShip(_point);
178 const pendingSpawns = getPendingSpawns(pendingTxs);
180 planets = planets.filter((point: number) => !pendingSpawns.has(point));
182 const starInfo = await api.getPoint(_point);
183 const invites: Invite[] = [];
185 const proxy = getSpawnProxy(starInfo.ownership!, _wallet.address);
187 if (proxy === undefined)
188 throw new Error("Error: Address doesn't match proxy");
190 const nonce = await api.getNonce({
195 for (let i = 0; i < numInvites && planets[i]; i++) {
196 setInviteGeneratingNum(i + 1);
197 const planet = planets[i];
198 const nonceInc = i + nonce;
200 const { ticket, inviteWallet } = await generateInviteWallet(
205 await submitL2Transaction({
207 address: _wallet.address,
212 pointToSpawn: planet,
218 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
222 await submitL2Transaction({
229 type: 'configureKeys',
234 await registerProxyAddress(
241 inviteWallet.management.keys.address,
246 await submitL2Transaction({
251 type: 'transferPoint',
253 address: inviteWallet.ownership.keys.address,
262 owner: inviteWallet.ownership.keys.address,
266 setStoredInvites(ls, invites);
278 setInviteGeneratingNum,
282 const getPoints = useCallback(
283 async (proxy: Proxy, address: EthAddress) => {
284 const points: Ship[] =
286 ? await api.getOwnedPoints(address)
288 ? await api.getManagerFor(address)
290 ? await api.getVotingFor(address)
291 : proxy === 'transfer'
292 ? await api.getTransferringFor(address)
294 ? await api.getSpawningFor(address)
302 const getPendingTransactions = useCallback(async () => {
304 const curPoint = Number(need.point(pointCursor));
305 const newPending = await api.getPendingByShip(curPoint);
307 setPendingTransactions(newPending);
309 console.warn('ERROR GETTING PENDING', error);
311 }, [api, setPendingTransactions, pointCursor]);
313 const generateInviteInfo = async (planet: number, _authToken: string) => {
314 const { ticket, inviteWallet } = await generateInviteWallet(
319 return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
322 const getInvites = useCallback(async () => {
324 setInvitesLoading(true);
325 const curPoint: number = Number(need.point(pointCursor));
326 const _authToken = authToken.getOrElse(null);
327 const _contracts = contracts.getOrElse(null);
329 if (_authToken && _contracts) {
330 const spawnedPoints = point.isL2Spawn
331 ? await api.getSpawned(curPoint)
334 const newPending = await api.getPendingByShip(curPoint);
335 setPendingTransactions(newPending);
336 const pendingSpawns = getPendingSpawns(newPending);
338 const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
343 const outgoingPoints = getOutgoingPoints(
346 ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
348 const invitePlanets = spawnedPoints
350 outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
352 .filter((p: number) => !pendingSpawns.has(p))
357 invitePlanets.map(p => inviteTemplate(p))
360 const newInvites: Invite[] = [];
361 // Iterate over all of the stored invites, generating wallet info as necessary
362 const storedInvites = getStoredInvites(ls);
364 for (let i = 0; i < invitePlanets.length; i++) {
365 setInviteGeneratingNum(i + 1);
366 const planet = invitePlanets[i];
367 const storedInvite = storedInvites[planet];
369 storedInvite || (await generateInviteInfo(planet, _authToken));
371 // TODO: check if the invite point's owner still matches the deterministic wallet address
372 const planetInfo = await api.getPoint(planet);
373 setStoredInvite(ls, invite);
376 invite.owner.toLowerCase() ===
377 planetInfo.ownership?.owner?.address ||
378 planetInfo.ownership?.transferProxy?.address !== ETH_ZERO_ADDR
380 newInvites.push(invite);
381 updateInvite(curPoint, invite);
383 removeInvite(curPoint, invite.planet);
387 setStoredInvite(ls, invite);
390 setInvites(curPoint, newInvites);
391 setInvitesLoading(false);
395 console.warn('ERROR GETTING INVITES', error);
409 setInviteGeneratingNum,
410 setPendingTransactions,
414 interface ConfigureKeysParams {
416 customNetworkSeed?: string;
419 const configureNetworkingKeys = async ({
422 }: ConfigureKeysParams) => {
423 const _point = need.point(pointCursor);
424 const azimuthPoint = await api.getPoint(_point);
425 const _wallet = wallet.getOrElse(null);
426 const _web3 = web3.getOrElse(null);
427 const _details = getDetails(_point);
428 if (!_wallet || !_details || !_web3) {
429 // not using need because we want a custom error
430 throw new Error('Internal Error: Missing Wallet/Details');
433 const proxy = getManagerProxy(azimuthPoint.ownership!, _wallet.address);
435 if (proxy === undefined)
436 throw new Error("Error: Address doesn't match proxy");
438 const nonce = await api.getNonce({
443 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
444 const nextRevision = networkRevision + 1;
445 const networkSeed = customNetworkSeed
447 : await attemptNetworkSeedDerivation({
454 revision: nextRevision,
456 const txHash = await submitL2Transaction({
463 type: 'configureKeys',
468 const pendingTx = await api.getPendingTx(txHash);
473 const performL2Reticket = async ({
479 }: ReticketParams) => {
480 const azimuthPoint = await api.getPoint(point);
482 const _web3 = web3.getOrElse(null);
483 let nonce = await api.getNonce({ ship: point, proxy });
487 // 1. Update networking keys
488 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
489 const nextRevision = networkRevision + 1;
490 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
494 const configureKeysRequest = await submitL2Transaction({
502 type: 'configureKeys',
506 requests.push(configureKeysRequest);
508 // 2. Set Management Proxy
509 const registerMgmtRequest = await registerProxyAddress(
521 requests.push(registerMgmtRequest);
523 // 3. Set Spawn Proxy
524 if (!isPlanet(Number(point))) {
525 const registerSpawnRequest = await registerProxyAddress(
532 toWallet.spawn.keys.address,
537 requests.push(registerSpawnRequest);
540 const transferTxRequest = await submitL2Transaction({
545 type: 'transferPoint',
551 requests.push(transferTxRequest);
553 const hashes = await Promise.all(requests);
557 const setProxyAddress = useCallback(
558 async (proxyType: Proxy, address: EthAddress) => {
559 const _point = need.point(pointCursor);
560 const _wallet = wallet.getOrElse(null);
561 const _web3 = web3.getOrElse(null);
564 // not using need because we want a custom error
565 throw new Error('Internal Error: Missing Wallet');
568 const pointDetails = await api.getPoint(_point);
569 const ownership = pointDetails.ownership!;
570 const proxy = getAddressProxy(ownership, _wallet.address, proxyType);
572 if (proxy === undefined)
573 throw new Error("Error: Address doesn't match expected proxy");
575 const nonce = await api.getNonce({ ship: _point, proxy });
577 const txHash = await registerProxyAddress(
589 const pendingTx = await api.getPendingTx(txHash);
593 [api, pointCursor, wallet, web3, walletType]
596 const adoptPoint = useCallback(
597 async (point: Ship) => {
598 const sponsor = need.point(pointCursor);
599 const _wallet = wallet.getOrElse(null);
601 const sponsorInfo = await api.getPoint(sponsor);
602 const ownership = sponsorInfo.ownership!;
603 const proxy = getManagerProxy(ownership, _wallet.address);
605 if (proxy === undefined)
606 throw new Error("Error: Address doesn't match expected proxy");
608 const nonce = await api.getNonce({ ship: sponsor, proxy });
609 const txHash = await adopt(api, _wallet, sponsor, proxy, nonce, point);
610 const pendingTx = await api.getPendingTx(txHash);
614 [api, pointCursor, wallet]
617 const kickPoint = useCallback(
618 async (point: Ship) => {
619 const sponsor = need.point(pointCursor);
620 const _wallet = wallet.getOrElse(null);
622 const sponsorInfo = await api.getPoint(sponsor);
623 const ownership = sponsorInfo.ownership!;
624 const proxy = getManagerProxy(ownership, _wallet.address);
626 if (proxy === undefined)
627 throw new Error("Error: Address doesn't match expected proxy");
629 const nonce = await api.getNonce({ ship: sponsor, proxy });
630 const txHash = await detach(api, _wallet, sponsor, proxy, nonce, point);
631 const pendingTx = await api.getPendingTx(txHash);
635 [api, pointCursor, wallet]
638 const rejectPoint = useCallback(
639 async (point: Ship) => {
640 const sponsor = need.point(pointCursor);
641 const _wallet = wallet.getOrElse(null);
643 const sponsorInfo = await api.getPoint(sponsor);
644 const ownership = sponsorInfo.ownership!;
645 const proxy = getManagerProxy(ownership, _wallet.address);
647 if (proxy === undefined)
648 throw new Error("Error: Address doesn't match expected proxy");
650 const nonce = await api.getNonce({ ship: sponsor, proxy });
651 const txHash = await reject(api, _wallet, sponsor, proxy, nonce, point);
652 const pendingTx = await api.getPendingTx(txHash);
656 [api, pointCursor, wallet]
659 const transferPoint = useCallback(
660 async (address: EthAddress, reset?: boolean) => {
661 const _point = need.point(pointCursor);
662 const _wallet = wallet.getOrElse(null);
663 const _web3 = web3.getOrElse(null);
666 // not using need because we want a custom error
667 throw new Error('Internal Error: Missing Wallet');
670 const pointDetails = await api.getPoint(_point);
671 const proxy = getTransferProxy(pointDetails.ownership!, _wallet.address);
673 if (proxy === undefined)
674 throw new Error("Error: Address doesn't match proxy");
676 const nonce = await api.getNonce({ ship: _point, proxy });
678 const txHash = await submitL2Transaction({
685 type: 'transferPoint',
691 const pendingTx = await api.getPendingTx(txHash);
695 [api, pointCursor, wallet, web3, walletType]
698 const sendL2Transaction = useCallback(
699 async (params: SendL2Params) => {
700 const _point = need.point(pointCursor);
701 const _wallet = wallet.getOrElse(null);
702 const _web3 = web3.getOrElse(null);
705 // not using need because we want a custom error
706 throw new Error('Internal Error: Missing Wallet');
709 return submitL2Transaction({
718 [api, pointCursor, wallet, web3, walletType]
721 const changeSponsor = useCallback(
722 async (newSponsor: number) => {
723 const _wallet = wallet.getOrElse(null);
724 const _point = need.point(pointCursor);
727 // not using need because we want a custom error
728 throw new Error('Internal Error: Missing Wallet');
731 const pointDetails = await api.getPoint(_point);
732 const proxy = getManagerProxy(pointDetails.ownership!, _wallet.address);
734 if (proxy === undefined)
735 throw new Error("Error: Address doesn't match proxy");
737 const nonce = await api.getNonce({ ship: _point, proxy });
747 [api, sendL2Transaction, wallet, pointCursor]
750 const initPoint = useCallback(
751 (relationship: Relationship) => async (point: string | number) => {
752 const _wallet = wallet.getOrElse(null);
758 const pointNum = Number(point);
760 const rawDetails = await api.getPoint(pointNum);
761 const details = toL1Details(rawDetails);
763 return new Point(pointNum, relationship, details, _wallet.address);
767 // try getting the details from L1
769 const details: L1Point = getDetails(point);
770 return new Point(pointNum, relationship, details, _wallet.address);
772 const details: L1Point = toL1Details();
773 return new Point(pointNum, relationship, details, _wallet.address);
777 [api, wallet, getDetails]
780 const getPointDetails = useCallback(
782 ownedPoints: number[],
783 incomingPoints: number[],
784 managingPoints: number[],
785 votingPoints: number[],
786 spawningPoints: number[]
790 await Promise.all(ownedPoints.map(initPoint(Relationship.own))),
792 incomingPoints.map(initPoint(Relationship.transfer))
794 await Promise.all(managingPoints.map(initPoint(Relationship.manage))),
795 await Promise.all(votingPoints.map(initPoint(Relationship.vote))),
796 await Promise.all(spawningPoints.map(initPoint(Relationship.spawn))),
800 allPoints.reduce((acc: Points, cur: (Point | undefined)[]) => {
813 [setPoints, initPoint]
816 // On load, get initial config
823 }, [config, fetchConfig]);
826 const time = isDevelopment ? 10000 : ONE_SECOND;
828 const interval = setInterval(() => {
829 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
830 setNextRoll(nextRoll);
832 if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
833 api.getRollerConfig().then(response => {
834 setNextBatchTime(response.nextBatch);
839 return () => clearInterval(interval);
840 }, [nextBatchTime, getPendingTransactions, point]); // eslint-disable-line react-hooks/exhaustive-deps
847 configureNetworkingKeys,
851 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