@@ -1,7 +1,6 @@
1import { useCallback, useEffect, useMemo, useState } from 'react';
1import { useCallback, useEffect, useMemo, useState } from 'react';
2import * as azimuth from 'azimuth-js';
2import * as azimuth from 'azimuth-js';
3import * as need from 'lib/need';
3import * as need from 'lib/need';
4−import * as ob from 'urbit-ob';
5import SecureLS from 'secure-ls';
4import SecureLS from 'secure-ls';
7import { Invite } from 'lib/types/Invite';
6import { Invite } from 'lib/types/Invite';
@@ -22,7 +21,7 @@ import {
22import { usePointCache } from 'store/pointCache';
21import { usePointCache } from 'store/pointCache';
23import { getOutgoingPoints } from 'views/Points';
22import { getOutgoingPoints } from 'views/Points';
25−import { isPlanet, toL1Details } from './utils/point';
24+import { getUpdatedPointMessage, isPlanet, toL1Details } from './utils/point';
26import { convertToInt } from './convertToInt';
25import { convertToInt } from './convertToInt';
27import { isDevelopment, isRopsten } from './flags';
26import { isDevelopment, isRopsten } from './flags';
@@ -47,11 +46,12 @@ import {
47 UnspawnedPoints,
46 UnspawnedPoints,
48} from '@urbit/roller-api';
47} from '@urbit/roller-api';
50−import Point, { Points } from './types/Point';
49+import Point, { PointField, Points } from './types/Point';
51import { useTimerStore } from 'store/timerStore';
50import { useTimerStore } from 'store/timerStore';
52import { ReticketParams, SendL2Params } from './types/L2Transaction';
51import { ReticketParams, SendL2Params } from './types/L2Transaction';
53import { L1Point } from './types/L1Point';
52import { L1Point } from './types/L1Point';
54import { ddmmmYYYY } from './utils/date';
53import { ddmmmYYYY } from './utils/date';
54+import { useWalletConnect } from './useWalletConnect';
56const ONE_SECOND = 1000;
56const ONE_SECOND = 1000;
@@ -75,6 +75,7 @@ export default function useRoller() {
75 urbitWallet,
75 urbitWallet,
76 walletType,
76 walletType,
77 }: any = useWallet();
77 }: any = useWallet();
78+ const { connector } = useWalletConnect();
78 const { pointCursor }: any = usePointCursor();
79 const { pointCursor }: any = usePointCursor();
79 const { web3, contracts }: any = useNetwork();
80 const { web3, contracts }: any = useNetwork();
80 const allPoints: any = usePointCache();
81 const allPoints: any = usePointCache();
@@ -135,12 +136,28 @@ export default function useRoller() {
135 [authToken]
136 [authToken]
139+ const getNextQuotaTime = useCallback(
140+ (nextSlice: number) => {
145+ setNextQuotaTime(next);
146+ getNextQuotaTime(next);
148+ .catch(err => console.warn(err));
149+ }, nextSlice - new Date().getTime());
151+ [api, setNextQuotaTime]
138 const fetchConfig = useCallback(async () => {
154 const fetchConfig = useCallback(async () => {
155+ getNextQuotaTime(1);
139− Promise.all([api.getRollerConfig(), api.whenNextSlice()])
157+ .getRollerConfig()
140− .then(([config, nextSlice]) => {
158+ .then(config => {
141 setConfig(config);
159 setConfig(config);
142 setNextBatchTime(config.nextBatch);
160 setNextBatchTime(config.nextBatch);
143− setNextQuotaTime(nextSlice);
145 .catch(err => {
162 .catch(err => {
146 // TODO: more elegant error handling
163 // TODO: more elegant error handling
@@ -149,7 +166,7 @@ export default function useRoller() {
152− }, [api]); // eslint-disable-line react-hooks/exhaustive-deps
169+ }, [api, getNextQuotaTime, setNextBatchTime]);
154 const quotaReached = useCallback(() => {
171 const quotaReached = useCallback(() => {
155 if (point.l2Quota <= 0) {
172 if (point.l2Quota <= 0) {
@@ -172,13 +189,19 @@ export default function useRoller() {
173 const pointNum = Number(point);
190 const pointNum = Number(point);
175− const [rawDetails, l2Quota] = await Promise.all([
192+ const rawDetails = await api.getPoint(pointNum);
176− api.getPoint(pointNum),
193+ const l2Quota =
194+ rawDetails?.dominion === 'l2'
177− api.getRemainingQuota(pointNum),
195+ ? await api.getRemainingQuota(pointNum)
179 const details = toL1Details(rawDetails);
197 const details = toL1Details(rawDetails);
181− return new Point(pointNum, details, _wallet.address, l2Quota);
199+ return new Point({
202+ address: _wallet.address,
182 } catch (e) {
205 } catch (e) {
183 console.warn(e);
206 console.warn(e);
@@ -186,12 +209,21 @@ export default function useRoller() {
187 const _contracts = need.contracts(contracts);
210 const _contracts = need.contracts(contracts);
188 const details = await azimuth.azimuth.getPoint(_contracts, point);
211 const details = await azimuth.azimuth.getPoint(_contracts, point);
189− return new Point(pointNum, details, _wallet.address);
212+ return new Point({
215+ address: _wallet.address,
190 } catch (e) {
217 } catch (e) {
191 console.warn(e);
218 console.warn(e);
192 // Just return a placeholder Point
219 // Just return a placeholder Point
193 const details: L1Point = toL1Details();
220 const details: L1Point = toL1Details();
194− return new Point(pointNum, details, _wallet.address);
221+ return new Point({
224+ address: _wallet.address,
225+ isPlaceholder: true,
@@ -199,19 +231,25 @@ export default function useRoller() {
201 const checkForUpdates = useCallback(
233 const checkForUpdates = useCallback(
202− async (point: number, message?: string) => {
236+ message?: string,
203 const interval = setInterval(async () => {
240 const interval = setInterval(async () => {
204 const updatedPoint = await initPoint(point);
241 const updatedPoint = await initPoint(point);
242+ const changedField = !points[point]
244+ : updatedPoint.getChangedField(points[point], field);
206− console.log('CHECK UPDATES', updatedPoint.equals(points[point]), updatedPoint)
208− if (!updatedPoint.equals(points[point])) {
246+ if (!updatedPoint.isPlaceholder && changedField) {
209− console.log('UPDATED')
247+ console.log('UPDATED', changedField, updatedPoint)
210 updatePoint(updatedPoint);
248 updatePoint(updatedPoint);
211 clearInterval(interval);
249 clearInterval(interval);
212− if (Notification?.permission === 'granted') {
250+ if (Notification?.permission === 'granted' && notify) {
213 new Notification(
251 new Notification(
214− `${message || `${updatedPoint.patp} has been updated!`}`
252+ `${message || getUpdatedPointMessage(updatedPoint, changedField)}`
@@ -220,6 +258,162 @@ export default function useRoller() {
220 [points, initPoint, updatePoint]
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,
223 const generateInviteCodes = useCallback(
417 const generateInviteCodes = useCallback(
224 async (numInvites: number) => {
418 async (numInvites: number) => {
225 const _point = need.point(pointCursor);
419 const _point = need.point(pointCursor);
@@ -281,6 +475,7 @@ export default function useRoller() {
281 type: 'spawn',
475 type: 'spawn',
282 walletType,
476 walletType,
283 web3: _web3,
477 web3: _web3,
286 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
481 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
@@ -297,6 +492,7 @@ export default function useRoller() {
297 type: 'configureKeys',
492 type: 'configureKeys',
298 walletType,
493 walletType,
299 web3: _web3,
494 web3: _web3,
302 await registerProxyAddress(
498 await registerProxyAddress(
@@ -308,7 +504,8 @@ export default function useRoller() {
309 inviteWallet.management.keys.address,
505 inviteWallet.management.keys.address,
310 walletType,
506 walletType,
314 await submitL2Transaction({
511 await submitL2Transaction({
@@ -321,6 +518,7 @@ export default function useRoller() {
321 address: inviteWallet.ownership.keys.address,
518 address: inviteWallet.ownership.keys.address,
322 walletType,
519 walletType,
323 web3: _web3,
520 web3: _web3,
326 invites.push({
524 invites.push({
@@ -332,10 +530,12 @@ export default function useRoller() {
334 setStoredInvites(ls, invites);
532 setStoredInvites(ls, invites);
338 authToken,
537 authToken,
339 contracts,
539 contracts,
340 pointCursor,
540 pointCursor,
@@ -344,6 +544,7 @@ export default function useRoller() {
345 getDetails,
545 getDetails,
347 setInviteGeneratingNum,
548 setInviteGeneratingNum,
@@ -379,107 +580,6 @@ export default function useRoller() {
380 }, [api, setPendingTransactions, point]);
581 }, [api, setPendingTransactions, point]);
382− const generateInviteInfo = async (planet: number, _authToken: string) => {
383− const { ticket, inviteWallet } = await generateInviteWallet(
388− return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
391− const getInvites = useCallback(async () => {
393− setInvitesLoading(true);
394− const curPoint: number = Number(need.point(pointCursor));
395− const _authToken = authToken.getOrElse(null);
396− const _contracts = contracts.getOrElse(null);
398− if (_authToken && _contracts) {
399− const spawnedPoints = point.isL2Spawn
400− ? await api.getSpawned(curPoint)
403− const newPending = await api.getPendingByShip(curPoint);
404− setPendingTransactions(newPending);
405− const pendingSpawns = getPendingSpawns(newPending);
407− const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
412− const outgoingPoints = getOutgoingPoints(
415− ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
417− const invitePlanets = spawnedPoints
419− outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
421− .filter((p: number) => !pendingSpawns.has(p))
426− invitePlanets.map(p => inviteTemplate(p))
429− const newInvites: Invite[] = [];
430− // Iterate over all of the stored invites, generating wallet info as necessary
431− const storedInvites = getStoredInvites(ls);
433− for (let i = 0; i < invitePlanets.length; i++) {
434− setInviteGeneratingNum(i + 1);
435− const planet = invitePlanets[i];
436− const storedInvite = storedInvites[planet];
438− storedInvite || (await generateInviteInfo(planet, _authToken));
440− // TODO: check if the invite point's owner still matches the deterministic wallet address
441− const planetInfo = await api.getPoint(planet);
442− setStoredInvite(ls, invite);
445− invite.owner.toLowerCase() ===
446− planetInfo.ownership?.owner?.address ||
447− planetInfo.ownership?.transferProxy?.address !== ETH_ZERO_ADDR
449− newInvites.push(invite);
450− updateInvite(curPoint, invite);
452− removeInvite(curPoint, invite.planet);
456− setStoredInvite(ls, invite);
459− setInvites(curPoint, newInvites);
460− setInvitesLoading(false);
464− console.warn('ERROR GETTING INVITES', error);
478− setInviteGeneratingNum,
479− setPendingTransactions,
483 interface ConfigureKeysParams {
583 interface ConfigureKeysParams {
484 breach: boolean;
584 breach: boolean;
485 customNetworkSeed?: string;
585 customNetworkSeed?: string;
@@ -530,13 +630,10 @@ export default function useRoller() {
530 type: 'configureKeys',
630 type: 'configureKeys',
531 walletType,
631 walletType,
532 web3: _web3,
632 web3: _web3,
538− `${point.patp}'s network keys have been updated!`
540 return api.getPendingTx(txHash);
637 return api.getPendingTx(txHash);
542 [// eslint-disable-line
639 [// eslint-disable-line
@@ -560,9 +657,6 @@ export default function useRoller() {
560 fromWallet,
657 fromWallet,
561 toWallet,
658 toWallet,
562 }: ReticketParams) => {
659 }: ReticketParams) => {
563− if (quotaReached()) {
566 const azimuthPoint = await api.getPoint(point);
660 const azimuthPoint = await api.getPoint(point);
567 const proxy = 'own';
661 const proxy = 'own';
568 const _web3 = web3.getOrElse(null);
662 const _web3 = web3.getOrElse(null);
@@ -587,6 +681,7 @@ export default function useRoller() {
587 walletType,
681 walletType,
588 type: 'configureKeys',
682 type: 'configureKeys',
589 web3: _web3,
683 web3: _web3,
591 nonce = nonce + 1;
686 nonce = nonce + 1;
592 requests.push(configureKeysRequest);
687 requests.push(configureKeysRequest);
@@ -601,7 +696,8 @@ export default function useRoller() {
603 walletType,
698 walletType,
606 nonce = nonce + 1;
702 nonce = nonce + 1;
607 requests.push(registerMgmtRequest);
703 requests.push(registerMgmtRequest);
@@ -617,7 +713,8 @@ export default function useRoller() {
618 toWallet.spawn.keys.address,
714 toWallet.spawn.keys.address,
619 walletType,
715 walletType,
622 nonce = nonce + 1;
719 nonce = nonce + 1;
623 requests.push(registerSpawnRequest);
720 requests.push(registerSpawnRequest);
@@ -633,6 +730,7 @@ export default function useRoller() {
633 address: to,
730 address: to,
634 walletType,
731 walletType,
635 web3: _web3,
732 web3: _web3,
637 requests.push(transferTxRequest);
735 requests.push(transferTxRequest);
@@ -675,16 +773,13 @@ export default function useRoller() {
677 walletType,
775 walletType,
683− `${point.patp}'s ${proxyType} proxy has been updated!`
685 return api.getPendingTx(txHash);
780 return api.getPendingTx(txHash);
687− [api, point, wallet, web3, walletType, checkForUpdates, quotaReached]
782+ [api, connector, point, wallet, web3, walletType, quotaReached]
690 const adoptPoint = useCallback(
785 const adoptPoint = useCallback(
@@ -703,13 +798,9 @@ export default function useRoller() {
703 const nonce = await api.getNonce({ ship: point.value, proxy });
798 const nonce = await api.getNonce({ ship: point.value, proxy });
704 const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
799 const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
708− `${ob.patp(ship)} is now a resident of ${point.patp}`
710 return api.getPendingTx(txHash);
801 return api.getPendingTx(txHash);
712− [api, point, wallet, checkForUpdates, quotaReached]
803+ [api, point, wallet, quotaReached]
715 const kickPoint = useCallback(
806 const kickPoint = useCallback(
@@ -732,13 +823,9 @@ export default function useRoller() {
732 const nonce = await api.getNonce({ ship: sponsor, proxy });
823 const nonce = await api.getNonce({ ship: sponsor, proxy });
733 const txHash = await detach(api, _wallet, sponsor, proxy, nonce, ship);
824 const txHash = await detach(api, _wallet, sponsor, proxy, nonce, ship);
737− `${ob.patp(ship)} is no longer a resident of ${point.patp}`
739 return api.getPendingTx(txHash);
826 return api.getPendingTx(txHash);
741− [api, point, wallet, checkForUpdates, quotaReached]
828+ [api, point, wallet, quotaReached]
744 const rejectPoint = useCallback(
831 const rejectPoint = useCallback(
@@ -761,13 +848,9 @@ export default function useRoller() {
761 const nonce = await api.getNonce({ ship: sponsor, proxy });
848 const nonce = await api.getNonce({ ship: sponsor, proxy });
762 const txHash = await reject(api, _wallet, sponsor, proxy, nonce, ship);
849 const txHash = await reject(api, _wallet, sponsor, proxy, nonce, ship);
766− `${ob.patp(ship)} has been rejected as a resident of ${point.patp}`
768 return api.getPendingTx(txHash);
851 return api.getPendingTx(txHash);
770− [api, point, wallet, checkForUpdates, quotaReached]
853+ [api, point, wallet, quotaReached]
773 const transferPoint = useCallback(
856 const transferPoint = useCallback(
@@ -802,25 +885,13 @@ export default function useRoller() {
802 type: 'transferPoint',
885 type: 'transferPoint',
803 walletType,
886 walletType,
804 web3: _web3,
887 web3: _web3,
810− `${point.patp} has been transferred to ${address}!`
812 return api.getPendingTx(txHash);
892 return api.getPendingTx(txHash);
814− [
894+ [api, connector, point, pointCursor, wallet, web3, walletType, quotaReached]
826 const sendL2Transaction = useCallback(
897 const sendL2Transaction = useCallback(
@@ -837,18 +908,17 @@ export default function useRoller() {
837 throw new Error('Internal Error: Missing Wallet or Point Info');
908 throw new Error('Internal Error: Missing Wallet or Point Info');
840− checkForUpdates(point.value);
842 return submitL2Transaction({
911 return submitL2Transaction({
843 ...params,
912 ...params,
845 wallet: _wallet,
914 wallet: _wallet,
846 walletType,
915 walletType,
847 web3: _web3,
916 web3: _web3,
848 ship: point.value,
918 ship: point.value,
851− [api, point, wallet, web3, walletType, checkForUpdates, quotaReached]
921+ [api, connector, point, wallet, web3, walletType, quotaReached]
854 const changeSponsor = useCallback(
924 const changeSponsor = useCallback(
@@ -939,28 +1009,38 @@ export default function useRoller() {
941 getPendingTransactions();
1011 getPendingTransactions();
1014+ getPendingTransactions();
1016+ if (!point.isDefault) {
1019+ }, TEN_SECONDS); // Should this be more like a minute?
943 }, time);
1021 }, time);
945 return () => clearInterval(interval);
1023 return () => clearInterval(interval);
946− }, [nextBatchTime, getPendingTransactions, point]); // eslint-disable-line react-hooks/exhaustive-deps
1024+ }, [point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
948 return {
1026 return {
949 adoptPoint,
1027 adoptPoint,
951 changeSponsor,
1029 changeSponsor,
953 configureNetworkingKeys,
1032 configureNetworkingKeys,
1033+ generateInviteCodes,
954 getInvites,
1034 getInvites,
955 getPoints,
1035 getPoints,
956 getPointsDetails,
1036 getPointsDetails,
957 getPendingTransactions,
1037 getPendingTransactions,
958− generateInviteCodes,
959 kickPoint,
1038 kickPoint,
963 performL2Reticket,
1040 performL2Reticket,
964 setProxyAddress,
1042 setProxyAddress,