1import { useCallback, useEffect, useMemo, useState } from 'react';
2import * as azimuth from 'azimuth-js';
3import * as need from 'lib/need';
4import SecureLS from 'secure-ls';
7 deriveNetworkSeedFromUrbitWallet,
8 attemptNetworkSeedDerivation,
11import { useNetwork } from 'store/network';
12import { usePointCursor } from 'store/pointCursor';
13import { EMPTY_POINT, useRollerStore } from 'store/rollerStore';
14import { useWallet } from 'store/wallet';
16import { getUpdatedPointMessage, isPlanet, toL1Details } from './utils/point';
17import { convertToInt } from './convertToInt';
18import { isDevelopment } from './flags';
23} from './utils/roller';
24import { TEN_SECONDS } from './constants';
32} from '@urbit/roller-api';
34import Point, { PointField, Points } from './types/Point';
35import { ReticketParams, SendL2Params } from './types/L2Transaction';
36import { L1Point } from './types/L1Point';
37import { ddmmmYYYY } from './utils/date';
38import { showNotification } from './utils/notifications';
39import { useWalletConnect } from './useWalletConnect';
40import { PendingL1Txn } from './types/PendingL1Transaction';
41import { TRANSACTION_PROGRESS } from './reticket';
42import { useRollerOptions } from './useRollerOptions';
43import { useTimerStore } from 'store/timerStore';
45interface UpdateParams {
46 point?: Point | number;
51 intervalTime?: number;
54export default function useRoller() {
62 const { connector, isConnected, signPersonalMessage } = useWalletConnect();
63 const { pointCursor }: any = usePointCursor();
64 const { web3, contracts }: any = useNetwork();
71 setPendingTransactions,
78 const { setNextBatchTime } = useTimerStore();
79 const [config, setConfig] = useState<Config | null>(null);
81 const { options } = useRollerOptions();
83 const api = useMemo(() => {
84 return new RollerRPCAPI(options);
91 encryptionSecret: authToken.getOrElse('default'),
96 const getNextQuotaTime = useCallback(
97 (nextSlice: number) => {
102 setNextQuotaTime(next);
103 getNextQuotaTime(next);
105 .catch(err => console.warn(err));
106 }, nextSlice - new Date().getTime());
108 [api, setNextQuotaTime]
111 const fetchConfig = useCallback(async () => {
112 getNextQuotaTime(Date.now() + 1000);
117 setNextBatchTime(config.nextBatch);
120 // TODO: more elegant error handling
122 '[fetchConfig:failed] check the roller connection?\n',
126 }, [api, getNextQuotaTime, setNextBatchTime]);
128 const quotaReached = useCallback(() => {
129 if (point.l2Quota <= 0) {
131 `You have reached your weekly L2 transaction limit. You will get another
132 ${point.l2Allowance} transactions on ${ddmmmYYYY(nextQuotaTime)}.`
136 return point.l2Quota <= 0;
137 }, [nextQuotaTime, point, setModalText]);
139 const initPoint = useCallback(
140 async (point: string | number): Promise<Point> => {
141 const _wallet = wallet.getOrElse(null);
147 const pointNum = Number(point);
150 // Try fetching from the roller
151 const rawDetails = await api.getPoint(pointNum); // this fails for newly spawned L1 point until roller updates
152 const details = toL1Details(rawDetails);
153 const isL2 = isL2Spawn(rawDetails?.dominion);
156 // Scenario 1: L2 point exists in roller state - use roller data
157 const l2Quota = await api.getRemainingQuota(pointNum);
158 const l2Allowance = await api.getAllowance(pointNum);
162 address: _wallet.address,
167 // Scenario 2: L1 point exists in roller state - use combination of chain and roller data
168 const _contracts = need.contracts(contracts);
169 const l1Details = await azimuth.azimuth.getPoint(
173 // Override with roller data for accuracy, L1 data here could be stale
174 l1Details.sponsor = details.sponsor;
175 l1Details.escapeRequested = details.escapeRequested;
176 l1Details.escapeRequestedTo = details.escapeRequestedTo;
180 address: _wallet.address,
184 // roller failed to fetch point
186 // Scenario 3: Newly spawned L1 point - not in roller state yet, use chain data only
187 const _contracts = need.contracts(contracts);
188 const l1Details = await azimuth.azimuth.getPoint(
195 address: _wallet.address,
198 // Scenario 4: Some other issue, point doesn’t exist on chain or in roller state. Just return placeholder.
199 console.warn('Failed to fetch point data:', e2);
200 const details: L1Point = toL1Details();
204 address: _wallet.address,
210 [api, wallet, contracts]
213 const getAndUpdatePoint = useCallback(
214 async (point: number) => {
215 const updatedPoint = await initPoint(point);
216 updatePoint(updatedPoint);
218 [initPoint, updatePoint]
221 const checkForUpdates = useCallback(
228 intervalTime = TEN_SECONDS,
229 }: UpdateParams) => {
230 const pVal = typeof point === 'number' ? point : point.value;
231 const p = await initPoint(pVal);
233 if (l1Txn) storePendingL1Txn(l1Txn);
236 const check = async () => {
237 const changedField = !points[p.value]
239 : p.getChangedField(points[p.value], field);
242 console.log(`CHECKING FOR ${changedField} UPDATES:`, p.patp);
245 if (!p.isPlaceholder && changedField) {
248 clearInterval(interval);
251 if (l1Txn) deletePendingL1Txn(l1Txn);
254 `${message || getUpdatedPointMessage(p, changedField)}`
260 interval = setInterval(check, intervalTime);
263 [points, initPoint, updatePoint, storePendingL1Txn, deletePendingL1Txn]
266 const spawnPoint = useCallback(
267 async (pointToSpawn: number, destinationAddress: string) => {
268 if (quotaReached()) {
272 const _point = need.point(pointCursor);
273 const _contracts = contracts.getOrElse(null);
274 const _web3 = web3.getOrElse(null);
275 const _wallet = wallet.getOrElse(null);
276 const _authToken = authToken.getOrElse(null);
278 if (!_contracts || !_web3 || !_wallet || !_authToken) {
279 // not using need because we want a custom error
280 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
283 const proxy = point.getSpawnProxy();
285 if (proxy === undefined)
286 throw new Error("Error: Address doesn't match proxy");
288 const nonce = await api.getNonce({
293 await submitL2Transaction({
295 address: destinationAddress,
325 const getPoints = useCallback(
326 async (proxy: Proxy, address: EthAddress) => {
327 const points: Ship[] =
329 ? await api.getOwnedPoints(address)
331 ? await api.getManagerFor(address)
333 ? await api.getVotingFor(address)
334 : proxy === 'transfer'
335 ? await api.getTransferringFor(address)
337 ? await api.getSpawningFor(address)
345 const getPendingTransactions = useCallback(async () => {
347 if (!point.isDefault) {
348 const newPending = await api.getPendingByShip(point.value);
349 setPendingTransactions(newPending);
352 console.warn('ERROR GETTING PENDING', error);
354 }, [api, setPendingTransactions, point]);
356 interface ConfigureKeysParams {
358 customNetworkSeed?: string;
361 const configureNetworkKeys = useCallback(
362 async ({ breach, customNetworkSeed }: ConfigureKeysParams) => {
363 if (quotaReached()) {
366 const _wallet = wallet.getOrElse(null);
367 const _web3 = web3.getOrElse(null);
368 if (!_wallet || !_web3 || point.isDefault) {
369 // not using need because we want a custom error
370 throw new Error('Internal Error: Missing Wallet/Details');
373 const proxy = point.getManagerProxy();
375 if (proxy === undefined)
376 throw new Error("Error: Address doesn't match proxy");
378 const nonce = await api.getNonce({
383 const nextRevision = point.keyRevisionNumber + 1;
384 const networkSeed = customNetworkSeed
386 : await attemptNetworkSeedDerivation({
393 revision: nextRevision,
395 const txHash = await submitL2Transaction({
402 type: 'configureKeys',
411 return api.getPendingTx(txHash);
414 // eslint-disable-line
430 const performL2Reticket = async ({
437 }: ReticketParams) => {
438 const azimuthPoint = await api.getPoint(point);
440 const _web3 = web3.getOrElse(null);
441 let nonce = await api.getNonce({ ship: point, proxy });
442 const progress = onUpdate
443 ? (state: number) => onUpdate({ type: 'progress', state })
448 // 1. Update networking keys
449 progress(TRANSACTION_PROGRESS.GENERATING);
450 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
451 const nextRevision = networkRevision + 1;
452 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
456 const configureKeysRequest = await submitL2Transaction({
464 type: 'configureKeys',
471 requests.push(configureKeysRequest);
473 // 2. Set Management Proxy
474 progress(TRANSACTION_PROGRESS.SIGNING);
475 const registerMgmtRequest = await registerProxyAddress(
490 requests.push(registerMgmtRequest);
492 // 3. Set Spawn Proxy
493 progress(TRANSACTION_PROGRESS.FUNDING);
494 if (!isPlanet(Number(point))) {
495 const registerSpawnRequest = await registerProxyAddress(
502 toWallet?.spawn?.keys?.address || to,
510 requests.push(registerSpawnRequest);
514 progress(TRANSACTION_PROGRESS.TRANSFERRING);
515 const transferTxRequest = await submitL2Transaction({
520 type: 'transferPoint',
529 requests.push(transferTxRequest);
531 progress(TRANSACTION_PROGRESS.CLEANING);
532 const hashes = await Promise.all(requests);
534 progress(TRANSACTION_PROGRESS.DONE);
538 const setProxyAddress = useCallback(
539 async (proxyType: Proxy, address: EthAddress) => {
540 if (quotaReached()) {
543 const _wallet = wallet.getOrElse(null);
544 const _web3 = web3.getOrElse(null);
547 // not using need because we want a custom error
548 throw new Error('Internal Error: Missing Wallet');
551 if (point.isDefault) {
552 // not using need because we want a custom error
553 throw new Error('Internal Error: Invalid point');
555 const proxy = point.getAddressProxy(proxyType);
557 if (proxy === undefined)
558 throw new Error("Error: Address doesn't match expected proxy");
560 const nonce = await api.getNonce({ ship: point.value, proxy });
561 const txHash = await registerProxyAddress(
575 return api.getPendingTx(txHash);
590 const changeSponsorship = useCallback(
593 type: 'adopt' | 'detach' | 'reject',
594 skipQuotaCheck = false
596 if (!skipQuotaCheck && quotaReached()) {
600 const _wallet = wallet.getOrElse(null);
601 const _web3 = web3.getOrElse(null);
602 const ship = point.value;
604 const proxy = point.getManagerProxy();
606 if (proxy === undefined)
607 throw new Error("Error: Address doesn't match expected proxy");
609 const nonce = await api.getNonce({ ship, proxy });
610 // const txHash = await adopt(api, _wallet, point.value, proxy, nonce, ship);
611 const txHash = await submitL2Transaction({
618 address: _wallet.address,
627 return api.getPendingTx(txHash);
642 const transferPoint = useCallback(
643 async (address: EthAddress, reset?: boolean) => {
644 if (quotaReached()) {
648 const _point = need.point(pointCursor);
649 const _wallet = wallet.getOrElse(null);
650 const _web3 = web3.getOrElse(null);
653 // not using need because we want a custom error
654 throw new Error('Internal Error: Missing Wallet');
657 const proxy = point.getTransferProxy();
659 if (proxy === undefined)
660 throw new Error("Error: Address doesn't match proxy");
662 const nonce = await api.getNonce({ ship: _point, proxy });
664 const txHash = await submitL2Transaction({
671 type: 'transferPoint',
680 return api.getPendingTx(txHash);
696 const sendL2Transaction = useCallback(
697 async (params: SendL2Params) => {
698 if (quotaReached()) {
702 const _wallet = wallet.getOrElse(null);
703 const _web3 = web3.getOrElse(null);
705 if (!_wallet || point.isDefault) {
706 // not using need because we want a custom error
707 throw new Error('Internal Error: Missing Wallet or Point Info');
710 return submitL2Transaction({
735 const changeSponsor = useCallback(
736 async (newSponsor: number) => {
737 if (point.isDefault) {
738 throw new Error('Internal Error: point is default');
741 const curPoint = point.value;
742 const _wallet = wallet.getOrElse(null);
745 // not using need because we want a custom error
746 throw new Error('Internal Error: Missing Wallet');
749 const proxy = point.getManagerProxy();
751 if (proxy === undefined)
752 throw new Error("Error: Address doesn't match proxy");
754 const nonce = await api.getNonce({ ship: curPoint, proxy });
756 return sendL2Transaction({
764 [api, point, sendL2Transaction, wallet]
767 const cancelEscape = useCallback(async () => {
768 if (point.isDefault) {
769 throw new Error('Internal Error: point is default');
772 const curPoint = point.value;
773 const _wallet = wallet.getOrElse(null);
776 // not using need because we want a custom error
777 throw new Error('Internal Error: Missing Wallet');
780 const proxy = point.getManagerProxy();
782 if (proxy === undefined)
783 throw new Error("Error: Address doesn't match proxy");
785 const nonce = await api.getNonce({ ship: curPoint, proxy });
787 return sendL2Transaction({
790 type: 'cancelEscape',
793 }, [api, point, sendL2Transaction, wallet]);
795 const getPointsDetails = useCallback(
797 ownedPoints: number[],
798 incomingPoints: number[],
799 managingPoints: number[],
800 votingPoints: number[],
801 spawningPoints: number[]
805 await Promise.all(ownedPoints.map(initPoint)),
806 await Promise.all(incomingPoints.map(initPoint)),
807 await Promise.all(managingPoints.map(initPoint)),
808 await Promise.all(votingPoints.map(initPoint)),
809 await Promise.all(spawningPoints.map(initPoint)),
813 allPoints.reduce((acc: Points, cur: (Point | undefined)[]) => {
826 [setPoints, initPoint]
829 // On load, get initial config
836 }, [config, fetchConfig]);
845 configureNetworkKeys,
849 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