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,
16 sendSignedTransaction,
17 waitForTransactionConfirm,
20import * as need from 'lib/need';
21import { ensureFundsFor } from 'lib/tank';
22import useDeepEqualReference from 'lib/useDeepEqualReference';
23import useGasPrice from 'lib/useGasPrice';
24import timeout from 'lib/timeout';
25import { safeToWei, safeFromWei } from 'lib/lib';
30 BROADCASTED: 'BROADCASTED',
31 CONFIRMED: 'CONFIRMED',
32 COMPLETED: 'COMPLETED',
36 * manage the state around sending and confirming an ethereum transaction
37 * @param {Transaction Function()} transactionBuilder
38 * @param {Promise<any> Function()} refetch async function called after completion
39 * @param {number} initialGasLimit gas limit
40 * @param {number} initialGasPrice gas price in gwei
42export default function useEthereumTransaction(
45 initialGasLimit = GAS_LIMITS.DEFAULT,
46 initialGasPrice = DEFAULT_GAS_PRICE_GWEI
48 const { wallet, walletType, walletHdPath } = useWallet();
49 const { web3, networkType } = useNetwork();
50 const { pointCursor } = usePointCursor();
52 const _web3 = need.web3(web3);
53 const _wallet = need.wallet(wallet);
55 const [error, _setError] = useState();
56 const setError = useCallback(
65 const [state, setState] = useState(STATE.NONE);
66 const [chainId, setChainId] = useState();
67 const [nonce, setNonce] = useState();
68 const { gasPrice, setGasPrice, resetGasPrice } = useGasPrice(initialGasPrice);
69 const [gasLimit] = useState(initialGasLimit);
70 const [unsignedTransactions, setUnsignedTransactions] = useState();
71 const [signedTransactions, setSignedTransactions] = useState();
72 const [txHashes, setTxHashes] = useState();
73 const [needFunds, setNeedFunds] = useState();
74 const [confirmationProgress, setConfirmationProgress] = useState(0.0);
76 const [finalCost, setFinalCost] = useState();
78 const initializing = nonce === undefined || chainId === undefined;
79 const constructed = !!unsignedTransactions;
80 const isDefaultState = state === STATE.NONE;
81 const signed = state === STATE.SIGNED;
82 const broadcasted = state === STATE.BROADCASTED;
83 const confirmed = state === STATE.CONFIRMED;
84 const completed = state === STATE.COMPLETED;
86 // lock inputs once we're out of the default state
87 const inputsLocked = !isDefaultState;
90 const canSign = !initializing && constructed && isDefaultState;
92 const construct = useCallback(
94 setUnsignedTransactions(await transactionBuilder(...args)),
98 const unconstruct = useCallback(() => setUnsignedTransactions(undefined), [
99 setUnsignedTransactions,
102 const generateAndSign = useCallback(async () => {
106 const utxs = Array.isArray(unsignedTransactions)
107 ? unsignedTransactions
108 : [unsignedTransactions];
110 const txns = await Promise.all(
120 gasPrice: gasPrice.toFixed(0),
126 setSignedTransactions(txns);
127 setState(STATE.SIGNED);
139 unsignedTransactions,
144 const broadcast = useCallback(async () => {
146 setConfirmationProgress(0.0);
149 const rawTxs = signedTransactions.map(stx => hexify(stx.serialize()));
151 const costGwei = toBN(gasLimit)
153 .mul(toBN(rawTxs.length));
154 const cost = safeToWei(costGwei, 'gwei');
155 let usedTank = false;
156 // if this ethereum transaction is being executed by a specific point
157 // see if we can use the tank
158 if (Just.hasInstance(pointCursor)) {
159 usedTank = await ensureFundsFor(
166 (address, minBalance, balance) =>
167 setNeedFunds({ address, minBalance, balance }),
168 () => setNeedFunds(undefined)
172 const txHashes = await signedTransactions.reduce(
175 sendSignedTransaction(
178 /* doubtNonceError */ usedTank
179 ).then(hash => [...hashes, hash])
184 setTxHashes(txHashes);
185 setState(STATE.BROADCASTED);
187 await timeout(PROGRESS_ANIMATION_DELAY_MS);
189 setConfirmationProgress(0.2);
191 const receipts = await Promise.all(
192 txHashes.map(txHash => waitForTransactionConfirm(_web3, txHash))
200 toBN(receipt.gasUsed)
211 setConfirmationProgress(0.9);
213 setState(STATE.CONFIRMED);
228 const reset = useCallback(() => {
229 setTxHashes(undefined);
230 setSignedTransactions(undefined);
232 setChainId(undefined);
234 setState(STATE.NONE);
236 setNeedFunds(undefined);
237 setConfirmationProgress(0.0);
238 setFinalCost(undefined);
239 }, [resetGasPrice, setError]);
243 // if nonce or chainId is undefined, re-fetch on-chain info
244 if (!(nonce === undefined || chainId === undefined)) {
251 const [nonce, chainId] = await Promise.all([
252 _web3.eth.getTransactionCount(_wallet.address),
253 _web3.eth.net.getId(),
267 return () => (mounted = false);
268 }, [_wallet, _web3, setError, nonce, chainId, networkType, initialGasPrice]);
280 console.error(error);
281 // track a user-friendly error
284 'The transaction succeeded but we were unable to refresh chain state. Refresh to continue.'
294 setConfirmationProgress(1.0);
296 await timeout(PROGRESS_ANIMATION_DELAY_MS);
302 setState(STATE.COMPLETED);
306 return () => (mounted = false);
307 }, [confirmed, refetch, completed, setError]);
309 const values = useDeepEqualReference({
325 confirmationProgress,
335 unsignedTransactions,
339 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