diff --git a/src/components/Web3Status/index.js b/src/components/Web3Status/index.js index 9f1d71cf08..22bf691703 100644 --- a/src/components/Web3Status/index.js +++ b/src/components/Web3Status/index.js @@ -6,7 +6,6 @@ import Web3 from 'web3'; import Jazzicon from 'jazzicon'; import { CSSTransitionGroup } from "react-transition-group"; import './web3-status.scss'; -import Pending from '../../assets/images/pending.svg'; import Modal from '../Modal'; function getEtherscanLink(tx) { @@ -19,13 +18,13 @@ class Web3Status extends Component { }; handleClick = () => { - if (this.props.hasPendingTransactions && !this.state.isShowingModal) { + if (this.props.pending.length && !this.state.isShowingModal) { this.setState({isShowingModal: true}); } - } + }; renderPendingTransactions() { - return this.props.pendingTransactions.map((transaction) => { + return this.props.pending.map((transaction) => { return (
- - Pending +
Pending
); @@ -71,25 +69,21 @@ class Web3Status extends Component { } render() { - const { address, transactions, pendingTransactions, hasPendingTransactions } = this.props; - let text = getText(address); - if (hasPendingTransactions) { - text = getPendingText(pendingTransactions); - } + const { address, pending, confirmed } = this.props; + const hasPendingTransactions = !!pending.length; + const hasConfirmedTransactions = !!confirmed.length; return (
- { - hasPendingTransactions ? - getPendingText(pendingTransactions) : - getText(address) - } + { hasPendingTransactions ? getPendingText(pending) : getText(address) }
- +
{pendingTransactions.length} Pending
); @@ -144,18 +138,11 @@ Web3Status.defaultProps = { export default connect( state => { - const pendingTransactions = []; - // Object.keys(state.transactions).forEach((transaction) => { - // if (state.transactions[transaction] && state.transactions[transaction].status === 'pending') { - // pendingTransactions.push(transaction); - // } - // }); - return { address: state.web3connect.account, isConnected: !!(state.web3connect.web3 && state.web3connect.account), - pendingTransactions, - hasPendingTransactions: pendingTransactions.length > 0, + pending: state.web3connect.transactions.pending, + confirmed: state.web3connect.transactions.confirmed, }; } )(Web3Status); diff --git a/src/components/Web3Status/web3-status.scss b/src/components/Web3Status/web3-status.scss index cb00b642f4..769f43bd5a 100644 --- a/src/components/Web3Status/web3-status.scss +++ b/src/components/Web3Status/web3-status.scss @@ -31,6 +31,12 @@ display: flex; justify-content: space-around; } + + &--pending { + background-color: $zumthor-blue; + color: $royal-blue; + border: 1px solid $royal-blue; + } } @@ -70,13 +76,16 @@ } &__pending-indicator { + @extend %row-nowrap; color: $royal-blue; - border: 1px solid $zumthor-blue; - padding: 10px; + border: 1px solid $royal-blue; + background-color: $zumthor-blue; + padding: .5rem .75rem;; border-radius: 100px; + font-size: .75rem; - & > img { - margin-right: 10px; + & > .loading { + margin-right: .5rem; } } diff --git a/src/ducks/web3connect.js b/src/ducks/web3connect.js index cc476f0cf0..3750d74a76 100644 --- a/src/ducks/web3connect.js +++ b/src/ducks/web3connect.js @@ -16,6 +16,9 @@ export const WATCH_APPROVALS = 'web3connect/watchApprovals'; export const UPDATE_APPROVALS = 'web3connect/updateApprovals'; export const ADD_CONTRACT = 'web3connect/addContract'; export const UPDATE_NETWORK_ID = 'web3connect/updateNetworkId'; +export const ADD_PENDING_TX = 'web3connect/addPendingTx'; +export const REMOVE_PENDING_TX = 'web3connect/removePendingTx'; +export const ADD_CONFIRMED_TX = 'web3connect/addConfirmedTx'; const initialState = { web3: null, @@ -32,8 +35,10 @@ const initialState = { }, }, }, - pendingTransactions: [], - transactions: {}, + transactions: { + pending: [], + confirmed: [], + }, watched: { balances: { ethereum: [], @@ -43,11 +48,6 @@ const initialState = { contracts: {}, }; -const TOKEN_LABEL_FALLBACK = { - '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359': 'DAI', - '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2': 'MKR', -}; - // selectors export const selectors = () => (dispatch, getState) => { const state = getState().web3connect; @@ -197,6 +197,11 @@ export const watchApprovals = ({ tokenAddress, tokenOwner, spender }) => (dispat }); }; +export const addPendingTx = txId => ({ + type: ADD_PENDING_TX, + payload: txId, +}); + export const updateApprovals = ({ tokenAddress, tokenOwner, spender, balance }) => ({ type: UPDATE_APPROVALS, payload: { @@ -215,6 +220,7 @@ export const sync = () => async (dispatch, getState) => { watched, contracts, networkId, + transactions: { pending, confirmed }, } = getState().web3connect; // Sync Account @@ -275,14 +281,12 @@ export const sync = () => async (dispatch, getState) => { const balance = await contract.methods.balanceOf(address).call(); const decimals = tokenBalance.decimals || await contract.methods.decimals().call(); let symbol = tokenBalance.symbol; - try { symbol = symbol || await contract.methods.symbol().call().catch(); } catch (e) { try { symbol = symbol || web3.utils.hexToString(await contractBytes32.methods.symbol().call().catch()); } catch (err) { - } } @@ -301,32 +305,56 @@ export const sync = () => async (dispatch, getState) => { }); }); - // Update Approvals - Object.entries(watched.approvals) - .forEach(([tokenAddress, token]) => { - const contract = contracts[tokenAddress] || new web3.eth.Contract(ERC20_ABI, tokenAddress); - Object.entries(token) - .forEach(([ tokenOwnerAddress, tokenOwner ]) => { - tokenOwner.forEach(async spenderAddress => { - const approvalBalance = getApprovals(tokenAddress, tokenOwnerAddress, spenderAddress); - const balance = await contract.methods.allowance(tokenOwnerAddress, spenderAddress).call(); - const decimals = approvalBalance.decimals || await contract.methods.decimals().call(); - const symbol = TOKEN_LABEL_FALLBACK[tokenAddress] || approvalBalance.label || await contract.methods.symbol().call(); + // Update Approvals + Object.entries(watched.approvals) + .forEach(([tokenAddress, token]) => { + const contract = contracts[tokenAddress] || new web3.eth.Contract(ERC20_ABI, tokenAddress); + const contractBytes32 = contracts[tokenAddress] || new web3.eth.Contract(ERC20_WITH_BYTES_ABI, tokenAddress); - if (approvalBalance.label && approvalBalance.value.isEqualTo(BN(balance))) { - return; + Object.entries(token) + .forEach(([ tokenOwnerAddress, tokenOwner ]) => { + tokenOwner.forEach(async spenderAddress => { + const approvalBalance = getApprovals(tokenAddress, tokenOwnerAddress, spenderAddress); + const balance = await contract.methods.allowance(tokenOwnerAddress, spenderAddress).call(); + const decimals = approvalBalance.decimals || await contract.methods.decimals().call(); + let symbol = approvalBalance.label; + try { + symbol = symbol || await contract.methods.symbol().call(); + } catch (e) { + try { + symbol = symbol || web3.utils.hexToString(await contractBytes32.methods.symbol().call()); + } catch (err) { } + } - dispatch(updateApprovals({ - tokenAddress, - tokenOwner: tokenOwnerAddress, - spender: spenderAddress, - balance: Balance(balance, symbol, decimals), - })); - }); + if (approvalBalance.label && approvalBalance.value.isEqualTo(BN(balance))) { + return; + } + + dispatch(updateApprovals({ + tokenAddress, + tokenOwner: tokenOwnerAddress, + spender: spenderAddress, + balance: Balance(balance, symbol, decimals), + })); }); + }); + }); + + pending.forEach(async txId => { + const data = await web3.eth.getTransactionReceipt(txId) || {}; + if (data.status) { + dispatch({ + type: REMOVE_PENDING_TX, + payload: txId, }); + dispatch({ + type: ADD_CONFIRMED_TX, + payload: txId, + }); + } + }); }; export const startWatching = () => async (dispatch, getState) => { @@ -445,6 +473,34 @@ export default function web3connectReducer(state = initialState, { type, payload }; case UPDATE_NETWORK_ID: return { ...state, networkId: payload }; + case ADD_PENDING_TX: + return { + ...state, + transactions: { + ...state.transactions, + pending: [ ...state.transactions.pending, payload ], + }, + }; + case REMOVE_PENDING_TX: + return { + ...state, + transactions: { + ...state.transactions, + pending: state.transactions.pending.filter(id => id !== payload), + }, + }; + case ADD_CONFIRMED_TX: + if (state.transactions.confirmed.includes(payload)) { + return state; + } + + return { + ...state, + transactions: { + ...state.transactions, + confirmed: [ ...state.transactions.confirmed, payload ], + }, + }; default: return state; } diff --git a/src/index.scss b/src/index.scss index b2d439d2d8..f5225842c3 100644 --- a/src/index.scss +++ b/src/index.scss @@ -35,3 +35,18 @@ html, body { right: 0; z-index: 200; } + +.loader { + border: 1px solid transparent; /* Light grey */ + border-top: 1px solid $royal-blue; /* Blue */ + border-radius: 50%; + width: .75rem; + height: .75rem; + margin-right: .25rem; + animation: spin 1s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/src/pages/Swap/index.js b/src/pages/Swap/index.js index ea5b8d1739..6b4821763a 100644 --- a/src/pages/Swap/index.js +++ b/src/pages/Swap/index.js @@ -5,7 +5,7 @@ import classnames from 'classnames'; import {BigNumber as BN} from "bignumber.js"; import MediaQuery from 'react-responsive'; import ReactGA from 'react-ga'; -import { selectors } from '../../ducks/web3connect'; +import { selectors, addPendingTx } from '../../ducks/web3connect'; import { CSSTransitionGroup } from "react-transition-group"; import Header from '../../components/Header'; import NavigationTabs from '../../components/NavigationTabs'; @@ -28,6 +28,7 @@ class Swap extends Component { account: PropTypes.string, isConnected: PropTypes.bool.isRequired, selectors: PropTypes.func.isRequired, + addPendingTx: PropTypes.func.isRequired, web3: PropTypes.object.isRequired, }; @@ -355,6 +356,7 @@ class Swap extends Component { account, web3, selectors, + addPendingTx, } = this.props; const { inputValue, @@ -392,7 +394,12 @@ class Swap extends Component { .send({ from: account, value: BN(inputValue).multipliedBy(10 ** 18).toFixed(0), - }, err => !err && this.reset()); + }, (err, data) => { + if (!err) { + addPendingTx(data); + this.reset(); + } + }); break; case 'TOKEN_TO_ETH': new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency]) @@ -402,9 +409,12 @@ class Swap extends Component { BN(outputValue).multipliedBy(10 ** outputDecimals).multipliedBy(1 - ALLOWED_SLIPPAGE).toFixed(0), deadline, ) - .send({ - from: account, - }, err => !err && this.reset()); + .send({ from: account }, (err, data) => { + if (!err) { + addPendingTx(data); + this.reset(); + } + }); break; case 'TOKEN_TO_TOKEN': new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency]) @@ -416,9 +426,12 @@ class Swap extends Component { deadline, outputCurrency, ) - .send({ - from: account, - }, err => !err && this.reset()); + .send({ from: account }, (err, data) => { + if (!err) { + addPendingTx(data); + this.reset(); + } + }); break; default: break; @@ -442,7 +455,12 @@ class Swap extends Component { .send({ from: account, value: BN(inputValue).multipliedBy(10 ** inputDecimals).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(0), - }, err => !err && this.reset()); + }, (err, data) => { + if (!err) { + addPendingTx(data); + this.reset(); + } + }); break; case 'TOKEN_TO_ETH': new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency]) @@ -452,9 +470,12 @@ class Swap extends Component { BN(inputValue).multipliedBy(10 ** inputDecimals).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(0), deadline, ) - .send({ - from: account, - }, err => !err && this.reset()); + .send({ from: account }, (err, data) => { + if (!err) { + addPendingTx(data); + this.reset(); + } + }); break; case 'TOKEN_TO_TOKEN': if (!inputAmountB) { @@ -469,9 +490,13 @@ class Swap extends Component { inputAmountB.multipliedBy(1.2).toFixed(0), deadline, outputCurrency, - ).send({ - from: account, - }, err => !err && this.reset()); + ) + .send({ from: account }, (err, data) => { + if (!err) { + addPendingTx(data); + this.reset(); + } + }); break; default: break; @@ -770,6 +795,7 @@ export default connect( }), dispatch => ({ selectors: () => dispatch(selectors()), + addPendingTx: id => dispatch(addPendingTx(id)), }), )(Swap);