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 (
- {
- 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);