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 { convertToInt } from './convertToInt';
8import { isDevelopment, isRopsten } from './flags';
9import { generateInviteWallet, getPendingSpawns } from './utils/roller';
10import { ROLLER_HOSTS } from './constants';
12import { useNetwork } from 'store/network';
13import { usePointCursor } from 'store/pointCursor';
14import { useRollerStore } from 'store/roller';
15import { useWallet } from 'store/wallet';
16import { getStoredInvites, setStoredInvites } from 'store/storage/roller';
17import { usePointCache } from 'store/pointCache';
18import { getOutgoingPoints } from 'views/Points';
24} from './utils/proxy';
25import { isPlanet } from './utils/point';
28 deriveNetworkSeedFromUrbitWallet,
29 attemptNetworkSeedDerivation,
40} from '@urbit/roller-api';
48} from './utils/roller';
50const ONE_SECOND = 1000;
52const inviteTemplate = (
64export default function useRoller() {
65 const { wallet, authToken, authMnemonic, urbitWallet }: any = useWallet();
66 const { pointCursor }: any = usePointCursor();
67 const { web3, contracts }: any = useNetwork();
68 const allPoints: any = usePointCache();
69 const controlledPoints = allPoints?.controlledPoints;
70 const getDetails = allPoints?.getDetails;
79 setPendingTransactions,
82 setInviteGeneratingNum,
84 const [config, setConfig] = useState<Config | null>(null);
86 const options: Options = useMemo(() => {
87 const type = isRopsten || !isDevelopment ? 'https' : 'http';
88 const host = isRopsten
89 ? ROLLER_HOSTS.ROPSTEN
92 : ROLLER_HOSTS.MAINNET;
93 const port = isRopsten ? 443 : 8080;
94 const path = '/v1/roller';
106 const api = useMemo(() => {
107 return new RollerRPCAPI(options);
110 const fetchConfig = useCallback(async () => {
115 setNextBatchTime(response.nextBatch);
118 // TODO: more elegant error handling
120 '[fetchConfig:failed] check the roller connection?\n',
124 }, [api]); // eslint-disable-line react-hooks/exhaustive-deps
126 const ls = new SecureLS({
127 isCompression: false,
128 encryptionSecret: authToken.getOrElse('default'),
131 const generateInviteCodes = useCallback(
132 async (numInvites: number) => {
133 const _point = need.point(pointCursor);
134 const _contracts = contracts.getOrElse(null);
135 const _web3 = web3.getOrElse(null);
136 const _wallet = wallet.getOrElse(null);
137 const _authToken = authToken.getOrElse(null);
138 const _details = getDetails(_point);
140 if (!_contracts || !_web3 || !_wallet || !_authToken || !_details) {
141 // not using need because we want a custom error
142 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
145 // if (Just.hasInstance(authMnemonic)) {
147 // "Auth key Error: A management mnemonic can't create invite codes"
150 let planets: UnspawnedPoints = await api.getUnspawned(_point);
152 const pendingTxs = await api.getPendingByShip(_point);
154 const pendingSpawns = getPendingSpawns(pendingTxs);
156 planets = planets.filter((point: number) => !pendingSpawns.has(point));
158 const starInfo = await api.getPoint(_point);
159 const invites: Invite[] = [];
160 const requests: Promise<string>[] = [];
162 const proxy = getSpawnProxy(starInfo.ownership!, _wallet.address);
164 if (proxy === undefined)
165 throw new Error("Error: Address doesn't match proxy");
167 const nonce = await api.getNonce({
172 for (let i = 0; i < numInvites && planets[i]; i++) {
173 setInviteGeneratingNum(i + 1);
174 const planet = planets[i];
175 const nonceInc = i + nonce;
177 const { ticket, inviteWallet } = await generateInviteWallet(
182 const spawnRequest = spawn(
191 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
195 const configureKeysRequest = configureKeys(
204 const setManagementProxyRequest = registerProxyAddress(
211 inviteWallet.management.keys.address
214 const transferRequest = transferPointRequest(
220 inviteWallet.ownership.keys.address
225 configureKeysRequest,
226 setManagementProxyRequest,
229 await Promise.all(requests);
235 owner: inviteWallet.ownership.keys.address,
239 setStoredInvites(ls, invites);
250 setInviteGeneratingNum,
254 const getPoints = useCallback(
255 async (proxy: Proxy, address: EthAddress) => {
256 const points: Ship[] =
258 ? await api.getOwnedPoints(address)
260 ? await api.getManagerFor(address)
262 ? await api.getVotingFor(address)
263 : proxy === 'transfer'
264 ? await api.getTransferringFor(address)
266 ? await api.getSpawningFor(address)
274 const getPendingTransactions = useCallback(async () => {
276 const curPoint = Number(need.point(pointCursor));
277 const newPending = await api.getPendingByShip(curPoint);
279 setPendingTransactions(newPending);
281 console.warn('ERROR GETTING PENDING', error);
283 }, [api, setPendingTransactions, pointCursor]);
285 const generateInviteInfo = async (planet: number, _authToken: string) => {
286 const { ticket, inviteWallet } = await generateInviteWallet(
291 return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
294 const getInvites = useCallback(
295 async (isL2: boolean, getAll = false) => {
297 const curPoint: number = Number(need.point(pointCursor));
298 setInvites(invitePoints.map(p => inviteTemplate(p)));
300 const _authToken = authToken.getOrElse(null);
301 const _contracts = contracts.getOrElse(null);
303 if (_authToken && _contracts) {
304 const spawnedPoints = isL2 ? await api.getSpawned(curPoint) : [];
306 const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
311 const outgoingPoints = getOutgoingPoints(
314 ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
316 const invitePlanets = spawnedPoints.concat(
317 outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
321 console.log('INVITED PLANETS', invitePlanets);
324 const newInvites: Invite[] = [];
325 // Iterate over all of the stored invites, generating wallet info as necessary
326 const storedInvites = getStoredInvites(ls);
328 for (let i = 0; i < invitePlanets.length; i++) {
329 setInviteGeneratingNum(i + 1);
330 const planet = invitePlanets[i];
331 const invite = storedInvites[planet];
333 if (invite?.ticket) {
334 newInvites.push(invite);
337 console.log('MISSING INVITE INFO', planet);
339 const newInvite = await generateInviteInfo(planet, _authToken);
340 newInvites.push(newInvite);
342 newInvites.push(inviteTemplate(planet));
346 setStoredInvites(ls, newInvites);
347 setInvites(newInvites);
352 console.warn('ERROR GETTING INVITES', error);
365 setInviteGeneratingNum,
369 const showInvite = useCallback(
370 async (planet: number) => {
371 const _authToken = authToken.getOrElse(null);
374 const newInvite = await generateInviteInfo(planet, _authToken);
375 const newInvites = invites.map(invite =>
376 invite.planet === planet ? newInvite : invite
378 setInvites(newInvites);
379 setStoredInvites(ls, newInvites);
382 [authToken, invites, ls, setInvites]
385 const getNumInvites = useCallback(
386 async (isL2: boolean) => {
388 const curPoint: number = Number(need.point(pointCursor));
389 const _authToken = authToken.getOrElse(null);
390 const _contracts = contracts.getOrElse(null);
392 if (_authToken && _contracts) {
393 let invitePoints = isL2 ? await api.getSpawned(curPoint) : [];
395 const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
400 const newPending = await api.getPendingByShip(curPoint);
401 const pendingSpawns = getPendingSpawns(newPending);
403 const outgoingPoints = getOutgoingPoints(
406 ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
408 setPendingTransactions(newPending);
412 outgoingPoints.filter((p: number) => !invitePoints.includes(p))
414 .filter((p: number) => !pendingSpawns.has(p))
431 setPendingTransactions,
435 interface ConfigureKeysParams {
437 customNetworkSeed?: string;
440 const configureNetworkingKeys = async ({
443 }: ConfigureKeysParams) => {
444 const _point = need.point(pointCursor);
445 const azimuthPoint = await api.getPoint(_point);
446 const _wallet = wallet.getOrElse(null);
447 const _details = getDetails(_point);
448 if (!_wallet || !_details) {
449 // not using need because we want a custom error
450 throw new Error('Internal Error: Missing Wallet/Details');
453 const proxy = getManagerProxy(azimuthPoint.ownership!, _wallet.address);
455 if (proxy === undefined)
456 throw new Error("Error: Address doesn't match proxy");
458 const nonce = await api.getNonce({
463 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
464 const nextRevision = networkRevision + 1;
465 const networkSeed = customNetworkSeed
467 : await attemptNetworkSeedDerivation({
474 revision: nextRevision,
476 const txHash = await configureKeys(
485 const pendingTx = await api.getPendingTx(txHash);
490 interface ReticketParams {
494 fromWallet: any; // TODO: wallet type
495 toWallet: any; // TODO: wallet type
498 const performL2Reticket = async ({
504 }: ReticketParams) => {
505 const azimuthPoint = await api.getPoint(point);
507 let nonce = await api.getNonce({ ship: point, proxy });
511 // 1. Update networking keys
512 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
513 const nextRevision = networkRevision + 1;
514 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
518 const configureKeysRequest = await configureKeys(
527 requests.push(configureKeysRequest);
529 // 2. Set Management Proxy
530 const registerMgmtRequest = await registerProxyAddress(
540 requests.push(registerMgmtRequest);
542 // 3. Set Spawn Proxy
543 if (!isPlanet(Number(point))) {
544 const registerSpawnRequest = await registerProxyAddress(
551 toWallet.spawn.keys.address
554 requests.push(registerSpawnRequest);
557 const transferTxRequest = await transferPointRequest(
565 requests.push(transferTxRequest);
567 const hashes = await Promise.all(requests);
571 const setProxyAddress = useCallback(
572 async (proxyType: Proxy, address: EthAddress) => {
573 const _point = need.point(pointCursor);
574 const _wallet = wallet.getOrElse(null);
577 // not using need because we want a custom error
578 throw new Error('Internal Error: Missing Wallet');
581 const pointDetails = await api.getPoint(_point);
582 const ownership = pointDetails.ownership!;
583 const proxy = getAddressProxy(ownership, _wallet.address, proxyType);
585 if (proxy === undefined)
586 throw new Error("Error: Address doesn't match expected proxy");
588 const nonce = await api.getNonce({ ship: _point, proxy });
590 const txHash = await registerProxyAddress(
600 const pendingTx = await api.getPendingTx(txHash);
604 [api, pointCursor, wallet]
607 const transferPoint = useCallback(
608 async (address: EthAddress, reset?: boolean) => {
609 const _point = need.point(pointCursor);
610 const _wallet = wallet.getOrElse(null);
613 // not using need because we want a custom error
614 throw new Error('Internal Error: Missing Wallet');
617 const pointDetails = await api.getPoint(_point);
618 const proxy = getTransferProxy(pointDetails.ownership!, _wallet.address);
620 if (proxy === undefined)
621 throw new Error("Error: Address doesn't match proxy");
623 const nonce = await api.getNonce({ ship: _point, proxy });
625 const txHash = await transferPointRequest(
634 const pendingTx = await api.getPendingTx(txHash);
638 [api, pointCursor, wallet]
641 // On load, get initial config
648 }, [config, fetchConfig]);
651 const interval = setInterval(() => {
652 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
653 setNextRoll(nextRoll);
655 if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
656 api.getRollerConfig().then(response => {
657 setNextBatchTime(response.nextBatch);
658 getNumInvites(currentL2);
663 return () => clearInterval(interval);
664 }, [nextBatchTime, getPendingTransactions, currentL2]); // eslint-disable-line react-hooks/exhaustive-deps
672 getPendingTransactions,
676 configureNetworkingKeys,
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