@@ -2,14 +2,21 @@ import * as ob from 'urbit-ob';
22import { Just } from 'folktale/maybe';
33import Tx from 'ethereumjs-tx';
44import { toWei, fromWei, toHex } from 'web3-utils';
5+import retry from 'async-retry';
56
6−import { BRIDGE_ERROR } from './error';
77import { NETWORK_TYPES } from './network';
88import { ledgerSignTransaction } from './ledger';
99import { trezorSignTransaction } from './trezor';
1010import { WALLET_TYPES, addHexPrefix } from './wallet';
1111import { CHECK_BLOCK_EVERY_MS } from './constants';
1212
13+const RETRY_OPTIONS = {
14+ retries: 99999,
15+ factor: 1,
16+ minTimeout: CHECK_BLOCK_EVERY_MS,
17+ randomize: false,
18+};
19+
1320const signTransaction = async config => {
1421 let {
1522 wallet,
@@ -24,6 +31,7 @@ const signTransaction = async config => {
2431 gasLimit,
2532 } = config;
2633
34+ //TODO require these in txn object
2735 nonce = toHex(nonce);
2836 chainId = toHex(chainId);
2937 gasPrice = toHex(toWei(gasPrice, 'gwei'));
@@ -74,47 +82,27 @@ const signTransaction = async config => {
7482 ? Object.assign(txParams, eip155Params)
7583 : txParams;
7684
77− const wal = wallet.matchWith({
78− Just: w => w.value,
79− Nothing: () => {
80− throw BRIDGE_ERROR.MISSING_WALLET;
81− },
82− });
83−
84− const sec = wal.privateKey;
85−
86− const utx = txn.matchWith({
87− Just: tx => Object.assign(tx.value, signingParams),
88− Nothing: () => {
89− throw BRIDGE_ERROR.MISSING_TXN;
90− },
91− });
85+ const utx = Object.assign(txn, signingParams);
9286
9387 const stx = new Tx(utx);
9488
89+ //TODO should try-catch and display error message to user,
90+ // ie ledger's "pls enable contract data"
91+ // needs to maybe happen at call-site though
9592 if (walletType === WALLET_TYPES.LEDGER) {
9693 await ledgerSignTransaction(stx, walletHdPath);
9794 } else if (walletType === WALLET_TYPES.TREZOR) {
9895 await trezorSignTransaction(stx, walletHdPath);
9996 } else {
100− stx.sign(sec);
97+ stx.sign(wallet.privateKey);
10198 }
10299
103100 setStx(Just(stx));
104101 return stx;
105102};
106103
107−// TODO(shrugs): refactor the hell out of all of these functions
108−// but especially this one
109−const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
110− const txn = stx.matchWith({
111− Just: tx => tx.value,
112− Nothing: () => {
113− throw BRIDGE_ERROR.MISSING_TXN;
114− },
115− });
116−
117− const rawTx = hexify(txn.serialize());
104+const sendSignedTransaction = (web3, stx, doubtNonceError) => {
105+ const rawTx = hexify(stx.serialize());
118106
119107 return new Promise(async (resolve, reject) => {
120108 web3.eth
@@ -122,39 +110,25 @@ const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
122110 .on('transactionHash', hash => {
123111 resolve(hash);
124112 })
125− //TODO do we also reach this if network is slow? web3 only tries a set
126− // amount of times... should we instead do waitForTransactionConfirm
127− // in on-transactionHash? we don't care (much) about additional
128− // confirms anyway.
129− .on('confirmation', (confirmationNum, txn) => {
130− if (confirmationCb) {
131− confirmationCb(txn.transactionHash, confirmationNum + 1);
132− }
133− resolve(txn.transactionHash);
134− })
135113 .on('error', err => {
136114 // if there's a nonce error, but we used the gas tank, it's likely
137115 // that it's because the tank already submitted our transaction.
138116 // we just wait for first confirmation here.
139117 console.error(err);
140− if (
141− doubtNonceError &&
142− (err.message || '').includes("the tx doesn't have the correct nonce.")
143− ) {
144− console.log('nonce error, likely from gas tank submission, ignoring');
118+ const isKnownError = (err.message || '').includes(
119+ 'known transaction: '
120+ );
121+ const isNonceError = (err.message || '').includes(
122+ "the tx doesn't have the correct nonce."
123+ );
124+ if (isKnownError || (doubtNonceError && isNonceError)) {
125+ console.log(
126+ 'tx send error likely from gas tank submission, ignoring'
127+ );
145128 const txHash = web3.utils.keccak256(rawTx);
146− //TODO can we do does-exists check first?
147− //TODO max wait time before assume fail?
148− waitForTransactionConfirm(web3, txHash).then(res => {
149− if (res) {
150− resolve(txHash);
151− confirmationCb(txHash, 1);
152− } else {
153− reject('Unexpected tx failure');
154− }
155− });
129+ resolve(txHash);
156130 } else {
157− reject(err.message);
131+ reject(err.message || 'Transaction sending failed!');
158132 }
159133 });
160134 });
@@ -162,23 +136,22 @@ const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
162136
163137// returns a Promise<bool>, where the bool indicates tx success/failure
164138const waitForTransactionConfirm = (web3, txHash) => {
165− return new Promise((resolve, reject) => {
166− const checkForConfirm = async () => {
167− console.log('checking for confirm', txHash);
168− const receipt = await web3.eth.getTransactionReceipt(txHash);
169− console.log('tried, got', receipt);
170− let confirmed = receipt !== null;
171− if (confirmed) resolve(receipt.status === true);
172− else setTimeout(checkForConfirm, CHECK_BLOCK_EVERY_MS);
173− };
174− checkForConfirm();
175− });
139+ return retry(async (bail, n) => {
140+ const receipt = await web3.eth.getTransactionReceipt(txHash);
141+ const confirmed = receipt !== null;
142+ if (confirmed) return receipt.status === true;
143+ else throw new Error('retrying');
144+ }, RETRY_OPTIONS);
176145};
177146
178−const isTransactionConfirmed = async (web3, txHash) => {
179− const receipt = await web3.eth.getTransactionReceipt(txHash);
180− console.log('got confirm state', receipt !== null, receipt.confirmations);
181− return receipt !== null;
147+// returns a Promise that resolves when all stxs have been sent & confirmed
148+const sendAndAwaitAll = (web3, stxs, doubtNonceError) => {
149+ return Promise.all(
150+ stxs.map(async tx => {
151+ const txHash = await sendSignedTransaction(web3, tx, doubtNonceError);
152+ await waitForTransactionConfirm(web3, txHash);
153+ })
154+ );
182155};
183156
184157const sendTransactionsAndAwaitConfirm = async (web3, signedTxs, usedTank) =>
@@ -217,11 +190,12 @@ const canDecodePatp = p => {
217190};
218191
219192export {
193+ RETRY_OPTIONS,
220194 signTransaction,
221195 sendSignedTransaction,
222196 waitForTransactionConfirm,
223197 sendTransactionsAndAwaitConfirm,
224− isTransactionConfirmed,
198+ sendAndAwaitAll,
225199 getTxnInfo,
226200 hexify,
227201 renderSignedTx,