1import Tx from 'ethereumjs-tx';
2import { toWei, fromWei, toHex, toBN } from 'web3-utils';
3import retry from 'async-retry';
5import { NETWORK_TYPES } from './network';
6import { ledgerSignTransaction } from './ledger';
7import { trezorSignTransaction } from './trezor';
8import { metamaskSignTransaction, FakeMetamaskTransaction } from './metamask';
9import { WALLET_TYPES, addHexPrefix } from './wallet';
10import { CHECK_BLOCK_EVERY_MS } from './constants';
11import patp2dec from './patp2dec';
13const RETRY_OPTIONS = {
16 minTimeout: CHECK_BLOCK_EVERY_MS,
20const signTransaction = async ({
28 gasPrice, // string, in gwei
29 gasLimit, // string | number
31 // TODO: require these in txn object
33 chainId = toHex(chainId);
34 gasPrice = toHex(toWei(gasPrice, 'gwei'));
35 gasLimit = toHex(gasLimit);
37 const txParams = { nonce, chainId, gasPrice, gasLimit };
41 // Ledger does not seem to handle EIP-155 automatically. When using a Ledger,
42 // if the block number is at least FORK_BLKNUM = 2675000, one needs to
43 // pre-set the ECDSA signature parameters with r = 0, s = 0, and v = chainId
46 // The easiest way to handle this is to just branch on the network, since
47 // mainnet and Ropsten have obviously passed FORK_BLKNUM. This is somewhat
48 // awkward when dealing with offline transactions, since we might want to
49 // test them on a local network as well.
51 // The best thing to do is probably to add an 'advanced' tab to offline
52 // transaction generation where one can disable the defaulted-on EIP-155
53 // settings in this case. This is pretty low-priority, but is a
54 // comprehensive solution.
58 // See https://github.com/LedgerHQ/ledgerjs/issues/43#issuecomment-366984725
60 // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
62 const eip155Params = {
68 const defaultEip155Networks = [
69 NETWORK_TYPES.MAINNET,
70 NETWORK_TYPES.ROPSTEN,
71 NETWORK_TYPES.OFFLINE,
74 const needEip155Params =
75 walletType === WALLET_TYPES.LEDGER &&
76 defaultEip155Networks.includes(networkType);
78 const signingParams = needEip155Params
79 ? Object.assign(txParams, eip155Params)
82 const utx = Object.assign(txn, signingParams);
84 const stx = new Tx(utx);
86 //TODO should try-catch and display error message to user,
87 // ie ledger's "pls enable contract data"
88 // needs to maybe happen at call-site though
89 if (walletType === WALLET_TYPES.LEDGER) {
90 await ledgerSignTransaction(stx, walletHdPath);
91 } else if (walletType === WALLET_TYPES.TREZOR) {
92 await trezorSignTransaction(stx, walletHdPath);
93 } else if (walletType === WALLET_TYPES.METAMASK) {
94 return metamaskSignTransaction(stx, wallet.address);
96 stx.sign(wallet.privateKey);
102const sendSignedTransaction = (web3, stx, doubtNonceError) => {
103 // Handle fake metamask transaction
104 if (stx instanceof FakeMetamaskTransaction) {
105 const { ethereum } = window;
107 method: 'eth_sendTransaction',
108 params: [stx.txnData],
109 from: stx.txnData.from,
111 return new Promise((resolve, reject) =>
112 ethereum.sendAsync(sendTxs, (err, data) =>
113 err ? reject(err) : resolve(data.result)
117 const rawTx = hexify(stx.serialize());
119 return new Promise(async (resolve, reject) => {
121 .sendSignedTransaction(rawTx)
122 .on('transactionHash', hash => {
125 .on('error', err => {
126 // if there's a nonce error, but we used the gas tank, it's likely
127 // that it's because the tank already submitted our transaction.
128 // we just wait for first confirmation here.
130 const isKnownError = (err.message || '').includes(
131 'known transaction: '
133 const isNonceError = (err.message || '').includes(
134 "the tx doesn't have the correct nonce."
136 if (isKnownError || (doubtNonceError && isNonceError)) {
138 'tx send error likely from gas tank submission, ignoring'
140 const txHash = web3.utils.keccak256(rawTx);
143 reject(err.message || 'Transaction sending failed!');
149// returns a Promise<void>, throwing on tx failure
150const waitForTransactionConfirm = (web3, txHash) => {
151 return retry(async (bail, n) => {
152 const receipt = await web3.eth.getTransactionReceipt(txHash);
153 const confirmed = receipt !== null;
155 throw new Error('Transaction not confirmed.');
158 const success = receipt.status === true;
160 return bail(new Error('Transaction failed.'));
165// returns a Promise that resolves when all stxs have been sent & confirmed
166const sendAndAwaitAll = (web3, stxs, doubtNonceError) => {
168 stxs.map(async tx => {
169 const txHash = await sendSignedTransaction(web3, tx, doubtNonceError);
170 await waitForTransactionConfirm(web3, txHash);
175const sendTransactionsAndAwaitConfirm = async (web3, signedTxs, usedTank) =>
176 Promise.all(signedTxs.map(tx => sendSignedTransaction(web3, tx, usedTank)));
178const hexify = val => addHexPrefix(val.toString('hex'));
180const renderSignedTx = stx => ({
181 messageHash: hexify(stx.hash()),
185 rawTransaction: hexify(stx.serialize()),
188const getTxnInfo = async (web3, addr) => {
189 let nonce = await web3.eth.getTransactionCount(addr);
190 let chainId = await web3.eth.net.getId();
191 let gasPrice = await web3.eth.getGasPrice();
196 gasPrice: fromWei(toBN(gasPrice), 'gwei'),
200// TODO(shrugs): deprecate, unifiy with other callsites
201const canDecodePatp = p => {
213 sendSignedTransaction,
214 waitForTransactionConfirm,
215 sendTransactionsAndAwaitConfirm,
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