@@ -0,0 +1,259 @@
1+//NOTE repurposed from /views/Activate/PassportTransfer,
2+// with a lot of logic from /lib/useInviter
3+
4+//TODO display "you may now _close this window_" cta after calling a flow cb
5+//TODO make sure we also call failure cb if we get closed prematurely
6+
7+import React, { useState, useCallback } from 'react';
8+import { Grid, Text, ErrorText, H4 } from 'indigo-react';
9+import cn from 'classnames';
10+import ob from 'urbit-ob';
11+import { toBN } from 'web3-utils';
12+
13+import { useNetwork } from 'store/network';
14+import { useWallet } from 'store/wallet';
15+
16+import * as wg from 'lib/walletgen';
17+import * as tank from 'lib/tank';
18+import { GAS_LIMITS } from 'lib/constants';
19+import {
20+ signTransaction,
21+ sendSignedTransaction,
22+ waitForTransactionConfirm,
23+ hexify,
24+} from 'lib/txn';
25+import useGasPrice from 'lib/useGasPrice';
26+import useLifecycle from 'lib/useLifecycle';
27+import useBlockWindowClose from 'lib/useBlockWindowClose';
28+import { useFlowCommand } from 'lib/flowCommand';
29+import { hasReceived, sendMail } from 'lib/inviteMail';
30+import { safeToWei } from 'lib/lib';
31+
32+import LoadingBar from 'components/LoadingBar';
33+import { RestartButton } from 'components/Buttons';
34+import NeedFundsNotice from 'components/NeedFundsNotice';
35+
36+import * as az from 'azimuth-js';
37+
38+const labelForProgress = progress => {
39+ if (progress <= 0) {
40+ return 'Checking sanity...';
41+ } else if (progress <= 0.2) {
42+ return 'Generating invite...';
43+ } else if (progress <= 0.3) {
44+ return 'Building transaction...';
45+ } else if (progress <= 0.4) {
46+ return 'Signing transaction...';
47+ } else if (progress <= 0.5) {
48+ return 'Funding transaction...';
49+ } else if (progress <= 0.7) {
50+ return 'Sending transaction...';
51+ } else if (progress <= 0.9) {
52+ return 'Sending invite email...';
53+ } else {
54+ return 'Done';
55+ }
56+};
57+
58+export default function JustInvite({ className, resetActivateRouter }) {
59+ const { wallet, walletType, walletHdPath, authToken } = useWallet();
60+ const flow = useFlowCommand();
61+ const { web3, contracts, networkType } = useNetwork();
62+ const { gasPrice } = useGasPrice();
63+ const [generalError, setGeneralError] = useState();
64+ const [progress, setProgress] = useState(0);
65+ const [needFunds, setNeedFunds] = useState(null);
66+
67+ useBlockWindowClose();
68+
69+ // useEffect(() => {
70+ // switch (type) {
71+ // case 'progress':
72+ // return setProcesss(state);
73+ // case 'askFunding':
74+ // return setNeedFunds(value);
75+ // case 'gotFunding':
76+ // return setNeedFunds(false);
77+ // default:
78+ // console.error(`Unknown update: ${type}`);
79+ // }
80+ // }, [progress]);
81+
82+ const sendInvite = useCallback(async () => {
83+ setGeneralError(false);
84+
85+ try {
86+ const _contracts = contracts.getOrElse(null);
87+ const _web3 = web3.getOrElse(null);
88+ const _wallet = wallet.getOrElse(null);
89+ const _authToken = authToken.getOrElse(null);
90+ if (!_contracts || !_web3 || !_wallet || !_authToken) {
91+ // not using need because we want a custom error
92+ throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
93+ }
94+
95+ const _as = ob.patp2dec(flow.as);
96+ const _ship = ob.patp2dec(flow.ship);
97+ const _email = flow.email;
98+
99+ console.log('xx checking email');
100+ // check if email not invited before
101+ if (await hasReceived(_email)) {
102+ throw new Error(
103+ `This email (${_email}) has already received an invite.`
104+ );
105+ }
106+ setProgress(0.1);
107+
108+ console.log('xx checking owner', _wallet.address, _as);
109+ // check if logged in with flow.as's owner
110+ //TODO we could move this check to the login screen
111+ if (!(await az.azimuth.isOwner(_contracts, _as, _wallet.address))) {
112+ throw new Error(`You are not logged in as ${flow.as}'s owner.'`);
113+ }
114+ setProgress(0.2);
115+
116+ // generate invite code + address
117+ const { ticket, owner } = await wg.generateTemporaryDeterministicWallet(
118+ _ship,
119+ _authToken
120+ );
121+ setProgress(0.3);
122+
123+ // generate invite tx
124+ const inviteTx = az.delegatedSending.sendPoint(
125+ _contracts,
126+ _as,
127+ _ship,
128+ owner.keys.address
129+ );
130+ setProgress(0.4);
131+
132+ // sign invite tx
133+ const signedTx = await signTransaction({
134+ wallet: _wallet,
135+ walletType,
136+ walletHdPath,
137+ networkType,
138+ chainId: await _web3.eth.net.getId(),
139+ nonce: await _web3.eth.getTransactionCount(_wallet.address),
140+ txn: inviteTx,
141+ gasPrice: gasPrice.toFixed(), // expects string gwei
142+ gasLimit: GAS_LIMITS.GIFT_PLANET.toFixed(),
143+ });
144+ const rawTx = hexify(signedTx.serialize());
145+ setProgress(0.5);
146+
147+ // ensure funds
148+ const totalCost = toBN(GAS_LIMITS.GIFT_PLANET).mul(toBN(gasPrice));
149+ const tankWasUsed = await tank.ensureFundsFor(
150+ _web3,
151+ _as,
152+ _wallet.address,
153+ walletType,
154+ safeToWei(totalCost, 'gwei'),
155+ [rawTx],
156+ (address, minBalance, balance) =>
157+ setNeedFunds({ address, minBalance, balance }),
158+ () => setNeedFunds(undefined)
159+ );
160+ setProgress(0.7);
161+
162+ // send and await transaction
163+ const txHash = await sendSignedTransaction(_web3, signedTx, tankWasUsed);
164+ await waitForTransactionConfirm(_web3, txHash);
165+ setProgress(0.9);
166+
167+ // send invite email
168+ //NOTE we could chuck a "pls join group-name" message in here
169+ await sendMail(_email, ticket, flow.as, '', rawTx);
170+ setProgress(1);
171+
172+ flow.success();
173+ } catch (error) {
174+ // some generic error
175+ setGeneralError(error);
176+ flow.failure(error);
177+ }
178+ }, [
179+ wallet,
180+ web3,
181+ contracts,
182+ networkType,
183+ authToken,
184+ flow,
185+ gasPrice,
186+ walletHdPath,
187+ walletType,
188+ ]);
189+
190+ useLifecycle(() => {
191+ sendInvite();
192+ });
193+
194+ const goToRestart = useCallback(() => {
195+ sendInvite();
196+ }, [sendInvite]);
197+
198+ const renderAdditionalInfo = () => {
199+ if (generalError) {
200+ console.log(generalError);
201+ let msg =
202+ typeof generalError === 'string'
203+ ? generalError
204+ : generalError.message || 'Something went wrong!';
205+ return (
206+ <>
207+ <Grid.Item full className="mt8">
208+ <ErrorText>{msg}</ErrorText>
209+ </Grid.Item>
210+ <Grid.Item
211+ full
212+ className="mt3"
213+ as={RestartButton}
214+ solid
215+ onClick={goToRestart}>
216+ Restart
217+ </Grid.Item>
218+ </>
219+ );
220+ }
221+
222+ if (needFunds) {
223+ return (
224+ <Grid.Item full as={NeedFundsNotice} className="mt8" {...needFunds} />
225+ );
226+ }
227+
228+ // if (progress < 1.0) {
229+ // return (
230+ // <Grid.Item full as={WarningBox} className="mt8">
231+ // Never give your Master Ticket to anyone
232+ // </Grid.Item>
233+ // );
234+ // }
235+
236+ return null;
237+ };
238+
239+ return (
240+ <Grid className={cn(className, 'auto-rows-min')}>
241+ <Grid.Item full as={H4}>
242+ {labelForProgress(progress)}
243+ </Grid.Item>
244+ <Grid>
245+ <Grid.Item full as={Grid} className="mt3" gap={3}>
246+ <Grid.Item full as={LoadingBar} progress={progress} />
247+ <Grid.Item full>
248+ <Text className="f5 green4">
249+ This process can take up to 5 minutes to complete. Don't close
250+ this page until the process is complete.
251+ </Text>
252+ </Grid.Item>
253+ </Grid.Item>
254+
255+ {renderAdditionalInfo()}
256+ </Grid>
257+ </Grid>
258+ );
259+}