on 5ca3bf8 help sign in
bridge/src/views/flows/JustInvite.js
256 lines 7.4 kB history raw
1//NOTE repurposed from /views/Activate/PassportTransfer,
2// with a lot of logic from /lib/useInviter
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
7import React, { useState, useCallback } from 'react';
8import { Grid, Text, ErrorText, H4 } from 'indigo-react';
9import cn from 'classnames';
10import ob from 'urbit-ob';
11import { toBN } from 'web3-utils';
13import { useNetwork } from 'store/network';
14import { useWallet } from 'store/wallet';
16import * as wg from 'lib/walletgen';
17import * as tank from 'lib/tank';
18import { GAS_LIMITS } from 'lib/constants';
19import {
20 signTransaction,
21 sendSignedTransaction,
22 waitForTransactionConfirm,
23 hexify,
24} from 'lib/txn';
25import useGasPrice from 'lib/useGasPrice';
26import useLifecycle from 'lib/useLifecycle';
27import useBlockWindowClose from 'lib/useBlockWindowClose';
28import { useFlowCommand } from 'lib/flowCommand';
29import { hasReceived, sendMail } from 'lib/inviteMail';
30import { safeToWei } from 'lib/lib';
32import LoadingBar from 'components/LoadingBar';
33import { RestartButton } from 'components/Buttons';
34import NeedFundsNotice from 'components/NeedFundsNotice';
36import * as az from 'azimuth-js';
38const 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};
58export 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);
67 useBlockWindowClose();
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]);
82 const sendInvite = useCallback(async () => {
83 setGeneralError(false);
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 }
95 const _as = ob.patp2dec(flow.as);
96 const _ship = ob.patp2dec(flow.ship);
97 const _email = flow.email;
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);
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);
116 // generate invite code + address
117 const { ticket, owner } = await wg.generateTemporaryDeterministicWallet(
118 _ship,
119 _authToken
120 );
121 setProgress(0.3);
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);
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);
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);
162 // send and await transaction
163 const txHash = await sendSignedTransaction(_web3, signedTx, tankWasUsed);
164 await waitForTransactionConfirm(_web3, txHash);
165 setProgress(0.9);
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 } catch (error) {
172 // some generic error
173 setGeneralError(error);
174 }
175 }, [
176 wallet,
177 web3,
178 contracts,
179 networkType,
180 authToken,
181 flow,
182 gasPrice,
183 walletHdPath,
184 walletType,
185 ]);
187 useLifecycle(() => {
188 sendInvite();
189 });
191 const goToRestart = useCallback(() => {
192 sendInvite();
193 }, [sendInvite]);
195 const renderAdditionalInfo = () => {
196 if (generalError) {
197 console.log(generalError);
198 let msg =
199 typeof generalError === 'string'
200 ? generalError
201 : generalError.message || 'Something went wrong!';
202 return (
203 <>
204 <Grid.Item full className="mt8">
205 <ErrorText>{msg}</ErrorText>
206 </Grid.Item>
207 <Grid.Item
208 full
209 className="mt3"
210 as={RestartButton}
211 solid
212 onClick={goToRestart}>
213 Restart
214 </Grid.Item>
215 </>
216 );
217 }
219 if (needFunds) {
220 return (
221 <Grid.Item full as={NeedFundsNotice} className="mt8" {...needFunds} />
222 );
223 }
225 // if (progress < 1.0) {
226 // return (
227 // <Grid.Item full as={WarningBox} className="mt8">
228 // Never give your Master Ticket to anyone
229 // </Grid.Item>
230 // );
231 // }
233 return null;
234 };
236 return (
237 <Grid className={cn(className, 'auto-rows-min')}>
238 <Grid.Item full as={H4}>
239 {labelForProgress(progress)}
240 </Grid.Item>
241 <Grid>
242 <Grid.Item full as={Grid} className="mt3" gap={3}>
243 <Grid.Item full as={LoadingBar} progress={progress} />
244 <Grid.Item full>
245 <Text className="f5 green4">
246 This process can take up to 5 minutes to complete. Don't close
247 this page until the process is complete.
248 </Text>
249 </Grid.Item>
250 </Grid.Item>
252 {renderAdditionalInfo()}
253 </Grid>
254 </Grid>
255 );