@@ -2,14 +2,21 @@ import * as ob from 'urbit-ob';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3import Tx from 'ethereumjs-tx';
3import Tx from 'ethereumjs-tx';
4import { toWei, fromWei, toHex } from 'web3-utils';
4import { toWei, fromWei, toHex } from 'web3-utils';
5+import retry from 'async-retry';
6−import { BRIDGE_ERROR } from './error';
7import { NETWORK_TYPES } from './network';
7import { NETWORK_TYPES } from './network';
8import { ledgerSignTransaction } from './ledger';
8import { ledgerSignTransaction } from './ledger';
9import { trezorSignTransaction } from './trezor';
9import { trezorSignTransaction } from './trezor';
10import { WALLET_TYPES, addHexPrefix } from './wallet';
10import { WALLET_TYPES, addHexPrefix } from './wallet';
11import { CHECK_BLOCK_EVERY_MS } from './constants';
11import { CHECK_BLOCK_EVERY_MS } from './constants';
13+const RETRY_OPTIONS = {
16+ minTimeout: CHECK_BLOCK_EVERY_MS,
13const signTransaction = async config => {
20const signTransaction = async config => {
@@ -24,6 +31,7 @@ const signTransaction = async config => {
25 } = config;
32 } = config;
34+ //TODO require these in txn object
27 nonce = toHex(nonce);
35 nonce = toHex(nonce);
28 chainId = toHex(chainId);
36 chainId = toHex(chainId);
29 gasPrice = toHex(toWei(gasPrice, 'gwei'));
37 gasPrice = toHex(toWei(gasPrice, 'gwei'));
@@ -74,47 +82,27 @@ const signTransaction = async config => {
74 ? Object.assign(txParams, eip155Params)
82 ? Object.assign(txParams, eip155Params)
75 : txParams;
83 : txParams;
77− const wal = wallet.matchWith({
80− throw BRIDGE_ERROR.MISSING_WALLET;
84− const sec = wal.privateKey;
86− const utx = txn.matchWith({
87− Just: tx => Object.assign(tx.value, signingParams),
85+ const utx = Object.assign(txn, signingParams);
89− throw BRIDGE_ERROR.MISSING_TXN;
93 const stx = new Tx(utx);
87 const stx = new Tx(utx);
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
95 if (walletType === WALLET_TYPES.LEDGER) {
92 if (walletType === WALLET_TYPES.LEDGER) {
96 await ledgerSignTransaction(stx, walletHdPath);
93 await ledgerSignTransaction(stx, walletHdPath);
97 } else if (walletType === WALLET_TYPES.TREZOR) {
94 } else if (walletType === WALLET_TYPES.TREZOR) {
98 await trezorSignTransaction(stx, walletHdPath);
95 await trezorSignTransaction(stx, walletHdPath);
100− stx.sign(sec);
97+ stx.sign(wallet.privateKey);
103 setStx(Just(stx));
100 setStx(Just(stx));
104 return stx;
101 return stx;
107−// TODO(shrugs): refactor the hell out of all of these functions
108−// but especially this one
109−const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
104+const sendSignedTransaction = (web3, stx, doubtNonceError) => {
110− const txn = stx.matchWith({
111− Just: tx => tx.value,
113− throw BRIDGE_ERROR.MISSING_TXN;
117− const rawTx = hexify(txn.serialize());
105+ const rawTx = hexify(stx.serialize());
119 return new Promise(async (resolve, reject) => {
107 return new Promise(async (resolve, reject) => {
@@ -122,39 +110,25 @@ const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
122 .on('transactionHash', hash => {
110 .on('transactionHash', hash => {
123 resolve(hash);
111 resolve(hash);
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
129− .on('confirmation', (confirmationNum, txn) => {
130− if (confirmationCb) {
131− confirmationCb(txn.transactionHash, confirmationNum + 1);
133− resolve(txn.transactionHash);
135 .on('error', err => {
113 .on('error', err => {
136 // if there's a nonce error, but we used the gas tank, it's likely
114 // if there's a nonce error, but we used the gas tank, it's likely
137 // that it's because the tank already submitted our transaction.
115 // that it's because the tank already submitted our transaction.
138 // we just wait for first confirmation here.
116 // we just wait for first confirmation here.
139 console.error(err);
117 console.error(err);
140− if (
118+ const isKnownError = (err.message || '').includes(
141− doubtNonceError &&
119+ 'known transaction: '
121+ const isNonceError = (err.message || '').includes(
142− (err.message || '').includes("the tx doesn't have the correct nonce.")
122+ "the tx doesn't have the correct nonce."
124+ if (isKnownError || (doubtNonceError && isNonceError)) {
144− console.log('nonce error, likely from gas tank submission, ignoring');
126+ 'tx send error likely from gas tank submission, ignoring'
145 const txHash = web3.utils.keccak256(rawTx);
128 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 => {
150− resolve(txHash);
129+ resolve(txHash);
151− confirmationCb(txHash, 1);
153− reject('Unexpected tx failure');
157− reject(err.message);
131+ reject(err.message || 'Transaction sending failed!');
@@ -162,23 +136,22 @@ const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
163// returns a Promise<bool>, where the bool indicates tx success/failure
137// returns a Promise<bool>, where the bool indicates tx success/failure
164const waitForTransactionConfirm = (web3, txHash) => {
138const waitForTransactionConfirm = (web3, txHash) => {
165− return new Promise((resolve, reject) => {
139+ return retry(async (bail, n) => {
166− const checkForConfirm = async () => {
167− console.log('checking for confirm', txHash);
168− const receipt = await web3.eth.getTransactionReceipt(txHash);
140+ const receipt = await web3.eth.getTransactionReceipt(txHash);
169− console.log('tried, got', receipt);
170− let confirmed = receipt !== null;
141+ const confirmed = receipt !== null;
171− if (confirmed) resolve(receipt.status === true);
142+ if (confirmed) return receipt.status === true;
172− else setTimeout(checkForConfirm, CHECK_BLOCK_EVERY_MS);
143+ else throw new Error('retrying');
173− };
144+ }, RETRY_OPTIONS);
178−const isTransactionConfirmed = async (web3, txHash) => {
147+// returns a Promise that resolves when all stxs have been sent & confirmed
179− const receipt = await web3.eth.getTransactionReceipt(txHash);
148+const sendAndAwaitAll = (web3, stxs, doubtNonceError) => {
180− console.log('got confirm state', receipt !== null, receipt.confirmations);
149+ return Promise.all(
181− return receipt !== null;
150+ stxs.map(async tx => {
151+ const txHash = await sendSignedTransaction(web3, tx, doubtNonceError);
152+ await waitForTransactionConfirm(web3, txHash);
184const sendTransactionsAndAwaitConfirm = async (web3, signedTxs, usedTank) =>
157const sendTransactionsAndAwaitConfirm = async (web3, signedTxs, usedTank) =>
@@ -217,11 +190,12 @@ const canDecodePatp = p => {
220 signTransaction,
194 signTransaction,
221 sendSignedTransaction,
195 sendSignedTransaction,
222 waitForTransactionConfirm,
196 waitForTransactionConfirm,
223 sendTransactionsAndAwaitConfirm,
197 sendTransactionsAndAwaitConfirm,
224− isTransactionConfirmed,
198+ sendAndAwaitAll,
225 getTxnInfo,
199 getTxnInfo,
227 renderSignedTx,
201 renderSignedTx,