Commit 0a5509e9 authored by Chi Kei Chan's avatar Chi Kei Chan

Add Pending Tx for Swap

parent 95008950
...@@ -6,7 +6,6 @@ import Web3 from 'web3'; ...@@ -6,7 +6,6 @@ import Web3 from 'web3';
import Jazzicon from 'jazzicon'; import Jazzicon from 'jazzicon';
import { CSSTransitionGroup } from "react-transition-group"; import { CSSTransitionGroup } from "react-transition-group";
import './web3-status.scss'; import './web3-status.scss';
import Pending from '../../assets/images/pending.svg';
import Modal from '../Modal'; import Modal from '../Modal';
function getEtherscanLink(tx) { function getEtherscanLink(tx) {
...@@ -19,13 +18,13 @@ class Web3Status extends Component { ...@@ -19,13 +18,13 @@ class Web3Status extends Component {
}; };
handleClick = () => { handleClick = () => {
if (this.props.hasPendingTransactions && !this.state.isShowingModal) { if (this.props.pending.length && !this.state.isShowingModal) {
this.setState({isShowingModal: true}); this.setState({isShowingModal: true});
} }
} };
renderPendingTransactions() { renderPendingTransactions() {
return this.props.pendingTransactions.map((transaction) => { return this.props.pending.map((transaction) => {
return ( return (
<div <div
key={transaction} key={transaction}
...@@ -36,8 +35,7 @@ class Web3Status extends Component { ...@@ -36,8 +35,7 @@ class Web3Status extends Component {
{transaction} {transaction}
</div> </div>
<div className="pending-modal__pending-indicator"> <div className="pending-modal__pending-indicator">
<img src={Pending} /> <div className="loader" /> Pending
Pending
</div> </div>
</div> </div>
); );
...@@ -71,25 +69,21 @@ class Web3Status extends Component { ...@@ -71,25 +69,21 @@ class Web3Status extends Component {
} }
render() { render() {
const { address, transactions, pendingTransactions, hasPendingTransactions } = this.props; const { address, pending, confirmed } = this.props;
let text = getText(address); const hasPendingTransactions = !!pending.length;
if (hasPendingTransactions) { const hasConfirmedTransactions = !!confirmed.length;
text = getPendingText(pendingTransactions);
}
return ( return (
<div <div
className={classnames("web3-status", { className={classnames("web3-status", {
'web3-status__connected': this.props.isConnected, 'web3-status__connected': this.props.isConnected,
'web3-status--pending': hasPendingTransactions,
'web3-status--confirmed': hasConfirmedTransactions,
})} })}
onClick={this.handleClick} onClick={this.handleClick}
> >
<div className="web3-status__text"> <div className="web3-status__text">
{ { hasPendingTransactions ? getPendingText(pending) : getText(address) }
hasPendingTransactions ?
getPendingText(pendingTransactions) :
getText(address)
}
</div> </div>
<div <div
className="web3-status__identicon" className="web3-status__identicon"
...@@ -117,7 +111,7 @@ class Web3Status extends Component { ...@@ -117,7 +111,7 @@ class Web3Status extends Component {
function getPendingText(pendingTransactions) { function getPendingText(pendingTransactions) {
return ( return (
<div className="web3-status__pending-container"> <div className="web3-status__pending-container">
<img key="icon" src={Pending} /> <div className="loader" />
<span key="text">{pendingTransactions.length} Pending</span> <span key="text">{pendingTransactions.length} Pending</span>
</div> </div>
); );
...@@ -144,18 +138,11 @@ Web3Status.defaultProps = { ...@@ -144,18 +138,11 @@ Web3Status.defaultProps = {
export default connect( export default connect(
state => { state => {
const pendingTransactions = [];
// Object.keys(state.transactions).forEach((transaction) => {
// if (state.transactions[transaction] && state.transactions[transaction].status === 'pending') {
// pendingTransactions.push(transaction);
// }
// });
return { return {
address: state.web3connect.account, address: state.web3connect.account,
isConnected: !!(state.web3connect.web3 && state.web3connect.account), isConnected: !!(state.web3connect.web3 && state.web3connect.account),
pendingTransactions, pending: state.web3connect.transactions.pending,
hasPendingTransactions: pendingTransactions.length > 0, confirmed: state.web3connect.transactions.confirmed,
}; };
} }
)(Web3Status); )(Web3Status);
...@@ -31,6 +31,12 @@ ...@@ -31,6 +31,12 @@
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
} }
&--pending {
background-color: $zumthor-blue;
color: $royal-blue;
border: 1px solid $royal-blue;
}
} }
...@@ -70,13 +76,16 @@ ...@@ -70,13 +76,16 @@
} }
&__pending-indicator { &__pending-indicator {
@extend %row-nowrap;
color: $royal-blue; color: $royal-blue;
border: 1px solid $zumthor-blue; border: 1px solid $royal-blue;
padding: 10px; background-color: $zumthor-blue;
padding: .5rem .75rem;;
border-radius: 100px; border-radius: 100px;
font-size: .75rem;
& > img { & > .loading {
margin-right: 10px; margin-right: .5rem;
} }
} }
......
...@@ -16,6 +16,9 @@ export const WATCH_APPROVALS = 'web3connect/watchApprovals'; ...@@ -16,6 +16,9 @@ export const WATCH_APPROVALS = 'web3connect/watchApprovals';
export const UPDATE_APPROVALS = 'web3connect/updateApprovals'; export const UPDATE_APPROVALS = 'web3connect/updateApprovals';
export const ADD_CONTRACT = 'web3connect/addContract'; export const ADD_CONTRACT = 'web3connect/addContract';
export const UPDATE_NETWORK_ID = 'web3connect/updateNetworkId'; 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 = { const initialState = {
web3: null, web3: null,
...@@ -32,8 +35,10 @@ const initialState = { ...@@ -32,8 +35,10 @@ const initialState = {
}, },
}, },
}, },
pendingTransactions: [], transactions: {
transactions: {}, pending: [],
confirmed: [],
},
watched: { watched: {
balances: { balances: {
ethereum: [], ethereum: [],
...@@ -43,11 +48,6 @@ const initialState = { ...@@ -43,11 +48,6 @@ const initialState = {
contracts: {}, contracts: {},
}; };
const TOKEN_LABEL_FALLBACK = {
'0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359': 'DAI',
'0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2': 'MKR',
};
// selectors // selectors
export const selectors = () => (dispatch, getState) => { export const selectors = () => (dispatch, getState) => {
const state = getState().web3connect; const state = getState().web3connect;
...@@ -197,6 +197,11 @@ export const watchApprovals = ({ tokenAddress, tokenOwner, spender }) => (dispat ...@@ -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 }) => ({ export const updateApprovals = ({ tokenAddress, tokenOwner, spender, balance }) => ({
type: UPDATE_APPROVALS, type: UPDATE_APPROVALS,
payload: { payload: {
...@@ -215,6 +220,7 @@ export const sync = () => async (dispatch, getState) => { ...@@ -215,6 +220,7 @@ export const sync = () => async (dispatch, getState) => {
watched, watched,
contracts, contracts,
networkId, networkId,
transactions: { pending, confirmed },
} = getState().web3connect; } = getState().web3connect;
// Sync Account // Sync Account
...@@ -275,14 +281,12 @@ export const sync = () => async (dispatch, getState) => { ...@@ -275,14 +281,12 @@ export const sync = () => async (dispatch, getState) => {
const balance = await contract.methods.balanceOf(address).call(); const balance = await contract.methods.balanceOf(address).call();
const decimals = tokenBalance.decimals || await contract.methods.decimals().call(); const decimals = tokenBalance.decimals || await contract.methods.decimals().call();
let symbol = tokenBalance.symbol; let symbol = tokenBalance.symbol;
try { try {
symbol = symbol || await contract.methods.symbol().call().catch(); symbol = symbol || await contract.methods.symbol().call().catch();
} catch (e) { } catch (e) {
try { try {
symbol = symbol || web3.utils.hexToString(await contractBytes32.methods.symbol().call().catch()); symbol = symbol || web3.utils.hexToString(await contractBytes32.methods.symbol().call().catch());
} catch (err) { } catch (err) {
} }
} }
...@@ -305,13 +309,23 @@ export const sync = () => async (dispatch, getState) => { ...@@ -305,13 +309,23 @@ export const sync = () => async (dispatch, getState) => {
Object.entries(watched.approvals) Object.entries(watched.approvals)
.forEach(([tokenAddress, token]) => { .forEach(([tokenAddress, token]) => {
const contract = contracts[tokenAddress] || new web3.eth.Contract(ERC20_ABI, tokenAddress); const contract = contracts[tokenAddress] || new web3.eth.Contract(ERC20_ABI, tokenAddress);
const contractBytes32 = contracts[tokenAddress] || new web3.eth.Contract(ERC20_WITH_BYTES_ABI, tokenAddress);
Object.entries(token) Object.entries(token)
.forEach(([ tokenOwnerAddress, tokenOwner ]) => { .forEach(([ tokenOwnerAddress, tokenOwner ]) => {
tokenOwner.forEach(async spenderAddress => { tokenOwner.forEach(async spenderAddress => {
const approvalBalance = getApprovals(tokenAddress, tokenOwnerAddress, spenderAddress); const approvalBalance = getApprovals(tokenAddress, tokenOwnerAddress, spenderAddress);
const balance = await contract.methods.allowance(tokenOwnerAddress, spenderAddress).call(); const balance = await contract.methods.allowance(tokenOwnerAddress, spenderAddress).call();
const decimals = approvalBalance.decimals || await contract.methods.decimals().call(); const decimals = approvalBalance.decimals || await contract.methods.decimals().call();
const symbol = TOKEN_LABEL_FALLBACK[tokenAddress] || approvalBalance.label || await contract.methods.symbol().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) {
}
}
if (approvalBalance.label && approvalBalance.value.isEqualTo(BN(balance))) { if (approvalBalance.label && approvalBalance.value.isEqualTo(BN(balance))) {
return; return;
...@@ -327,6 +341,20 @@ export const sync = () => async (dispatch, getState) => { ...@@ -327,6 +341,20 @@ export const sync = () => async (dispatch, getState) => {
}); });
}); });
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) => { export const startWatching = () => async (dispatch, getState) => {
...@@ -445,6 +473,34 @@ export default function web3connectReducer(state = initialState, { type, payload ...@@ -445,6 +473,34 @@ export default function web3connectReducer(state = initialState, { type, payload
}; };
case UPDATE_NETWORK_ID: case UPDATE_NETWORK_ID:
return { ...state, networkId: payload }; 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: default:
return state; return state;
} }
......
...@@ -35,3 +35,18 @@ html, body { ...@@ -35,3 +35,18 @@ html, body {
right: 0; right: 0;
z-index: 200; 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); }
}
...@@ -5,7 +5,7 @@ import classnames from 'classnames'; ...@@ -5,7 +5,7 @@ import classnames from 'classnames';
import {BigNumber as BN} from "bignumber.js"; import {BigNumber as BN} from "bignumber.js";
import MediaQuery from 'react-responsive'; import MediaQuery from 'react-responsive';
import ReactGA from 'react-ga'; import ReactGA from 'react-ga';
import { selectors } from '../../ducks/web3connect'; import { selectors, addPendingTx } from '../../ducks/web3connect';
import { CSSTransitionGroup } from "react-transition-group"; import { CSSTransitionGroup } from "react-transition-group";
import Header from '../../components/Header'; import Header from '../../components/Header';
import NavigationTabs from '../../components/NavigationTabs'; import NavigationTabs from '../../components/NavigationTabs';
...@@ -28,6 +28,7 @@ class Swap extends Component { ...@@ -28,6 +28,7 @@ class Swap extends Component {
account: PropTypes.string, account: PropTypes.string,
isConnected: PropTypes.bool.isRequired, isConnected: PropTypes.bool.isRequired,
selectors: PropTypes.func.isRequired, selectors: PropTypes.func.isRequired,
addPendingTx: PropTypes.func.isRequired,
web3: PropTypes.object.isRequired, web3: PropTypes.object.isRequired,
}; };
...@@ -355,6 +356,7 @@ class Swap extends Component { ...@@ -355,6 +356,7 @@ class Swap extends Component {
account, account,
web3, web3,
selectors, selectors,
addPendingTx,
} = this.props; } = this.props;
const { const {
inputValue, inputValue,
...@@ -392,7 +394,12 @@ class Swap extends Component { ...@@ -392,7 +394,12 @@ class Swap extends Component {
.send({ .send({
from: account, from: account,
value: BN(inputValue).multipliedBy(10 ** 18).toFixed(0), value: BN(inputValue).multipliedBy(10 ** 18).toFixed(0),
}, err => !err && this.reset()); }, (err, data) => {
if (!err) {
addPendingTx(data);
this.reset();
}
});
break; break;
case 'TOKEN_TO_ETH': case 'TOKEN_TO_ETH':
new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency]) new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency])
...@@ -402,9 +409,12 @@ class Swap extends Component { ...@@ -402,9 +409,12 @@ class Swap extends Component {
BN(outputValue).multipliedBy(10 ** outputDecimals).multipliedBy(1 - ALLOWED_SLIPPAGE).toFixed(0), BN(outputValue).multipliedBy(10 ** outputDecimals).multipliedBy(1 - ALLOWED_SLIPPAGE).toFixed(0),
deadline, deadline,
) )
.send({ .send({ from: account }, (err, data) => {
from: account, if (!err) {
}, err => !err && this.reset()); addPendingTx(data);
this.reset();
}
});
break; break;
case 'TOKEN_TO_TOKEN': case 'TOKEN_TO_TOKEN':
new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency]) new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency])
...@@ -416,9 +426,12 @@ class Swap extends Component { ...@@ -416,9 +426,12 @@ class Swap extends Component {
deadline, deadline,
outputCurrency, outputCurrency,
) )
.send({ .send({ from: account }, (err, data) => {
from: account, if (!err) {
}, err => !err && this.reset()); addPendingTx(data);
this.reset();
}
});
break; break;
default: default:
break; break;
...@@ -442,7 +455,12 @@ class Swap extends Component { ...@@ -442,7 +455,12 @@ class Swap extends Component {
.send({ .send({
from: account, from: account,
value: BN(inputValue).multipliedBy(10 ** inputDecimals).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(0), 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; break;
case 'TOKEN_TO_ETH': case 'TOKEN_TO_ETH':
new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency]) new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency])
...@@ -452,9 +470,12 @@ class Swap extends Component { ...@@ -452,9 +470,12 @@ class Swap extends Component {
BN(inputValue).multipliedBy(10 ** inputDecimals).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(0), BN(inputValue).multipliedBy(10 ** inputDecimals).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(0),
deadline, deadline,
) )
.send({ .send({ from: account }, (err, data) => {
from: account, if (!err) {
}, err => !err && this.reset()); addPendingTx(data);
this.reset();
}
});
break; break;
case 'TOKEN_TO_TOKEN': case 'TOKEN_TO_TOKEN':
if (!inputAmountB) { if (!inputAmountB) {
...@@ -469,9 +490,13 @@ class Swap extends Component { ...@@ -469,9 +490,13 @@ class Swap extends Component {
inputAmountB.multipliedBy(1.2).toFixed(0), inputAmountB.multipliedBy(1.2).toFixed(0),
deadline, deadline,
outputCurrency, outputCurrency,
).send({ )
from: account, .send({ from: account }, (err, data) => {
}, err => !err && this.reset()); if (!err) {
addPendingTx(data);
this.reset();
}
});
break; break;
default: default:
break; break;
...@@ -770,6 +795,7 @@ export default connect( ...@@ -770,6 +795,7 @@ export default connect(
}), }),
dispatch => ({ dispatch => ({
selectors: () => dispatch(selectors()), selectors: () => dispatch(selectors()),
addPendingTx: id => dispatch(addPendingTx(id)),
}), }),
)(Swap); )(Swap);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment