@@ -1,4 +1,5 @@
1import { Nothing, Just } from 'folktale/maybe'
1import { Nothing, Just } from 'folktale/maybe'
2+import { Ok } from 'folktale/result'
2import React from 'react'
3import React from 'react'
4import { Code, H3 } from './Base'
5import { Code, H3 } from './Base'
@@ -10,14 +11,24 @@ import { Warning } from '../components/Base'
10import { addressFromSecp256k1Public } from '../lib/wallet'
11import { addressFromSecp256k1Public } from '../lib/wallet'
11import { BRIDGE_ERROR } from '../lib/error'
12import { BRIDGE_ERROR } from '../lib/error'
12import { ROUTE_NAMES } from '../lib/router'
13import { ROUTE_NAMES } from '../lib/router'
14+import * as tank from '../lib/tank'
15 sendSignedTransaction,
17 sendSignedTransaction,
18+ waitForTransactionConfirm,
17 renderSignedTx,
22 renderSignedTx,
18 signTransaction
23 signTransaction
19 } from '../lib/txn'
24 } from '../lib/txn'
26+const SUBMISSION_STATES = {
27+ PROMPT: 'Send transaction',
28+ FUNDING: 'Finding transaction funding...',
29+ SENDING: 'Sending transaction...'
21class StatelessTransaction extends React.Component {
32class StatelessTransaction extends React.Component {
23 constructor(props) {
34 constructor(props) {
@@ -33,11 +44,11 @@ class StatelessTransaction extends React.Component {
33 nonce: '0',
44 nonce: '0',
34 stx: Nothing(),
45 stx: Nothing(),
35 txn: Nothing(),
46 txn: Nothing(),
47+ txStatus: SUBMISSION_STATES.PROMPT,
36 txError: Nothing()
48 txError: Nothing()
39 this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
51 this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
40− this.submit = this.submit.bind(this)
41 this.setUserApproval = this.setUserApproval.bind(this)
52 this.setUserApproval = this.setUserApproval.bind(this)
42 this.setTxn = this.setTxn.bind(this)
53 this.setTxn = this.setTxn.bind(this)
43 this.setStx = this.setStx.bind(this)
54 this.setStx = this.setStx.bind(this)
@@ -86,32 +97,6 @@ class StatelessTransaction extends React.Component {
90− const { props, state } = this
91− sendSignedTransaction(props.web3.value, state.stx, props.setTxnConfirmations)
93− props.setTxnHashCursor(sent)
97− if (props.networkSeed) {
98− props.setNetworkSeedCache(props.networkSeed);
99− routeData.promptKeyfile = true;
101− if (props.promptText) {
102− routeData.promptText = props.promptText;
104− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION, routeData);
108− this.setState({ txError: err.map(val => val.merge()) })
110− this.setState({ txError: err })
115 handleChainUpdate(chainId) {
100 handleChainUpdate(chainId) {
116 if (chainId === "custom") {
101 if (chainId === "custom") {
117 this.setState({
102 this.setState({
@@ -194,16 +179,95 @@ class StatelessTransaction extends React.Component {
197− // TODO: Investigate
182+ async sendTxn() {
198− // setState doesn't seem to work in SetProxy/submit;
183+ const { props, state } = this;
199− // - TypeError: Cannot read property 'updater' of undefined
184+ const web3 = props.web3.value;
200− // so just modifying the DOM manually here. (https://imgur.com/a/i0Qsyq1)
185+
186+ this.setState({ txStatus: SUBMISSION_STATES.FUNDING });
188+ const rawTx = hexify(state.stx.value.serialize());
189+ const cost = (state.gasLimit * toWei(state.gasPrice, 'gwei'));
202− sendTxn(e) {
191+ //TODO need a lib function or something for address=, it's everywhere
203− e.target.setAttribute("disabled", true);
192+ const address = props.wallet.matchWith({
204− let spinner = e.target.querySelectorAll('.btn-spinner')[0];
193+ Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
205− spinner.classList.remove('hide');
194+ Nothing: () => {
206− this.submit()
195+ throw BRIDGE_ERROR.MISSING_WALLET
198+ let balance = await web3.eth.getBalance(address);
199+ let hasBalance = (balance >= cost);
201+ let usedTank = false;
202+ // if we need to, try and fund the transaction
204+ hasBalance = await this.ensureFundsFor(web3, address, cost, [rawTx]);
205+ usedTank = hasBalance;
208+ // if we still don't have sufficient balance, fail and tell the user
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.`
218+ // if we have the balance, proceed with submission
220+ this.setState({ txStatus: SUBMISSION_STATES.SENDING });
222+ sendSignedTransaction(
226+ props.setTxnConfirmations
229+ props.setTxnHashCursor(Just(Ok(txHash)));
233+ if (props.networkSeed) {
234+ props.setNetworkSeedCache(props.networkSeed);
235+ routeData.promptKeyfile = true;
237+ if (props.promptText) {
238+ routeData.promptText = props.promptText;
240+ props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION, routeData);
244+ txStatus: SUBMISSION_STATES.PROMPT,
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) {
257+ const res = await tank.fundTransactions(signedTxs);
261+ await waitForTransactionConfirm(web3, res.txHash);
209 getChainTitle(chainId) {
273 getChainTitle(chainId) {
@@ -249,7 +313,7 @@ class StatelessTransaction extends React.Component {
249 const { web3, canGenerate } = this.props
313 const { web3, canGenerate } = this.props
250 const { gasPrice, gasLimit, nonce, chainId,
314 const { gasPrice, gasLimit, nonce, chainId,
251 txn, stx, userApproval, showGasDetails,
315 txn, stx, userApproval, showGasDetails,
252− customChain } = this.state
316+ customChain, txStatus } = this.state
254 const { setNonce, setChainId, setGasLimit, setGasPrice, toggleGasDetails,
318 const { setNonce, setChainId, setGasLimit, setGasPrice, toggleGasDetails,
255 setUserApproval, sendTxn, createUnsignedTxn, handleChainUpdate } = this
319 setUserApproval, sendTxn, createUnsignedTxn, handleChainUpdate } = this
@@ -439,16 +503,18 @@ class StatelessTransaction extends React.Component {
440 </CheckboxButton>
504 </CheckboxButton>
506+ const sending = (txStatus !== SUBMISSION_STATES.PROMPT);
507+ const sendTxnSpinner = sending ? '' : 'hide';
442 const sendTxnButton =
508 const sendTxnButton =
444 prop-size={ 'xl wide' }
510 prop-size={ 'xl wide' }
445 className={ 'mt-8' }
511 className={ 'mt-8' }
446− disabled={ !canSend }
512+ disabled={ !canSend || sending }
447 onClick={ sendTxn }
513 onClick={ sendTxn }
449 <span className="relative">
515 <span className="relative">
450− <span className="btn-spinner hide"></span>
516+ <span className={`btn-spinner ${sendTxnSpinner}`}></span>
451− { 'Send Transaction' }
517+ { txStatus }
453 </Button>
519 </Button>