on b87ca5c help sign in
bridge/src/lib/useEthereumTransaction.js
232 lines 5.8 kB history raw
1import { useCallback, useState } from 'react';
2import { Just } from 'folktale/maybe';
3import { fromWei, toWei } from 'web3-utils';
5import { useNetwork } from 'store/network';
6import { useWallet } from 'store/wallet';
7import { usePointCursor } from 'store/pointCursor';
9import { GAS_LIMITS } from './constants';
10import {
11 signTransaction,
12 sendSignedTransaction,
13 waitForTransactionConfirm,
14 hexify,
15} from './txn';
16import * as need from './need';
17import useLifecycle from './useLifecycle';
18import { ensureFundsFor } from './tank';
20const STATE = {
21 NONE: 'NONE',
22 SIGNED: 'SIGNED',
23 BROADCASTED: 'BROADCASTED',
24 CONFIRMED: 'CONFIRMED',
25};
27/**
28 * manage the state around sending and confirming an ethereum transaction
29 * @param {number} initialGasLimit
30 */
31export default function useEthereumTransaction(
32 initialGasLimit = GAS_LIMITS.DEFAULT,
33 initialGasPrice = 20
34) {
35 const { wallet, walletType, walletHdPath } = useWallet();
36 const { web3, networkType } = useNetwork();
37 const { pointCursor } = usePointCursor();
39 const _web3 = need.web3(web3);
40 const _wallet = need.wallet(wallet);
42 const [error, _setError] = useState();
43 const setError = useCallback(
44 error => {
45 _setError(error);
46 if (error) {
47 console.error(error);
48 }
49 },
50 [_setError]
51 );
52 const [state, setState] = useState(STATE.NONE);
53 const [chainId, setChainId] = useState();
54 const [nonce, setNonce] = useState();
55 const [gasPrice, setGasPrice] = useState(initialGasPrice); // gwei
56 const [suggestedGasPrice, setSuggestedGasPrice] = useState(gasPrice); // gwei
57 const [gasLimit] = useState(initialGasLimit);
58 const [unsignedTransaction, setUnsignedTransaction] = useState();
59 const [signedTransaction, setSignedTransaction] = useState();
60 const [txHash, setTxHash] = useState();
61 const [needFunds, setNeedFunds] = useState();
63 const initializing = nonce === undefined || chainId === undefined;
64 const constructed = !!unsignedTransaction;
65 const isDefaultState = state === STATE.NONE;
66 const signed = state === STATE.SIGNED;
67 const broadcasted = state === STATE.BROADCASTED;
68 const confirmed = state === STATE.CONFIRMED;
70 // disable the inputs when:
71 const inputsLocked = signed || broadcasted || confirmed;
73 // we can sign when:
74 const canSign = !initializing && constructed;
76 const construct = useCallback(txn => setUnsignedTransaction(txn), [
77 setUnsignedTransaction,
78 ]);
80 const unconstruct = useCallback(() => setUnsignedTransaction(undefined), [
81 setUnsignedTransaction,
82 ]);
84 const generateAndSign = useCallback(async () => {
85 try {
86 setError(undefined);
88 const txn = await signTransaction({
89 wallet: _wallet,
90 walletType,
91 walletHdPath,
92 networkType,
93 txn: unsignedTransaction,
94 nonce,
95 chainId,
96 gasPrice: gasPrice.toFixed(0),
97 gasLimit,
98 });
100 setSignedTransaction(txn);
101 setState(STATE.SIGNED);
102 } catch (error) {
103 setError(error);
104 }
105 }, [
106 _wallet,
107 chainId,
108 gasLimit,
109 gasPrice,
110 networkType,
111 nonce,
112 setError,
113 unsignedTransaction,
114 walletHdPath,
115 walletType,
116 ]);
118 const broadcast = useCallback(async () => {
119 try {
120 setError(undefined);
122 const rawTx = hexify(signedTransaction.serialize());
123 const cost = toWei(gasPrice.toFixed(0), 'gwei') * gasLimit;
125 let usedTank = false;
126 // if this ethereum transaction is being executed by a specific point
127 // see if we can use the tank
128 if (Just.hasInstance(pointCursor)) {
129 usedTank = await ensureFundsFor(
130 _web3,
131 pointCursor.value,
132 _wallet.address,
133 cost,
134 [rawTx],
135 (address, minBalance, balance) =>
136 setNeedFunds({ address, minBalance, balance }),
137 () => setNeedFunds(undefined)
138 );
139 }
141 const txHash = await sendSignedTransaction(
142 _web3,
143 signedTransaction,
144 /* doubtNonceError */ usedTank
145 );
147 setState(STATE.BROADCASTED);
148 setTxHash(txHash);
150 await waitForTransactionConfirm(_web3, txHash);
152 setState(STATE.CONFIRMED);
153 } catch (error) {
154 setError(error);
155 }
156 }, [
157 _wallet.address,
158 _web3,
159 gasLimit,
160 gasPrice,
161 pointCursor,
162 setError,
163 signedTransaction,
164 ]);
166 const reset = useCallback(() => {
167 setUnsignedTransaction(undefined);
168 setTxHash(undefined);
169 setSignedTransaction(undefined);
170 setGasPrice(suggestedGasPrice);
171 setState(STATE.NONE);
172 setError(undefined);
173 setNeedFunds(undefined);
174 }, [suggestedGasPrice, setError]);
176 const resetGasPrice = useCallback(() => setGasPrice(suggestedGasPrice), [
177 setGasPrice,
178 suggestedGasPrice,
179 ]);
181 useLifecycle(() => {
182 (async () => {
183 try {
184 setError(undefined);
185 const [nonce, chainId, estimatedGasPrice] = await Promise.all([
186 _web3.eth.getTransactionCount(_wallet.address),
187 _web3.eth.net.getId(),
188 _web3.eth.getGasPrice(),
189 ]);
191 setNonce(nonce);
192 setChainId(chainId);
193 const gasPrice = parseInt(fromWei(estimatedGasPrice, 'gwei'), 10);
194 setSuggestedGasPrice(gasPrice);
195 setGasPrice(gasPrice);
196 } catch (error) {
197 setError(error);
198 }
199 })();
200 });
202 const values = {
203 isDefaultState,
204 initializing,
205 construct,
206 unconstruct,
207 constructed,
208 canSign,
209 generateAndSign,
210 signed,
211 broadcast,
212 broadcasted,
213 confirmed,
214 reset,
215 error,
216 inputsLocked,
217 //
218 txHash,
219 signedTransaction,
220 gasPrice,
221 setGasPrice,
222 resetGasPrice,
223 nonce,
224 chainId,
225 needFunds,
226 };
228 return {
229 ...values,
230 bind: values,
231 };