1import { useCallback, useEffect, useState } from 'react';
2import { Just } from 'folktale/maybe';
3import { toBN } from 'web3-utils';
5import { useNetwork } from 'store/network';
6import { useWallet } from 'store/wallet';
7import { usePointCursor } from 'store/pointCursor';
11 DEFAULT_GAS_PRICE_GWEI,
12 PROGRESS_ANIMATION_DELAY_MS,
17 sendSignedTransaction,
18 waitForTransactionConfirm,
21import * as need from 'lib/need';
22import { ensureFundsFor } from 'lib/tank';
23import useDeepEqualReference from 'lib/useDeepEqualReference';
24import useGasPrice from 'lib/useGasPrice';
25import { timeout } from 'lib/timeout';
26import { safeFromWei, gweiToWei } from 'lib/lib';
27import { useWalletConnect } from './useWalletConnect';
28import { Transaction } from '@ethereumjs/tx';
33 FAKE_SIGNED: 'FAKE_SIGNED',
34 BROADCASTED: 'BROADCASTED',
35 CONFIRMED: 'CONFIRMED',
36 COMPLETED: 'COMPLETED',
40 * manage the state around sending and confirming an ethereum transaction
41 * @param {Transaction Function()} transactionBuilder
42 * @param {Promise<any> Function()} refetch async function called after completion
43 * @param {number} initialGasLimit gas limit
44 * @param {number} initialGasPrice gas price in gwei
46export default function useEthereumTransaction(
49 initialGasLimit = GAS_LIMITS.DEFAULT,
50 initialGasPrice = DEFAULT_GAS_PRICE_GWEI
52 const { wallet, walletType, walletHdPath } = useWallet();
53 const { web3, networkType } = useNetwork();
54 const { pointCursor } = usePointCursor();
56 signTransaction: wcSign,
57 sendTransaction: wcSend,
58 } = useWalletConnect();
60 const _web3 = need.web3(web3);
61 const _wallet = need.wallet(wallet);
63 const [error, _setError] = useState();
64 const setError = useCallback(
73 const [state, setState] = useState(STATE.NONE);
74 const [chainId, setChainId] = useState();
75 const [nonce, setNonce] = useState();
76 const { gasPrice, setGasPrice, resetGasPrice } = useGasPrice(initialGasPrice);
77 const [gasLimit] = useState(initialGasLimit);
78 const [unsignedTransactions, setUnsignedTransactions] = useState();
79 const [signedTransactions, setSignedTransactions] = useState();
80 const [txHashes, setTxHashes] = useState();
81 const [needFunds, setNeedFunds] = useState();
82 const [confirmationProgress, setConfirmationProgress] = useState(0.0);
84 const [finalCost, setFinalCost] = useState();
86 const initializing = nonce === undefined || chainId === undefined;
87 const constructed = !!unsignedTransactions;
88 const isDefaultState = state === STATE.NONE;
89 const signed = state === STATE.SIGNED || state === STATE.FAKE_SIGNED;
90 const fakeSigned = state === STATE.FAKE_SIGNED;
91 const broadcasted = state === STATE.BROADCASTED;
92 const confirmed = state === STATE.CONFIRMED;
93 const completed = state === STATE.COMPLETED;
95 // lock inputs once we're out of the default state
96 const inputsLocked = !isDefaultState;
99 const canSign = !initializing && constructed && isDefaultState;
101 const construct = useCallback(
103 setUnsignedTransactions(await transactionBuilder(...args)),
107 const unconstruct = useCallback(() => setUnsignedTransactions(undefined), [
108 setUnsignedTransactions,
111 const generateAndSign = useCallback(async () => {
115 const utxs = Array.isArray(unsignedTransactions)
116 ? unsignedTransactions
117 : [unsignedTransactions];
119 // Due to React's Rules of Hooks, optionally inject the
120 // WC transaction signing function
121 // https://reactjs.org/docs/hooks-rules.html
122 //TODO this is duplicated across relevant callsites, should refactor
124 walletType === WALLET_TYPES.WALLET_CONNECT ? wcSign : undefined;
126 walletType === WALLET_TYPES.WALLET_CONNECT ? wcSend : undefined;
128 const txns = await Promise.all(
138 gasPriceData: gasPrice,
147 setSignedTransactions(txns);
149 // Per return type of `signTransaction`, tx is either a Transaction or FakeSignedTx.
150 // We check the negation in this case, since TS cannot perform run-time check of type
151 // FakeSignedTx, but it can check for an instance of the ethereumjs/tx class.
152 if (txns.some(tx => !(tx instanceof Transaction))) {
153 setState(STATE.FAKE_SIGNED);
155 setState(STATE.SIGNED);
158 console.error('error signing txs', error);
169 unsignedTransactions,
176 const broadcast = useCallback(async () => {
178 setConfirmationProgress(0.0);
180 setState(STATE.BROADCASTED);
182 const rawTxs = signedTransactions.map(stx => hexify(stx.serialize()));
184 const cost = toBN(gasLimit)
185 .mul(gweiToWei(gasPrice.price))
186 .mul(toBN(rawTxs.length));
187 let usedTank = false;
188 // if this ethereum transaction is being executed by a specific point
189 // see if we can use the tank
190 if (Just.hasInstance(pointCursor)) {
191 usedTank = await ensureFundsFor(
198 (address, minBalance, balance) =>
199 setNeedFunds({ address, minBalance, balance }),
200 () => setNeedFunds(undefined)
204 const txHashes = await signedTransactions.reduce(
207 sendSignedTransaction(
210 /* doubtNonceError */ usedTank
211 ).then(hash => [...hashes, hash])
216 setTxHashes(txHashes);
218 await timeout(PROGRESS_ANIMATION_DELAY_MS);
220 setConfirmationProgress(0.2);
222 const receipts = await Promise.all(
223 txHashes.map(txHash => waitForTransactionConfirm(_web3, txHash))
230 toBN(receipt.gasUsed)
231 .mul(gweiToWei(gasPrice.price))
239 setConfirmationProgress(0.9);
241 setState(STATE.CONFIRMED);
256 const reset = useCallback(() => {
257 setTxHashes(undefined);
258 setSignedTransactions(undefined);
260 setChainId(undefined);
262 setState(STATE.NONE);
264 setNeedFunds(undefined);
265 setConfirmationProgress(0.0);
266 setFinalCost(undefined);
267 }, [resetGasPrice, setError]);
271 // if nonce or chainId is undefined, re-fetch on-chain info
272 if (!(nonce === undefined || chainId === undefined)) {
279 const [nonce, chainId] = await Promise.all([
280 _web3.eth.getTransactionCount(_wallet.address),
281 _web3.eth.getChainId(),
294 return () => (mounted = false);
295 }, [_wallet, _web3, setError, nonce, chainId, networkType, initialGasPrice]);
307 console.error(error);
308 // track a user-friendly error
311 'The transaction succeeded but we were unable to refresh chain state. Refresh to continue.'
321 setConfirmationProgress(1.0);
323 await timeout(PROGRESS_ANIMATION_DELAY_MS);
329 setState(STATE.COMPLETED);
333 return () => (mounted = false);
334 }, [confirmed, refetch, completed, setError]);
336 const values = useDeepEqualReference({
353 confirmationProgress,
363 unsignedTransactions,
367 return { ...values, bind: values };
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list