@@ -2,8 +2,8 @@ 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 * as retry from 'async-retry';
56
6−import { BRIDGE_ERROR } from './error';
77import { NETWORK_TYPES } from './network';
88import { ledgerSignTransaction } from './ledger';
99import { trezorSignTransaction } from './trezor';
@@ -12,6 +12,13 @@ import { WALLET_TYPES, addHexPrefix } from './wallet';
1212const CHECK_BLOCK_EVERY_MS =
1313 process.env.NODE_ENV === 'development' ? 1000 : 5000;
1414
15+const RETRY_OPTIONS = {
16+ retries: 99999,
17+ factor: 1,
18+ minTimeout: CHECK_BLOCK_EVERY_MS,
19+ randomize: false,
20+};
21+
1522const TXN_PURPOSE = {
1623 SET_MANAGEMENT_PROXY: Symbol('SET_MANAGEMENT_PROXY'),
1724 SET_TRANSFER_PROXY: Symbol('SET_TRANSFER_PROXY'),
@@ -56,6 +63,7 @@ const signTransaction = async config => {
5663 gasLimit,
5764 } = config;
5865
66+ //TODO require these in txn object
5967 nonce = toHex(nonce);
6068 chainId = toHex(chainId);
6169 gasPrice = toHex(toWei(gasPrice, 'gwei'));
@@ -106,45 +114,27 @@ const signTransaction = async config => {
106114 ? Object.assign(txParams, eip155Params)
107115 : txParams;
108116
109− const wal = wallet.matchWith({
110− Just: w => w.value,
111− Nothing: () => {
112− throw BRIDGE_ERROR.MISSING_WALLET;
113− },
114− });
115−
116− const sec = wal.privateKey;
117−
118− const utx = txn.matchWith({
119− Just: tx => Object.assign(tx.value, signingParams),
120− Nothing: () => {
121− throw BRIDGE_ERROR.MISSING_TXN;
122− },
123− });
117+ const utx = Object.assign(txn, signingParams);
124118
125119 const stx = new Tx(utx);
126120
121+ //TODO should try-catch and display error message to user,
122+ // ie ledger's "pls enable contract data"
123+ // needs to maybe happen at call-site though
127124 if (walletType === WALLET_TYPES.LEDGER) {
128125 await ledgerSignTransaction(stx, walletHdPath);
129126 } else if (walletType === WALLET_TYPES.TREZOR) {
130127 await trezorSignTransaction(stx, walletHdPath);
131128 } else {
132− stx.sign(sec);
129+ stx.sign(wallet.privateKey);
133130 }
134131
135132 setStx(Just(stx));
136133 return stx;
137134};
138135
139−const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
140− const txn = stx.matchWith({
141− Just: tx => tx.value,
142− Nothing: () => {
143− throw BRIDGE_ERROR.MISSING_TXN;
144− },
145− });
146−
147− const rawTx = hexify(txn.serialize());
136+const sendSignedTransaction = (web3, stx, doubtNonceError) => {
137+ const rawTx = hexify(stx.serialize());
148138
149139 return new Promise(async (resolve, reject) => {
150140 web3.eth
@@ -152,14 +142,6 @@ const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
152142 .on('transactionHash', hash => {
153143 resolve(hash);
154144 })
155− //TODO do we also reach this if network is slow? web3 only tries a set
156− // amount of times... should we instead do waitForTransactionConfirm
157− // in on-transactionHash? we don't care (much) about additional
158− // confirms anyway.
159− .on('confirmation', (confirmationNum, txn) => {
160− confirmationCb(txn.transactionHash, confirmationNum + 1);
161− resolve(txn.transactionHash);
162− })
163145 .on('error', err => {
164146 // if there's a nonce error, but we used the gas tank, it's likely
165147 // that it's because the tank already submitted our transaction.
@@ -176,16 +158,7 @@ const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
176158 'tx send error likely from gas tank submission, ignoring'
177159 );
178160 const txHash = web3.utils.keccak256(rawTx);
179− //TODO can we do does-exists check first?
180− //TODO max wait time before assume fail?
181− waitForTransactionConfirm(web3, txHash).then(res => {
182− if (res) {
183− resolve(txHash);
184− confirmationCb(txHash, 1);
185− } else {
186− reject('Unexpected tx failure');
187− }
188− });
161+ resolve(txHash);
189162 } else {
190163 reject(err.message || 'Transaction sending failed!');
191164 }
@@ -195,23 +168,22 @@ const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
195168
196169// returns a Promise<bool>, where the bool indicates tx success/failure
197170const waitForTransactionConfirm = (web3, txHash) => {
198− return new Promise((resolve, reject) => {
199− const checkForConfirm = async () => {
200− console.log('checking for confirm', txHash);
201− const receipt = await web3.eth.getTransactionReceipt(txHash);
202− console.log('tried, got', receipt);
203− let confirmed = receipt !== null;
204− if (confirmed) resolve(receipt.status === true);
205− else setTimeout(checkForConfirm, CHECK_BLOCK_EVERY_MS);
206− };
207− checkForConfirm();
208− });
171+ return retry(async (bail, n) => {
172+ const receipt = await web3.eth.getTransactionReceipt(txHash);
173+ const confirmed = receipt !== null;
174+ if (confirmed) return receipt.status === true;
175+ else throw new Error('retrying');
176+ }, RETRY_OPTIONS);
209177};
210178
211−const isTransactionConfirmed = async (web3, txHash) => {
212− const receipt = await web3.eth.getTransactionReceipt(txHash);
213− console.log('got confirm state', receipt !== null, receipt.confirmations);
214− return receipt !== null;
179+// returns a Promise that resolves when all stxs have been sent & confirmed
180+const sendAndAwaitAll = (web3, stxs, doubtNonceError) => {
181+ return Promise.all(
182+ stxs.map(async tx => {
183+ const txHash = await sendSignedTransaction(web3, tx, doubtNonceError);
184+ await waitForTransactionConfirm(web3, txHash);
185+ })
186+ );
215187};
216188
217189const hexify = val => addHexPrefix(val.toString('hex'));
@@ -245,10 +217,11 @@ const canDecodePatp = p => {
245217};
246218
247219export {
220+ RETRY_OPTIONS,
248221 signTransaction,
249222 sendSignedTransaction,
250223 waitForTransactionConfirm,
251− isTransactionConfirmed,
224+ sendAndAwaitAll,
252225 TXN_PURPOSE,
253226 getTxnInfo,
254227 renderTxnPurpose,