@@ -19,7 +19,12 @@ import { BRIDGE_ERROR } from '../lib/error'
1919import { Row, Col, Warning, Button, H1, H3 } from '../components/Base'
2020
2121// for transaction generation and signing
22−import { signTransaction, sendSignedTransaction } from '../lib/txn'
22+import {
23+ signTransaction,
24+ sendSignedTransaction,
25+ waitForTransactionConfirm,
26+ isTransactionConfirmed
27+} from '../lib/txn'
2328import { attemptSeedDerivation, genKey } from '../lib/keys'
2429import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
2530import * as tank from '../lib/tank'
@@ -43,10 +48,13 @@ class InviteVerify extends React.Component {
4348 point: this.props.routeData.point,
4449 ticket: this.props.routeData.ticket
4550 }
51+
52+ this.startTransactions = this.startTransactions.bind(this);
4653 }
4754
4855 componentDidMount() {
49− this.startTransactions();
56+ //NOTE delayed so that we hang *after* rendering the page
57+ setTimeout(this.startTransactions, 100);
5058 }
5159
5260 componentDidUpdate(prevProps) {
@@ -82,8 +90,8 @@ class InviteVerify extends React.Component {
8290
8391 // ping gas tank with txs if needed
8492
85− let nonce = await web3.eth.getTransactionCount(inviteAddress);
86− transferTx.nonce = nonce++;
93+ let inviteNonce = await web3.eth.getTransactionCount(inviteAddress);
94+ transferTx.nonce = inviteNonce++;
8795 transferTx.gasPrice = 20000000000; //NOTE we pay the premium for faster ux
8896 transferTx.from = inviteAddress;
8997
@@ -92,53 +100,11 @@ class InviteVerify extends React.Component {
92100 transferStx.sign(inviteWallet.privateKey);
93101 let rawTransferStx = '0x'+transferStx.serialize().toString('hex');
94102
95− let transferCost = (transferTx.gasPrice * transferTx.gas);
96− let balance = await web3.eth.getBalance(inviteAddress);
97− console.log('balance', balance, 'transfercost', transferCost, 'need funds', transferCost > balance);
98− if (transferCost > balance) {
99−
100− //TODO want to do this earlier, maybe? but we need costs for all
101− // transactions, and all balances, known before we can display useful
102− // prompts to the user.
103− const fundsRemaining = await tank.remainingTransactions(point);
104− console.log('freebies remaining', fundsRemaining);
105− if (fundsRemaining < 1) {
106− throw new Error(`TODO present user with "please fund $(inviteAddress) with $(transferCost), then click to retry"`);
107− }
108−
109− const res = await this.pingGasTank([rawTransferStx]);
110− if (!res) {
111− //TODO show pls fund msg + try again button
112− console.log('tank request failed');
113− }
114−
115− //TODO await confirm of res.txHash
116− console.log('funded transfer tx');
117− } else {
118− console.log('paying for ourselves');
119− }
103+ const transferCost = transferTx.gas * transferTx.gasPrice;
104+ await this.ensureFundsFor(web3, point, inviteAddress, transferCost, [rawTransferStx]);
120105
121106 // send transaction
122− await web3.eth.sendSignedTransaction(rawTransferStx);
123−
124− const transferTxHash = web3.utils.keccak256(rawTransferStx);
125− //TODO refactor into waitForConfirm function in /lib/txn
126− let confirmed = false;
127− let success = false;
128− while (!confirmed) {
129− let receipt = await web3.eth.getTransactionReceipt(transferTxHash);
130− confirmed = (receipt !== null);
131− if (confirmed) {
132− console.log('confirmed!', receipt);
133− success = receipt.status;
134− }
135− }
136−
137− if (!success) {
138− throw new Error('transaction failed');
139− }
140−
141− console.log('sent transfer tx');
107+ await this.sendAndAwaitConfirm(web3, [rawTransferStx]);
142108
143109 //
144110 // we're gonna be operating as the new wallet from here on out, so change
@@ -194,37 +160,14 @@ class InviteVerify extends React.Component {
194160 return '0x'+stx.serialize().toString('hex');
195161 });
196162
197− //TODO copy-pasted from above, refactor
198− balance = await web3.eth.getBalance(newAddress);
199− console.log('balance', balance, 'totalcost', totalCost, 'need funds', totalCost > balance);
200− if (totalCost > balance) {
201−
202− const fundsRemaining = await tank.remainingTransactions(point);
203− console.log('freebies remaining', fundsRemaining);
204− if (fundsRemaining < 2) {
205− throw new Error(`TODO present user with "please fund $(newAddress) with at least $(totalCost), then click to retry"`);
206− }
207−
208− const res = await this.pingGasTank(rawStxs);
209− if (!res) {
210− //TODO show pls fund msg + try again button
211− console.log('tank request failed');
212− }
163+ await this.ensureFundsFor(web3, point, newAddress, totalCost, rawStxs);
213164
214− //TODO await confirm of res.txHash
215− console.log('funded transfer tx');
216− } else {
217− console.log('paying for ourselves');
218− }
219−
220− await web3.eth.sendSignedTransaction(rawStxs[0]);
221− await web3.eth.sendSignedTransaction(rawStxs[1]);
165+ await this.sendAndAwaitConfirm(web3, rawStxs);
222166 console.log('sent transactions');
223− //TODO use PromiEvent functions to wait for confirm
224167
225168 // if non-trivial eth left in invite wallet, transfer to new ownership
226169 //TODO
227− balance = await web3.eth.getBalance(inviteAddress);
170+ let balance = await web3.eth.getBalance(inviteAddress);
228171 const gasPrice = 20000000000;
229172 const gasLimit = 21000;
230173 const sendEthCost = gasPrice * gasLimit;
@@ -236,12 +179,14 @@ class InviteVerify extends React.Component {
236179 value: value,
237180 gasPrice: gasPrice,
238181 gas: gasLimit,
239− nonce: nonce++
182+ nonce: inviteNonce++
240183 }
241184 let stx = new Tx(tx);
242185 stx.sign(inviteWallet.privateKey);
243186 const rawTx = '0x'+stx.serialize().toString('hex');
244− await web3.eth.sendSignedTransaction(rawTx);
187+ await web3.eth.sendSignedTransaction(rawTx).catch(err => {
188+ console.log('error sending value tx, who cares', err);
189+ });
245190 console.log('sent old balance');
246191 }
247192
@@ -250,22 +195,54 @@ class InviteVerify extends React.Component {
250195 //TODO forward to "all done!" screen
251196 }
252197
253− //TODO move into tank.js?
254− async pingGasTank(signedTxs) {
255− //TODO use folktale/either folktale/result more
256− const fundRes = await tank.fundTransactions(signedTxs);
198+ async ensureFundsFor(web3, point, address, cost, signedTxs) {
199+ let balance = await web3.eth.getBalance(address);
257200
258− let success = false;
201+ if (cost > balance) {
202+
203+ const fundsRemaining = await tank.remainingTransactions(point);
204+ if (fundsRemaining < signedTxs.length) {
205+ throw new Error(`TODO present user with "please fund $(address) with at least $(cost), then click to retry"`);
206+ }
207+
208+ const res = await tank.fundTransactions(signedTxs);
209+ if (!res.success) {
210+ //TODO show pls fund msg
211+ console.log('tank request failed');
212+ } else {
213+ await waitForTransactionConfirm(web3, res.txHash);
214+ let newBalance = await web3.eth.getBalance(address);
215+ console.log('funds have confirmed', balance >= cost, balance, newBalance);
216+ }
259217
260− if (fundRes.success === true) {
261− //TODO await confirmation on fundRes.txHash
262218 } else {
263− console.log(fundRes.reason);
219+ console.log('already have sufficient funds');
264220 }
221+ }
265222
266− return success;
223+ async sendAndAwaitConfirm(web3, signedTxs) {
224+ let promises = signedTxs.map(tx => {
225+ console.log('sending...');
226+ return new Promise((resolve, reject) => {
227+ web3.eth.sendSignedTransaction(tx).then(res => {
228+ //TODO figure out how to handle the doesn't-resolve-immediately case
229+ //res.transactionHash;
230+ resolve();
231+ }).catch(async err => {
232+ // if there's an error, check if it's because the transaction was
233+ // already confirmed prior to sending.
234+ // this is really only the case in local dev environments.
235+ const txHash = web3.utils.keccak256(tx);
236+ const confirmed = await isTransactionConfirmed(web3, txHash);
237+ if (confirmed) resolve();
238+ else reject(err);
239+ });
240+ });
241+ });
267242 }
268243
244+ //TODO waitForBalance(address, minBalance)
245+
269246 render() {
270247
271248 const { point } = this.state;