on df0c310 help sign in
bridge/src/views/Point/MigrateL2.tsx
362 lines 11 kB history raw
1import React, { useState, useEffect, useCallback } from 'react';
2import { Grid } from 'indigo-react';
3import { ecliptic } from 'azimuth-js';
4import * as ob from 'urbit-ob';
6import {
7 getHideMigrationMessage,
8 storeHideMigrationMessage,
9} from 'store/storage/roller';
10import { useNetwork } from 'store/network';
11import { usePointCache } from 'store/pointCache';
12import { useRollerStore } from 'store/rollerStore';
14import { useLocalRouter } from 'lib/LocalRouter';
15import * as need from 'lib/need';
16import Point, { PointField } from 'lib/types/Point';
17import useRoller from 'lib/useRoller';
18import { L1TxnType } from 'lib/types/PendingL1Transaction';
19import useEthereumTransaction from 'lib/useEthereumTransaction';
20import { DUMMY_L2_ADDRESS, GAS_LIMITS } from 'lib/constants';
21import { isPlanet } from 'lib/utils/point';
23import View from 'components/View';
24import L2BackHeader from 'components/L2/Headers/L2BackHeader';
25import BodyPane from 'components/L2/Window/BodyPane';
26import HeaderPane from 'components/L2/Window/HeaderPane';
27import Window from 'components/L2/Window/Window';
28import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
29import Dropdown from 'components/L2/Dropdowns/Dropdown';
30import Sigil from 'components/Sigil';
31import { ReactComponent as StarIcon } from 'assets/star.svg';
33import {
34 Box,
35 Button,
36 Checkbox,
37 Icon,
38 RadioButton,
39 Row,
40} from '@tlon/indigo-react';
42import './MigrateL2.scss';
44const PointEntry = ({
45 point,
46 select,
47}: {
48 point: number;
49 select?: () => void;
50}) => {
51 if (point < 0) {
52 return (
53 <Row className="entry" onClick={select}>
54 <Box>Select a point</Box>
55 </Row>
56 );
57 }
59 const patp = ob.patp(point);
61 return (
62 <Row className="entry" onClick={select}>
63 <Box className="sigil">
64 <Sigil patp={patp} size={1} colors={['#000000', '#FFFFFF']} />
65 </Box>
66 <Box className="mono">{patp}</Box>
67 </Row>
68 );
69};
71const useMigrate = () => {
72 const { contracts }: any = useNetwork();
73 const { syncDates }: any = usePointCache();
75 const _contracts = need.contracts(contracts);
77 const [selectedPoint, setSelectedPoint] = useState<number | null>(null);
79 return useEthereumTransaction(
80 useCallback(
81 (selectedPoint: number, transfer: boolean) => {
82 if (selectedPoint < 0) {
83 return;
84 }
85 setSelectedPoint(selectedPoint);
86 return transfer || isPlanet(selectedPoint)
87 ? ecliptic.transferPoint(
88 _contracts,
89 selectedPoint,
90 DUMMY_L2_ADDRESS,
91 false
92 )
93 : ecliptic.setSpawnProxy(_contracts, selectedPoint, DUMMY_L2_ADDRESS);
94 },
95 [_contracts]
96 ),
97 useCallback(() => syncDates(selectedPoint), [selectedPoint, syncDates]),
98 GAS_LIMITS.DEFAULT
99 );
100};
102export default function MigrateL2() {
103 const { pop }: any = useLocalRouter();
104 const { point, pointList } = useRollerStore();
105 const { checkForUpdates } = useRoller();
106 const l1Points = pointList.filter(({ canMigrate }) => canMigrate);
108 const [proceed, setProceed] = useState(false);
109 const [hideMessage, setHideMessage] = useState(false);
110 const [showDropdown, setShowDropdown] = useState(false);
111 const [transfer, setTransfer] = useState(true);
113 const [selectedPoint, setSelectedPoint] = useState(
114 point.isL1 ? point : l1Points[0]
115 );
116 const hideInfo = getHideMigrationMessage();
118 const { construct, unconstruct, bind, completed, txHashes } = useMigrate();
120 useEffect(() => {
121 if (point.isGalaxy && point.isL1) {
122 setTransfer(false);
123 construct(point.value, false);
124 } else if (point.isL1) {
125 construct(point.value, transfer);
126 } else {
127 unconstruct();
128 }
129 }, []); // eslint-disable-line react-hooks/exhaustive-deps
131 useEffect(() => {
132 if (completed) {
133 const message = transfer
134 ? `${selectedPoint.patp} has been migrated to Layer 2!`
135 : `${selectedPoint.patp}'s spawn proxy has been set to Layer 2!`;
137 checkForUpdates({
138 point: selectedPoint,
139 message,
140 notify: true,
141 field: PointField.layer,
142 l1Txn: {
143 id: `${DUMMY_L2_ADDRESS}-${selectedPoint.value}`,
144 point: selectedPoint.value,
145 type: transfer ? L1TxnType.migrate : L1TxnType.migrateSpawn,
146 hash: txHashes[0],
147 time: new Date().getTime(),
148 },
149 });
151 pop();
152 }
153 }, [completed, pop]); // eslint-disable-line react-hooks/exhaustive-deps
155 const goBack = useCallback(() => {
156 if (proceed) {
157 setProceed(false);
158 } else {
159 pop();
160 }
161 }, [pop, proceed, setProceed]);
163 const goProceed = useCallback(() => {
164 if (hideMessage) {
165 storeHideMigrationMessage();
166 }
168 setProceed(true);
169 }, [hideMessage]);
171 const selectPoint = useCallback(
172 (point: Point) => {
173 if (point.isPlanet || (point.isStar && point.isL2Spawn)) {
174 setTransfer(true);
175 } else if (point.isGalaxy) {
176 setTransfer(false);
177 }
178 setSelectedPoint(point);
179 setShowDropdown(false);
180 bind.reset();
181 construct(point.value, transfer);
182 },
183 [setSelectedPoint, setShowDropdown, bind, construct, transfer]
184 );
186 const toggleTransfer = useCallback(() => {
187 construct(selectedPoint.value, !transfer);
188 setTransfer(!transfer);
189 }, [transfer, setTransfer, construct, selectedPoint]);
191 const getContent = () => {
192 if (l1Points.length === 0) {
193 return (
194 <Box className="content">
195 <Box className="ship-selector">
196 <Box>All of your eligible ships are already on Layer 2.</Box>
197 </Box>
198 </Box>
199 );
200 }
202 const isStar = Boolean(selectedPoint?.isStar);
203 const isGalaxy = Boolean(selectedPoint?.isGalaxy);
205 if (proceed) {
206 return (
207 <Box className="content">
208 <Box className="ship-selector">
209 <Box>Ship</Box>
210 <Box className="select-ship">Select one of your ships</Box>
211 <Dropdown
212 open={showDropdown}
213 toggleOpen={() => setShowDropdown(!showDropdown)}
214 value={<PointEntry point={selectedPoint.value} />}
215 className="migrate-selector">
216 {l1Points.map((p: Point) => (
217 <PointEntry
218 point={p.value}
219 key={p.value}
220 select={() => selectPoint(p)}
221 />
222 ))}
223 </Dropdown>
224 {isStar && !selectedPoint?.isL2Spawn && (
225 <Box className="transfer-spawn-selector">
226 <Row onClick={toggleTransfer} className="transfer">
227 <RadioButton
228 color="rgba(0,0,0,0.3)"
229 className={`radio ${transfer ? 'selected' : ''}`}
230 selected={transfer}
231 name="transferType"
232 />
233 Transfer Ship
234 </Row>
235 <Row onClick={toggleTransfer} className="transfer">
236 <RadioButton
237 color="rgba(0,0,0,0.3)"
238 className={`radio ${!transfer ? 'selected' : ''}`}
239 selected={!transfer}
240 name="transferType"
241 />
242 Set Spawn Proxy
243 </Row>
244 </Box>
245 )}
246 {isStar && (
247 <Box className="mt4">
248 Transferring this point will allow you to conduct all
249 transactions on Layer 2. Setting the spawn proxy of a star to
250 Layer 2 will allow you to create planet invites for free, but
251 will require you to do all other transactions on Layer 1.
252 </Box>
253 )}
254 {isGalaxy && (
255 <Box className="mt4">
256 Setting the spawn proxy of this galaxy to Layer 2 will allow you
257 to spawn stars on Layer 2 for free, but will still require you
258 to do all other transactions on Layer 1.
259 </Box>
260 )}
261 </Box>
262 <Grid.Item
263 full
264 as={InlineEthereumTransaction}
265 label={`${
266 transfer ? 'Transfer ship' : 'Set spawn proxy'
267 } to Layer 2`}
268 {...bind}
269 onReturn={() => pop()}
270 />
271 </Box>
272 );
273 }
275 return (
276 <Box className="content">
277 <Box className="message">
278 You are about to migrate a node from Layer 1 to Layer 2. This will
279 make transactions faster and cheaper. Here are some things you should
280 know.{' '}
281 <a
282 className="bold"
283 target="_blank"
284 rel="noreferrer"
285 href="https://urbit.org/docs/azimuth/l2/layer2">
286 Learn More
287 </a>
288 </Box>
289 {!hideInfo && (
290 <Box className="info-list">
291 <Row className="info-row">
292 <Box className="icon-background">
293 <StarIcon className="icon" />
294 </Box>
295 <Box className="info-message">
296 <span className="warning">
297 Migration to Layer 2 is irreversible
298 </span>
299 {'. '}
300 You will not be able to revert back to Layer 1.
301 </Box>
302 </Row>
303 <Row className="info-row">
304 <Box className="icon-background">
305 <Box className="icon">$</Box>
306 </Box>
307 <Box className="info-message">
308 A migration is expensive, but a one-time fee. The estimated
309 price is <span className="bold">$120</span>. Transaction fees
310 will be subsidized on Layer 2.
311 </Box>
312 </Row>
313 <Row className="info-row">
314 <Box className="icon-background">
315 <Icon icon="Clock" className="icon" color="white" />
316 </Box>
317 {/* <HistoryIcon className="icon" /> */}
318 <Box className="info-message">
319 All transactions will now be pushed at the end of the day. A
320 timer will show when the next push is.
321 </Box>
322 </Row>
323 </Box>
324 )}
325 {!hideInfo && (
326 <Row className="hide-row">
327 <Checkbox
328 className="checkbox"
329 selected={hideMessage}
330 onClick={() => setHideMessage(!hideMessage)}
331 />
332 <Box className="dont-show">Do not show this again</Box>
333 </Row>
334 )}
335 <Row className="buttons">
336 <Button className="cancel" onClick={goBack}>
337 Cancel
338 </Button>
339 <Button className="proceed" onClick={goProceed}>
340 Proceed
341 </Button>
342 </Row>
343 </Box>
344 );
345 };
347 return (
348 <View
349 className="migrate-l2"
350 pop={pop}
351 hideBack
352 inset
353 header={<L2BackHeader back={goBack} />}>
354 <Window>
355 <HeaderPane>
356 <h5>Migrating to Layer 2</h5>
357 </HeaderPane>
358 <BodyPane>{getContent()}</BodyPane>
359 </Window>
360 </View>
361 );