@@ -19,7 +19,12 @@ import { BRIDGE_ERROR } from '../lib/error'
19import { Row, Col, Warning, Button, H1, H3 } from '../components/Base'
19import { Row, Col, Warning, Button, H1, H3 } from '../components/Base'
21// for transaction generation and signing
21// for transaction generation and signing
22−import { signTransaction, sendSignedTransaction } from '../lib/txn'
24+ sendSignedTransaction,
25+ waitForTransactionConfirm,
26+ isTransactionConfirmed
23import { attemptSeedDerivation, genKey } from '../lib/keys'
28import { attemptSeedDerivation, genKey } from '../lib/keys'
24import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
29import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
25import * as tank from '../lib/tank'
30import * as tank from '../lib/tank'
@@ -43,10 +48,13 @@ class InviteVerify extends React.Component {
43 point: this.props.routeData.point,
48 point: this.props.routeData.point,
44 ticket: this.props.routeData.ticket
49 ticket: this.props.routeData.ticket
52+ this.startTransactions = this.startTransactions.bind(this);
48 componentDidMount() {
55 componentDidMount() {
56+ //NOTE delayed so that we hang *after* rendering the page
49− this.startTransactions();
57+ setTimeout(this.startTransactions, 100);
52 componentDidUpdate(prevProps) {
60 componentDidUpdate(prevProps) {
@@ -82,8 +90,8 @@ class InviteVerify extends React.Component {
83 // ping gas tank with txs if needed
91 // ping gas tank with txs if needed
85− let nonce = await web3.eth.getTransactionCount(inviteAddress);
93+ let inviteNonce = await web3.eth.getTransactionCount(inviteAddress);
86− transferTx.nonce = nonce++;
94+ transferTx.nonce = inviteNonce++;
87 transferTx.gasPrice = 20000000000; //NOTE we pay the premium for faster ux
95 transferTx.gasPrice = 20000000000; //NOTE we pay the premium for faster ux
88 transferTx.from = inviteAddress;
96 transferTx.from = inviteAddress;
@@ -92,53 +100,11 @@ class InviteVerify extends React.Component {
92 transferStx.sign(inviteWallet.privateKey);
100 transferStx.sign(inviteWallet.privateKey);
93 let rawTransferStx = '0x'+transferStx.serialize().toString('hex');
101 let rawTransferStx = '0x'+transferStx.serialize().toString('hex');
95− let transferCost = (transferTx.gasPrice * transferTx.gas);
103+ const transferCost = transferTx.gas * transferTx.gasPrice;
96− let balance = await web3.eth.getBalance(inviteAddress);
104+ await this.ensureFundsFor(web3, point, inviteAddress, transferCost, [rawTransferStx]);
97− console.log('balance', balance, 'transfercost', transferCost, 'need funds', transferCost > balance);
98− if (transferCost > balance) {
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"`);
109− const res = await this.pingGasTank([rawTransferStx]);
111− //TODO show pls fund msg + try again button
112− console.log('tank request failed');
115− //TODO await confirm of res.txHash
116− console.log('funded transfer tx');
118− console.log('paying for ourselves');
121 // send transaction
106 // send transaction
122− await web3.eth.sendSignedTransaction(rawTransferStx);
107+ await this.sendAndAwaitConfirm(web3, [rawTransferStx]);
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);
132− console.log('confirmed!', receipt);
133− success = receipt.status;
138− throw new Error('transaction failed');
141− console.log('sent transfer tx');
144 // we're gonna be operating as the new wallet from here on out, so change
110 // we're gonna be operating as the new wallet from here on out, so change
@@ -194,37 +160,14 @@ class InviteVerify extends React.Component {
194 return '0x'+stx.serialize().toString('hex');
160 return '0x'+stx.serialize().toString('hex');
197− //TODO copy-pasted from above, refactor
163+ await this.ensureFundsFor(web3, point, newAddress, totalCost, rawStxs);
198− balance = await web3.eth.getBalance(newAddress);
199− console.log('balance', balance, 'totalcost', totalCost, 'need funds', totalCost > balance);
200− if (totalCost > balance) {
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"`);
208− const res = await this.pingGasTank(rawStxs);
210− //TODO show pls fund msg + try again button
211− console.log('tank request failed');
214− //TODO await confirm of res.txHash
165+ await this.sendAndAwaitConfirm(web3, rawStxs);
215− console.log('funded transfer tx');
217− console.log('paying for ourselves');
220− await web3.eth.sendSignedTransaction(rawStxs[0]);
221− await web3.eth.sendSignedTransaction(rawStxs[1]);
222 console.log('sent transactions');
166 console.log('sent transactions');
223− //TODO use PromiEvent functions to wait for confirm
225 // if non-trivial eth left in invite wallet, transfer to new ownership
168 // if non-trivial eth left in invite wallet, transfer to new ownership
227− balance = await web3.eth.getBalance(inviteAddress);
170+ let balance = await web3.eth.getBalance(inviteAddress);
228 const gasPrice = 20000000000;
171 const gasPrice = 20000000000;
229 const gasLimit = 21000;
172 const gasLimit = 21000;
230 const sendEthCost = gasPrice * gasLimit;
173 const sendEthCost = gasPrice * gasLimit;
@@ -236,12 +179,14 @@ class InviteVerify extends React.Component {
236 value: value,
179 value: value,
237 gasPrice: gasPrice,
180 gasPrice: gasPrice,
238 gas: gasLimit,
181 gas: gasLimit,
239− nonce: nonce++
182+ nonce: inviteNonce++
241 let stx = new Tx(tx);
184 let stx = new Tx(tx);
242 stx.sign(inviteWallet.privateKey);
185 stx.sign(inviteWallet.privateKey);
243 const rawTx = '0x'+stx.serialize().toString('hex');
186 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);
245 console.log('sent old balance');
190 console.log('sent old balance');
@@ -250,22 +195,54 @@ class InviteVerify extends React.Component {
250 //TODO forward to "all done!" screen
195 //TODO forward to "all done!" screen
253− //TODO move into tank.js?
198+ async ensureFundsFor(web3, point, address, cost, signedTxs) {
254− async pingGasTank(signedTxs) {
199+ let balance = await web3.eth.getBalance(address);
255− //TODO use folktale/either folktale/result more
256− const fundRes = await tank.fundTransactions(signedTxs);
201+ if (cost > balance) {
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"`);
208+ const res = await tank.fundTransactions(signedTxs);
258− let success = false;
209+ if (!res.success) {
210+ //TODO show pls fund msg
211+ console.log('tank request failed');
213+ await waitForTransactionConfirm(web3, res.txHash);
214+ let newBalance = await web3.eth.getBalance(address);
215+ console.log('funds have confirmed', balance >= cost, balance, newBalance);
260− if (fundRes.success === true) {
261− //TODO await confirmation on fundRes.txHash
263− console.log(fundRes.reason);
219+ console.log('already have sufficient funds');
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;
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();
244+ //TODO waitForBalance(address, minBalance)
269 render() {
246 render() {
271 const { point } = this.state;
248 const { point } = this.state;