From 58ac6426a090b61dc32da4535bf785287b74e7b7 Mon Sep 17 00:00:00 2001 From: pkova Date: Mon, 8 Apr 2019 22:48:06 +0300 Subject: [PATCH 1/7] Add transaction status indicator to SentTransaction --- src/bridge/Bridge.js | 15 +++++++-- src/bridge/components/StatelessTransaction.js | 2 +- src/bridge/lib/txn.js | 6 +++- src/bridge/views/AcceptTransfer.js | 1 + src/bridge/views/CancelTransfer.js | 1 + src/bridge/views/CreateGalaxy.js | 1 + src/bridge/views/IssueChild.js | 1 + src/bridge/views/SentTransaction.js | 32 +++++++++++++++---- src/bridge/views/SetKeys.js | 1 + src/bridge/views/SetProxy.js | 1 + src/bridge/views/Transfer.js | 1 + 11 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/bridge/Bridge.js b/src/bridge/Bridge.js index efcc8b32c..a962d227c 100644 --- a/src/bridge/Bridge.js +++ b/src/bridge/Bridge.js @@ -80,7 +80,8 @@ class Bridge extends React.Component { pointCursor: Maybe.Nothing(), pointCache: {}, // txn - txnHashCursor: Maybe.Nothing() + txnHashCursor: Maybe.Nothing(), + txnConfirmations: {} } this.pushRoute = this.pushRoute.bind(this) @@ -95,6 +96,7 @@ class Bridge extends React.Component { this.setAuthMnemonic = this.setAuthMnemonic.bind(this) this.setPointCursor = this.setPointCursor.bind(this) this.setTxnHashCursor = this.setTxnHashCursor.bind(this) + this.setTxnConfirmations = this.setTxnConfirmations.bind(this) this.setNetworkSeedCache = this.setNetworkSeedCache.bind(this) this.addToPointCache = this.addToPointCache.bind(this) } @@ -234,6 +236,12 @@ class Bridge extends React.Component { this.setState({ txnHashCursor }) } + setTxnConfirmations(txnHash, txnConfirmations) { + this.setState((prevState, _) => + ({txnConfirmations: { ...prevState.txnConfirmations, + [txnHash]: txnConfirmations}})) + } + render() { const { routeCrumbs, @@ -248,6 +256,7 @@ class Bridge extends React.Component { pointCursor, pointCache, txnHashCursor, + txnConfirmations, web3, contracts } = this.state @@ -300,7 +309,9 @@ class Bridge extends React.Component { setNetworkSeedCache= { this.setNetworkSeedCache } // txn setTxnHashCursor={ this.setTxnHashCursor } - txnHashCursor={ txnHashCursor } /> + txnHashCursor={ txnHashCursor } + setTxnConfirmations={ this.setTxnConfirmations } + txnConfirmations={ txnConfirmations } />
diff --git a/src/bridge/components/StatelessTransaction.js b/src/bridge/components/StatelessTransaction.js index 527d3c946..0e1541d27 100644 --- a/src/bridge/components/StatelessTransaction.js +++ b/src/bridge/components/StatelessTransaction.js @@ -85,7 +85,7 @@ class StatelessTransaction extends React.Component { submit(){ const { props, state } = this - sendSignedTransaction(props.web3.value, state.stx) + sendSignedTransaction(props.web3.value, state.stx, props.setTxnConfirmations) .then(sent => { props.setTxnHashCursor(sent) props.popRoute() diff --git a/src/bridge/lib/txn.js b/src/bridge/lib/txn.js index 48f3b34d9..1a9cc7da2 100644 --- a/src/bridge/lib/txn.js +++ b/src/bridge/lib/txn.js @@ -137,7 +137,7 @@ const signTransaction = async config => { setStx(Just(stx)) } -const sendSignedTransaction = (web3, stx) => { +const sendSignedTransaction = (web3, stx, confirmationCb) => { const txn = stx.matchWith({ Just: (tx) => tx.value, Nothing: () => { @@ -155,6 +155,10 @@ const sendSignedTransaction = (web3, stx) => { .on('receipt', txn => { resolve(Just(Ok(txn.transactionHash))) }) + .on('confirmation', (confirmationNum, txn) => { + confirmationCb(txn.transactionHash, confirmationNum + 1) + resolve(Just(Ok(txn.transactionHash))) + }) .on('error', err => { reject(Just(Error(err.message))) }) diff --git a/src/bridge/views/AcceptTransfer.js b/src/bridge/views/AcceptTransfer.js index 2e4d423d4..2095397e9 100644 --- a/src/bridge/views/AcceptTransfer.js +++ b/src/bridge/views/AcceptTransfer.js @@ -126,6 +126,7 @@ class AcceptTransfer extends React.Component { walletHdPath={props.walletHdPath} networkType={props.networkType} setTxnHashCursor={props.setTxnHashCursor} + setTxnConfirmations={props.setTxnConfirmations} popRoute={props.popRoute} pushRoute={props.pushRoute} // Other diff --git a/src/bridge/views/CancelTransfer.js b/src/bridge/views/CancelTransfer.js index e005499be..acacd9866 100644 --- a/src/bridge/views/CancelTransfer.js +++ b/src/bridge/views/CancelTransfer.js @@ -87,6 +87,7 @@ class CancelTransfer extends React.Component { walletHdPath={props.walletHdPath} networkType={props.networkType} setTxnHashCursor={props.setTxnHashCursor} + setTxnConfirmations={props.setTxnConfirmations} popRoute={props.popRoute} pushRoute={props.pushRoute} // Other diff --git a/src/bridge/views/CreateGalaxy.js b/src/bridge/views/CreateGalaxy.js index ebc96f613..28d734cd3 100644 --- a/src/bridge/views/CreateGalaxy.js +++ b/src/bridge/views/CreateGalaxy.js @@ -212,6 +212,7 @@ class CreateGalaxy extends React.Component { walletHdPath={props.walletHdPath} networkType={props.networkType} setTxnHashCursor={props.setTxnHashCursor} + setTxnConfirmations={props.setTxnConfirmations} popRoute={props.popRoute} pushRoute={props.pushRoute} // Other diff --git a/src/bridge/views/IssueChild.js b/src/bridge/views/IssueChild.js index b94e5b529..8a5ef3332 100644 --- a/src/bridge/views/IssueChild.js +++ b/src/bridge/views/IssueChild.js @@ -303,6 +303,7 @@ class IssueChild extends React.Component { walletHdPath={props.walletHdPath} networkType={props.networkType} setTxnHashCursor={props.setTxnHashCursor} + setTxnConfirmations={props.setTxnConfirmations} popRoute={props.popRoute} pushRoute={props.pushRoute} // Other diff --git a/src/bridge/views/SentTransaction.js b/src/bridge/views/SentTransaction.js index 4a82b334d..4c6de0701 100644 --- a/src/bridge/views/SentTransaction.js +++ b/src/bridge/views/SentTransaction.js @@ -1,3 +1,4 @@ +import Maybe from 'folktale/maybe' import React from 'react' import { Row, Col, H1, H3, P, Warning, Anchor } from '../components/Base' import { Button } from '../components/Base' @@ -6,14 +7,17 @@ import { ROUTE_NAMES } from '../lib/router' import { BRIDGE_ERROR, renderTxnError } from '../lib/error' import { NETWORK_NAMES } from '../lib/network' + const Success = (props) => { + const { networkType, hash, txnConfirmations } = props + const esvisible = - props.networkType === NETWORK_NAMES.ROPSTEN || - props.networkType === NETWORK_NAMES.MAINNET + networkType === NETWORK_NAMES.ROPSTEN || + networkType === NETWORK_NAMES.MAINNET const esdomain = - props.networkType === NETWORK_NAMES.ROPSTEN + networkType === NETWORK_NAMES.ROPSTEN ? "ropsten.etherscan.io" : "etherscan.io" @@ -29,10 +33,16 @@ const Success = (props) => { className={'mb-4 mt-1'} prop-size={'sm'} target={'_blank'} - href={`https://${esdomain}/tx/${props.hash}`}> + href={`https://${esdomain}/tx/${hash}`}> {'View on Etherscan ↗'} + const confirmations = Maybe.fromNullable(txnConfirmations[hash]).getOrElse(0) + + const requiredConfirmations = 1 + + const status = confirmations < requiredConfirmations ? 'Pending...' : `Confirmed x${confirmations}!` + return ( @@ -47,10 +57,17 @@ const Success = (props) => {

{ 'Transaction Hash' }

- { props.hash } + { hash } +

+ +

{ 'Transaction Status' }

+

+

{ status }
+
{ esanchor }
+

+

- { esanchor }
@@ -76,7 +93,7 @@ const Failure = (props) => const SentTransaction = (props) => { - const { web3, txnHashCursor, networkType, popRoute, pushRoute } = props + const { web3, txnHashCursor, networkType, popRoute, pushRoute, txnConfirmations} = props const { setPointCursor, pointCursor } = props const promptKeyfile = props.routeData && props.routeData.promptKeyfile @@ -97,6 +114,7 @@ const SentTransaction = (props) => { }) diff --git a/src/bridge/views/SetKeys.js b/src/bridge/views/SetKeys.js index 8efe7263d..bea67392f 100644 --- a/src/bridge/views/SetKeys.js +++ b/src/bridge/views/SetKeys.js @@ -194,6 +194,7 @@ class SetKeys extends React.Component { walletHdPath={props.walletHdPath} networkType={props.networkType} setTxnHashCursor={props.setTxnHashCursor} + setTxnConfirmations={props.setTxnConfirmations} popRoute={props.popRoute} pushRoute={props.pushRoute} // Tx diff --git a/src/bridge/views/SetProxy.js b/src/bridge/views/SetProxy.js index 3da633645..37c9ca694 100644 --- a/src/bridge/views/SetProxy.js +++ b/src/bridge/views/SetProxy.js @@ -162,6 +162,7 @@ class SetProxy extends React.Component { walletHdPath={props.walletHdPath} networkType={props.networkType} setTxnHashCursor={props.setTxnHashCursor} + setTxnConfirmations={props.setTxnConfirmations} popRoute={props.popRoute} pushRoute={props.pushRoute} // Other diff --git a/src/bridge/views/Transfer.js b/src/bridge/views/Transfer.js index 03ed0136c..66cb2f3af 100644 --- a/src/bridge/views/Transfer.js +++ b/src/bridge/views/Transfer.js @@ -132,6 +132,7 @@ class Transfer extends React.Component { walletHdPath={props.walletHdPath} networkType={props.networkType} setTxnHashCursor={props.setTxnHashCursor} + setTxnConfirmations={props.setTxnConfirmations} popRoute={props.popRoute} pushRoute={props.pushRoute} // Checks From 31aa34911b1a5f57824535a8d294cba8b5ca6c2b Mon Sep 17 00:00:00 2001 From: pkova Date: Tue, 16 Apr 2019 20:28:56 +0300 Subject: [PATCH 2/7] Animate pending dots --- src/bridge/views/SentTransaction.js | 128 +++++++++++++++++----------- 1 file changed, 79 insertions(+), 49 deletions(-) diff --git a/src/bridge/views/SentTransaction.js b/src/bridge/views/SentTransaction.js index 4c6de0701..be8a4c8ff 100644 --- a/src/bridge/views/SentTransaction.js +++ b/src/bridge/views/SentTransaction.js @@ -8,70 +8,100 @@ import { BRIDGE_ERROR, renderTxnError } from '../lib/error' import { NETWORK_NAMES } from '../lib/network' -const Success = (props) => { +class Success extends React.Component { - const { networkType, hash, txnConfirmations } = props + constructor(props) { + super(props) - const esvisible = - networkType === NETWORK_NAMES.ROPSTEN || - networkType === NETWORK_NAMES.MAINNET + this.state = { + pending: '.', + interval: null + } + } - const esdomain = - networkType === NETWORK_NAMES.ROPSTEN - ? "ropsten.etherscan.io" - : "etherscan.io" + componentDidMount() { + const nextDot = {'.': '..', + '..': '...', + '...': '.'} - const esmessage = - esvisible === true - ? "If you’d like to keep track of it, click the Etherscan link below." - : '' + const interval = setInterval(() => { + this.setState(({ pending }) => ({pending: nextDot[pending]})) + }, 1000) + this.setState({interval: interval}) + } - const esanchor = - esvisible === false - ?
- : - {'View on Etherscan ↗'} - + componentDidUnmount() { + clearInterval(this.state.interval) + } - const confirmations = Maybe.fromNullable(txnConfirmations[hash]).getOrElse(0) + render() { - const requiredConfirmations = 1 + const { props, state } = this + const { networkType, hash, txnConfirmations } = props + const { pending } = state - const status = confirmations < requiredConfirmations ? 'Pending...' : `Confirmed x${confirmations}!` + const esvisible = + networkType === NETWORK_NAMES.ROPSTEN || + networkType === NETWORK_NAMES.MAINNET - return ( - - -

{ 'Your Transaction was Sent' }

+ const esdomain = + networkType === NETWORK_NAMES.ROPSTEN + ? "ropsten.etherscan.io" + : "etherscan.io" -

- { - `We sent your transaction to the chain. It can take some time to + const esmessage = + esvisible === true + ? "If you’d like to keep track of it, click the Etherscan link below." + : '' + + const esanchor = + esvisible === false + ?

+ : + {'View on Etherscan ↗'} + + + const confirmations = Maybe.fromNullable(txnConfirmations[hash]).getOrElse(0) + + const requiredConfirmations = 1 + + const status = confirmations < requiredConfirmations ? + `Pending${pending}` : `Confirmed! (x${confirmations} confirmations)!` + + return ( + + +

{ 'Your Transaction was Sent' }

+ +

+ { + `We sent your transaction to the chain. It can take some time to execute, especially if the network is busy. ${esmessage}` - } -

+ } +

-

{ 'Transaction Hash' }

-

- { hash } -

+

{ 'Transaction Hash' }

+

+ { hash } +

-

{ 'Transaction Status' }

-

-

{ status }
-
{ esanchor }
-

-

-

+

{ 'Transaction Status' }

+

+

{ status }
+
{ esanchor }
+

+

+

- -
- ) + + + ) + } } const Failure = (props) => From 765deb1d40945c2f61b5b80411a3bb7977bfb683 Mon Sep 17 00:00:00 2001 From: pkova Date: Tue, 16 Apr 2019 20:31:16 +0300 Subject: [PATCH 3/7] Dont put divs inside p --- src/bridge/views/SentTransaction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bridge/views/SentTransaction.js b/src/bridge/views/SentTransaction.js index be8a4c8ff..4abe577fb 100644 --- a/src/bridge/views/SentTransaction.js +++ b/src/bridge/views/SentTransaction.js @@ -91,8 +91,8 @@ class Success extends React.Component {

{ 'Transaction Status' }

-

{ status }
-
{ esanchor }
+ { status } + { esanchor }

From bf869de152a12c933e29ab1039b7eae857867a34 Mon Sep 17 00:00:00 2001 From: pkova Date: Tue, 16 Apr 2019 21:13:37 +0300 Subject: [PATCH 4/7] Give status an esanchor their own paragraphs --- src/bridge/views/SentTransaction.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bridge/views/SentTransaction.js b/src/bridge/views/SentTransaction.js index 4abe577fb..5f5e2daf6 100644 --- a/src/bridge/views/SentTransaction.js +++ b/src/bridge/views/SentTransaction.js @@ -92,6 +92,8 @@ class Success extends React.Component {

{ 'Transaction Status' }

{ status } +

+

{ esanchor }

From e2a43bbcf53f64ce48b6c47a4f342eb5ac3dbbe6 Mon Sep 17 00:00:00 2001 From: pkova Date: Tue, 16 Apr 2019 21:35:55 +0300 Subject: [PATCH 5/7] Fix bug in interval clearing on component unmount --- src/bridge/views/SentTransaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/views/SentTransaction.js b/src/bridge/views/SentTransaction.js index 5f5e2daf6..03d4fc985 100644 --- a/src/bridge/views/SentTransaction.js +++ b/src/bridge/views/SentTransaction.js @@ -30,7 +30,7 @@ class Success extends React.Component { this.setState({interval: interval}) } - componentDidUnmount() { + componentWillUnmount() { clearInterval(this.state.interval) } From d67f269f813cff95acb1f03acba0a704bc52cb9e Mon Sep 17 00:00:00 2001 From: pkova Date: Tue, 16 Apr 2019 21:36:29 +0300 Subject: [PATCH 6/7] Remove dangling p element --- src/bridge/views/SentTransaction.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/bridge/views/SentTransaction.js b/src/bridge/views/SentTransaction.js index 03d4fc985..ac7dc54ab 100644 --- a/src/bridge/views/SentTransaction.js +++ b/src/bridge/views/SentTransaction.js @@ -96,9 +96,6 @@ class Success extends React.Component {

{ esanchor }

-

-

- From 8de9bc3d0bc97ddbe123e84412a090202dbd1ec9 Mon Sep 17 00:00:00 2001 From: pkova Date: Tue, 16 Apr 2019 21:36:41 +0300 Subject: [PATCH 7/7] Return nothing instead of empty div when esvisible == false React complains about having divs inside of p elements otherwise --- src/bridge/views/SentTransaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/views/SentTransaction.js b/src/bridge/views/SentTransaction.js index ac7dc54ab..ccd9405d3 100644 --- a/src/bridge/views/SentTransaction.js +++ b/src/bridge/views/SentTransaction.js @@ -56,7 +56,7 @@ class Success extends React.Component { const esanchor = esvisible === false - ?
+ ? null :