on 6ecd461 help sign in
bridge/src/lib/useEthereumTransaction.js
230 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';
19import useDeepEqualReference from './useDeepEqualReference';
21const STATE = {
22 NONE: 'NONE',
23 SIGNED: 'SIGNED',
24 BROADCASTED: 'BROADCASTED',
25 CONFIRMED: 'CONFIRMED',
26};
28/**
29 * manage the state around sending and confirming an ethereum transaction
30 * @param {number} initialGasLimit
31 */
32export default function useEthereumTransaction(
33 initialGasLimit = GAS_LIMITS.DEFAULT,
34 initialGasPrice = 20
35) {
36 const { wallet, walletType, walletHdPath } = useWallet();
37 const { web3, networkType } = useNetwork();
38 const { pointCursor } = usePointCursor();
40 const _web3 = need.web3(web3);
41 const _wallet = need.wallet(wallet);
43 const [error, _setError] = useState();
44 const setError = useCallback(
45 error => {
46 _setError(error);
47 if (error) {
48 console.error(error);
49 }
50 },
51 [_setError]
52 );
53 const [state, setState] = useState(STATE.NONE);
54 const [chainId, setChainId] = useState();
55 const [nonce, setNonce] = useState();
56 const [gasPrice, setGasPrice] = useState(initialGasPrice); // gwei
57 const [suggestedGasPrice, setSuggestedGasPrice] = useState(gasPrice); // gwei
58 const [gasLimit] = useState(initialGasLimit);
59 const [unsignedTransaction, setUnsignedTransaction] = useState();
60 const [signedTransaction, setSignedTransaction] = useState();
61 const [txHash, setTxHash] = useState();
62 const [needFunds, setNeedFunds] = useState();
64 const initializing = nonce === undefined || chainId === undefined;
65 const constructed = !!unsignedTransaction;
66 const isDefaultState = state === STATE.NONE;
67 const signed = state === STATE.SIGNED;
68 const broadcasted = state === STATE.BROADCASTED;
69 const confirmed = state === STATE.CONFIRMED;
71 // disable the inputs when:
72 const inputsLocked = signed || broadcasted || confirmed;
74 // we can sign when:
75 const canSign = !initializing && constructed;
77 const construct = useCallback(txn => setUnsignedTransaction(txn), [
78 setUnsignedTransaction,
79 ]);
81 const unconstruct = useCallback(() => setUnsignedTransaction(undefined), [
82 setUnsignedTransaction,
83 ]);
85 const generateAndSign = useCallback(async () => {
86 try {
87 setError(undefined);
89 const txn = await signTransaction({
90 wallet: _wallet,
91 walletType,
92 walletHdPath,
93 networkType,
94 txn: unsignedTransaction,
95 nonce,
96 chainId,
97 gasPrice: gasPrice.toFixed(0),
98 gasLimit,
99 });
101 setSignedTransaction(txn);
102 setState(STATE.SIGNED);
103 } catch (error) {
104 setError(error);
105 }
106 }, [
107 _wallet,
108 chainId,
109 gasLimit,
110 gasPrice,
111 networkType,
112 nonce,
113 setError,
114 unsignedTransaction,
115 walletHdPath,
116 walletType,
117 ]);
119 const broadcast = useCallback(async () => {
120 try {
121 setError(undefined);
123 const rawTx = hexify(signedTransaction.serialize());
124 const cost = toWei(gasPrice.toFixed(0), 'gwei') * gasLimit;
126 let usedTank = false;
127 // if this ethereum transaction is being executed by a specific point
128 // see if we can use the tank
129 if (Just.hasInstance(pointCursor)) {
130 usedTank = await ensureFundsFor(
131 _web3,
132 pointCursor.value,
133 _wallet.address,
134 cost,
135 [rawTx],
136 (address, minBalance, balance) =>
137 setNeedFunds({ address, minBalance, balance }),
138 () => setNeedFunds(undefined)
139 );
140 }
142 const txHash = await sendSignedTransaction(
143 _web3,
144 signedTransaction,
145 /* doubtNonceError */ usedTank
146 );
148 setState(STATE.BROADCASTED);
149 setTxHash(txHash);
151 await waitForTransactionConfirm(_web3, txHash);
153 setState(STATE.CONFIRMED);
154 } catch (error) {
155 setError(error);
156 }
157 }, [
158 _wallet.address,
159 _web3,
160 gasLimit,
161 gasPrice,
162 pointCursor,
163 setError,
164 signedTransaction,
165 ]);
167 const reset = useCallback(() => {
168 setUnsignedTransaction(undefined);
169 setTxHash(undefined);
170 setSignedTransaction(undefined);
171 setGasPrice(suggestedGasPrice);
172 setState(STATE.NONE);
173 setError(undefined);
174 setNeedFunds(undefined);
175 }, [suggestedGasPrice, setError]);
177 const resetGasPrice = useCallback(() => setGasPrice(suggestedGasPrice), [
178 setGasPrice,
179 suggestedGasPrice,
180 ]);
182 useLifecycle(() => {
183 (async () => {
184 try {
185 setError(undefined);
186 const [nonce, chainId, estimatedGasPrice] = await Promise.all([
187 _web3.eth.getTransactionCount(_wallet.address),
188 _web3.eth.net.getId(),
189 _web3.eth.getGasPrice(),
190 ]);
192 setNonce(nonce);
193 setChainId(chainId);
194 const gasPrice = parseInt(fromWei(estimatedGasPrice, 'gwei'), 10);
195 setSuggestedGasPrice(gasPrice);
196 setGasPrice(gasPrice);
197 } catch (error) {
198 setError(error);
199 }
200 })();
201 });
203 const values = useDeepEqualReference({
204 isDefaultState,
205 initializing,
206 construct,
207 unconstruct,
208 constructed,
209 canSign,
210 generateAndSign,
211 signed,
212 broadcast,
213 broadcasted,
214 confirmed,
215 reset,
216 error,
217 inputsLocked,
218 //
219 txHash,
220 signedTransaction,
221 gasPrice,
222 setGasPrice,
223 resetGasPrice,
224 nonce,
225 chainId,
226 needFunds,
227 });
229 return { ...values, bind: values };