1import { UnspawnedPoints } from '@urbit/roller-api';
2import { DEFAULT_CSV_NAME, POINT_DOMINIONS } from 'lib/constants';
3import { deriveNetworkSeedFromUrbitWallet } from 'lib/keys';
4import * as need from 'lib/need';
5import * as azimuth from 'azimuth-js';
6import { Invite, Invites } from 'lib/types/Invite';
7import useRoller from 'lib/useRoller';
8import { useWalletConnect } from 'lib/useWalletConnect';
9import { showNotification } from 'lib/utils/notifications';
15} from 'lib/utils/roller';
16import { useCallback, useRef } from 'react';
17import { useNetwork } from 'store/network';
18import { usePointCursor } from 'store/pointCursor';
19import { useRollerStore } from 'store/rollerStore';
24} from 'store/storage/roller';
25import { useWallet } from 'store/wallet';
26import { usePointCache } from 'store/pointCache';
27import create from 'zustand';
28import { generateCsvLine, generateCsvName } from 'lib/utils/invite';
30const inviteTemplate = (
42export type InviteGeneratingStatus =
48interface InviteStore {
50 generatingStatus: InviteGeneratingStatus;
51 generatingNum: number;
53 addInvite: (point: number, invite: Invite) => void;
54 setInvites: (points: number, invites: Invite[]) => void;
57export const useInviteStore = create<InviteStore>((set, get) => ({
59 generatingStatus: 'initial',
62 addInvite: (point: number, invite: Invite) => {
64 const newInvites: Invites = {};
65 const invites = state.invites[point];
67 if (!invites.find(inv => inv.planet === invite.planet)) {
68 newInvites[point] = [...invites, invite];
70 return { invites: Object.assign(state.invites, newInvites) };
73 setInvites: (point: number, invites: Invite[]) =>
75 const newInvites: Invites = {};
76 newInvites[point] = invites;
77 return { invites: Object.assign(state.invites, newInvites) };
81export function useInvites() {
82 const { wallet, authToken, walletType }: any = useWallet();
83 const { connector } = useWalletConnect();
84 const { pointCursor }: any = usePointCursor();
85 const { web3, contracts }: any = useNetwork();
86 const allPoints: any = usePointCache();
87 const getDetails = allPoints?.getDetails;
88 const { api, ls } = useRoller();
89 const { point, pointList, setPendingTransactions } = useRollerStore();
90 const { addInvite, setInvites } = useInviteStore();
91 const updatingInvites = useRef<number[]>([]);
93 const generateInviteInfo = async (planet: number, _authToken: string) => {
94 const { ticket, inviteWallet } = await generateInviteWallet(
99 return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
102 const getInvites = useCallback(async () => {
103 if (updatingInvites.current.includes(point.value)) {
107 updatingInvites.current.push(point.value);
109 useInviteStore.setState({ loading: true });
110 const curPoint: number = Number(need.point(pointCursor));
111 const _authToken = authToken.getOrElse(null);
112 const _contracts = contracts.getOrElse(null);
114 if (_authToken && _contracts) {
115 const spawnedPoints = point.isL2Spawn
116 ? await api.getSpawned(curPoint)
119 const newPending = await api.getPendingByShip(curPoint);
120 setPendingTransactions(newPending);
121 const pendingSpawns = getPendingSpawns(newPending);
123 const availablePoints = await azimuth.azimuth.getUnspawnedChildren(
128 const outgoingPoints = pointList
130 ({ value, isPlanet }) => isPlanet && availablePoints.includes(value)
132 .map(({ value }) => value);
134 const invitePlanets = spawnedPoints
136 outgoingPoints.filter((p: number) => !spawnedPoints.includes(p))
138 .filter((p: number) => !pendingSpawns.has(p))
141 setInvites(curPoint, []);
143 const newInvites: Invite[] = [];
144 // Iterate over all of the stored invites, generating wallet info as necessary
145 const storedInvites = getStoredInvites(ls);
147 for (let i = 0; i < invitePlanets.length; i++) {
148 useInviteStore.setState({ generatingNum: i + 1 });
149 const planet = invitePlanets[i];
150 const storedInvite = storedInvites[planet];
152 storedInvite || (await generateInviteInfo(planet, _authToken));
154 // TODO: check if the invite point's owner still matches the deterministic wallet address
155 const planetInfo = await api.getPoint(planet);
156 setStoredInvite(ls, invite);
159 invite.owner.toLowerCase() ===
160 planetInfo.ownership?.owner?.address &&
161 planetInfo.dominion === POINT_DOMINIONS.L2
163 addInvite(curPoint, invite);
164 newInvites.push(invite);
168 setStoredInvite(ls, invite);
172 useInviteStore.setState({ loading: false, generatingNum: 0 });
176 console.warn('ERROR GETTING INVITES', error);
177 useInviteStore.setState({ loading: false, generatingNum: 0 });
180 const pointIndex = updatingInvites.current.indexOf(point.value);
181 updatingInvites.current.splice(pointIndex, 1);
192 setPendingTransactions,
195 const generateInviteCodes = useCallback(
196 async (numInvites: number) => {
197 const _point = need.point(pointCursor);
198 const _contracts = contracts.getOrElse(null);
199 const _web3 = web3.getOrElse(null);
200 const _wallet = wallet.getOrElse(null);
201 const _authToken = authToken.getOrElse(null);
202 const _details = getDetails(_point);
204 if (!_contracts || !_web3 || !_wallet || !_authToken || !_details) {
205 // not using need because we want a custom error
206 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
209 // TODO: remove when UI disables management proxy from spawning invites
210 // if (Just.hasInstance(authMnemonic)) {
212 // "Auth key Error: A management mnemonic can't create invite codes"
215 let planets: UnspawnedPoints = await api.getUnspawned(_point);
217 const pendingTxs = await api.getPendingByShip(_point);
219 const pendingSpawns = getPendingSpawns(pendingTxs);
221 planets = planets.filter((point: number) => !pendingSpawns.has(point));
223 const invites: Invite[] = [];
225 const proxy = point.getSpawnProxy();
227 if (proxy === undefined)
228 throw new Error("Error: Address doesn't match proxy");
230 const nonce = await api.getNonce({
235 for (let i = 0; i < numInvites && planets[i]; i++) {
236 useInviteStore.setState({ generatingNum: i + 1 });
237 const planet = planets[i];
238 const nonceInc = i + nonce;
240 const { ticket, inviteWallet } = await generateInviteWallet(
245 await submitL2Transaction({
247 address: _wallet.address,
252 pointToSpawn: planet,
259 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
263 await submitL2Transaction({
270 type: 'configureKeys',
276 await registerProxyAddress(
283 inviteWallet.management.keys.address,
289 await submitL2Transaction({
294 type: 'transferPoint',
296 address: inviteWallet.ownership.keys.address,
306 owner: inviteWallet.ownership.keys.address,
310 setStoredInvites(ls, invites);
311 console.log('getting invites from code generation');
314 `Your invite${invites.length > 1 ? 's have' : ' has'} been generated!`
333 const generateCsv = useCallback(async () => {
334 const invites = await getInvites();
337 throw new Error('There was an error creating the CSV');
340 const csv = invites.reduce(
341 (csvData, { ticket, planet }, ind) =>
342 (csvData += generateCsvLine(ind, ticket, planet)),
343 'Number,Planet,Invite URL,Point,Ticket\n'
346 document.getElementById('csv')?.remove();
347 const hiddenElement = document.createElement('a');
348 hiddenElement.href = `data:text/csv;charset=utf-8,${encodeURIComponent(
351 hiddenElement.target = '_blank';
352 hiddenElement.id = 'csv';
354 //provide the name for the CSV file to be downloaded
355 hiddenElement.download = generateCsvName(
357 point.patp?.slice(1) || 'bridge'
360 return hiddenElement;
361 }, [getInvites, point]);
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