@@ -1,4 +1,5 @@
11import { Nothing, Just } from 'folktale/maybe'
2+import { Ok } from 'folktale/result'
23import React from 'react'
34
45import { Code, H3 } from './Base'
@@ -10,14 +11,24 @@ import { Warning } from '../components/Base'
1011import { addressFromSecp256k1Public } from '../lib/wallet'
1112import { BRIDGE_ERROR } from '../lib/error'
1213import { ROUTE_NAMES } from '../lib/router'
14+import * as tank from '../lib/tank'
1315
1416import {
1517 sendSignedTransaction,
18+ waitForTransactionConfirm,
1619 fromWei,
20+ toWei,
21+ hexify,
1722 renderSignedTx,
1823 signTransaction
1924 } from '../lib/txn'
2025
26+const SUBMISSION_STATES = {
27+ PROMPT: 'Send transaction',
28+ FUNDING: 'Finding transaction funding...',
29+ SENDING: 'Sending transaction...'
30+};
31+
2132class StatelessTransaction extends React.Component {
2233
2334 constructor(props) {
@@ -33,11 +44,11 @@ class StatelessTransaction extends React.Component {
3344 nonce: '0',
3445 stx: Nothing(),
3546 txn: Nothing(),
47+ txStatus: SUBMISSION_STATES.PROMPT,
3648 txError: Nothing()
3749 }
3850
3951 this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
40− this.submit = this.submit.bind(this)
4152 this.setUserApproval = this.setUserApproval.bind(this)
4253 this.setTxn = this.setTxn.bind(this)
4354 this.setStx = this.setStx.bind(this)
@@ -86,32 +97,6 @@ class StatelessTransaction extends React.Component {
8697 });
8798 }
8899
89− submit(){
90− const { props, state } = this
91− sendSignedTransaction(props.web3.value, state.stx, props.setTxnConfirmations)
92− .then(sent => {
93− props.setTxnHashCursor(sent)
94− props.popRoute()
95−
96− let routeData = {};
97− if (props.networkSeed) {
98− props.setNetworkSeedCache(props.networkSeed);
99− routeData.promptKeyfile = true;
100− }
101− if (props.promptText) {
102− routeData.promptText = props.promptText;
103− }
104− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION, routeData);
105− })
106− .catch(err => {
107− if (err.map) {
108− this.setState({ txError: err.map(val => val.merge()) })
109− } else {
110− this.setState({ txError: err })
111− }
112− })
113− }
114−
115100 handleChainUpdate(chainId) {
116101 if (chainId === "custom") {
117102 this.setState({
@@ -194,16 +179,95 @@ class StatelessTransaction extends React.Component {
194179 })
195180 }
196181
197− // TODO: Investigate
198− // setState doesn't seem to work in SetProxy/submit;
199− // - TypeError: Cannot read property 'updater' of undefined
200− // so just modifying the DOM manually here. (https://imgur.com/a/i0Qsyq1)
182+ async sendTxn() {
183+ const { props, state } = this;
184+ const web3 = props.web3.value;
185+
186+ this.setState({ txStatus: SUBMISSION_STATES.FUNDING });
187+
188+ const rawTx = hexify(state.stx.value.serialize());
189+ const cost = (state.gasLimit * toWei(state.gasPrice, 'gwei'));
201190
202− sendTxn(e) {
203− e.target.setAttribute("disabled", true);
204− let spinner = e.target.querySelectorAll('.btn-spinner')[0];
205− spinner.classList.remove('hide');
206− this.submit()
191+ //TODO need a lib function or something for address=, it's everywhere
192+ const address = props.wallet.matchWith({
193+ Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
194+ Nothing: () => {
195+ throw BRIDGE_ERROR.MISSING_WALLET
196+ }
197+ });
198+ let balance = await web3.eth.getBalance(address);
199+ let hasBalance = (balance >= cost);
200+
201+ let usedTank = false;
202+ // if we need to, try and fund the transaction
203+ if (!hasBalance) {
204+ hasBalance = await this.ensureFundsFor(web3, address, cost, [rawTx]);
205+ usedTank = hasBalance;
206+ }
207+
208+ // if we still don't have sufficient balance, fail and tell the user
209+ if (!hasBalance) {
210+ this.setState({
211+ txStatus: SUBMISSION_STATES.PROMPT,
212+ txError: Just(`Insufficient funds.
213+ Address ${address} needs at least ${fromWei(cost.toString())} ETH,
214+ currently has ${fromWei(balance.toString())} ETH.`
215+ )
216+ });
217+
218+ // if we have the balance, proceed with submission
219+ } else {
220+ this.setState({ txStatus: SUBMISSION_STATES.SENDING });
221+
222+ sendSignedTransaction(
223+ web3,
224+ state.stx,
225+ usedTank,
226+ props.setTxnConfirmations
227+ )
228+ .then(txHash => {
229+ props.setTxnHashCursor(Just(Ok(txHash)));
230+ props.popRoute();
231+
232+ let routeData = {};
233+ if (props.networkSeed) {
234+ props.setNetworkSeedCache(props.networkSeed);
235+ routeData.promptKeyfile = true;
236+ }
237+ if (props.promptText) {
238+ routeData.promptText = props.promptText;
239+ }
240+ props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION, routeData);
241+ })
242+ .catch(err => {
243+ this.setState({
244+ txStatus: SUBMISSION_STATES.PROMPT,
245+ txError: err
246+ });
247+ });
248+ }
249+ }
250+
251+ //TODO partially copied from InviteTransactions, try to move into tank lib
252+ async ensureFundsFor(web3, address, cost, signedTxs) {
253+ let balance = await web3.eth.getBalance(address);
254+ if (cost > balance) {
255+ try {
256+
257+ const res = await tank.fundTransactions(signedTxs);
258+ if (!res.success) {
259+ return false;
260+ } else {
261+ await waitForTransactionConfirm(web3, res.txHash);
262+ return true;
263+ }
264+
265+ } catch (e) {
266+ return false;
267+ }
268+ } else {
269+ return true;
270+ }
207271 }
208272
209273 getChainTitle(chainId) {
@@ -249,7 +313,7 @@ class StatelessTransaction extends React.Component {
249313 const { web3, canGenerate } = this.props
250314 const { gasPrice, gasLimit, nonce, chainId,
251315 txn, stx, userApproval, showGasDetails,
252− customChain } = this.state
316+ customChain, txStatus } = this.state
253317
254318 const { setNonce, setChainId, setGasLimit, setGasPrice, toggleGasDetails,
255319 setUserApproval, sendTxn, createUnsignedTxn, handleChainUpdate } = this
@@ -439,16 +503,18 @@ class StatelessTransaction extends React.Component {
439503 </div>
440504 </CheckboxButton>
441505
506+ const sending = (txStatus !== SUBMISSION_STATES.PROMPT);
507+ const sendTxnSpinner = sending ? '' : 'hide';
442508 const sendTxnButton =
443509 <Button
444510 prop-size={ 'xl wide' }
445511 className={ 'mt-8' }
446− disabled={ !canSend }
512+ disabled={ !canSend || sending }
447513 onClick={ sendTxn }
448514 >
449515 <span className="relative">
450− <span className="btn-spinner hide"></span>
451− { 'Send Transaction' }
516+ <span className={`btn-spinner ${sendTxnSpinner}`}></span>
517+ { txStatus }
452518 </span>
453519 </Button>
454520