UNPKG

@bzxnetwork/portal

Version:
657 lines (613 loc) 20.6 kB
import styled from "styled-components"; import Button from "@material-ui/core/Button"; import Icon from "@material-ui/core/Icon"; import Tooltip from "@material-ui/core/Tooltip"; import IconButton from "@material-ui/core/IconButton"; import Input from "@material-ui/core/Input"; import InputLabel from "@material-ui/core/InputLabel"; import InputAdornment from "@material-ui/core/InputAdornment"; import FormControl from "@material-ui/core/FormControl"; import TextField from "@material-ui/core/TextField"; import Dialog from "@material-ui/core/Dialog"; import DialogActions from "@material-ui/core/DialogActions"; import DialogContent from "@material-ui/core/DialogContent"; import DialogContentText from "@material-ui/core/DialogContentText"; import DialogTitle from "@material-ui/core/DialogTitle"; import { COLORS } from "../styles/constants"; import { removeTrackedToken, PERMA_TOKEN_SYMBOLS, FAUCET_TOKEN_SYMBOLS } from "../common/trackedTokens"; import { fromBigNumber, toBigNumber } from "../common/utils"; import BZxComponent from "../common/BZxComponent"; const Container = styled.div` display: flex; align-items: center; margin-bottom: 24px; & > * { margin-right: 12px; } `; const TokenInfo = styled.a.attrs({ target: `_blank`, rel: `noopener noreferrer` })` display: block; width: 120px; display: flex; align-items: center; text-decoration: none; `; const TokenInfoLink = styled.a.attrs({ target: `_blank`, rel: `noopener noreferrer` })` text-decoration: underline; cursor: pointer; font-size: 0.75rem; `; const TokenIcon = styled.img` height: 24px; width: 24px; margin-right: 12px; `; const Name = styled.div` font-size: 12px; color: ${COLORS.gray}; `; const BalanceAmount = styled.div``; const ButtonGroup = styled.div` margin-left: auto; display: flex; align-items: center; & > *:first-child { margin-right: 12px; } `; const TooltipText = styled.div` font-family: monospace; font-size: 12px; `; const TxHashLink = styled.a.attrs({ target: `_blank`, rel: `noopener noreferrer` })` font-family: monospace; display: block; text-overflow: ellipsis; overflow: auto; } `; export default class TrackedTokenItem extends BZxComponent { state = { showSendDialog: false, showRequestDialog: false, recipientAddress: ``, sendAmount: ``, approved: null, balance: null, approvalLoading: false }; async componentDidMount() { this.checkAllowance(); this.getBalance(); } setStateForInput = key => e => this.setState({ [key]: e.target.value }); getBalance = async () => { const { bZx, token, accounts } = this.props; const balance = await window.pqueueTokens.add(() => this.wrapAndRun(bZx.getBalance({ tokenAddress: token.address, ownerAddress: accounts[0].toLowerCase() }))); console.log(`balance of`, token.name, balance.toNumber()); this.setState({ balance: fromBigNumber(balance, 10 ** token.decimals) }); }; checkAllowance = async () => { const { bZx, token, accounts } = this.props; console.log(`checking allowance`); console.log(token.name, token.address); const allowance = await window.pqueueTokens.add(() => this.wrapAndRun(bZx.getAllowance({ tokenAddress: token.address, ownerAddress: accounts[0].toLowerCase() }))); console.log(`Allowance:`, allowance.toNumber()); this.setState({ approved: allowance.toNumber() !== 0, approvalLoading: false }); }; toggleSendDialog = () => this.setState(p => ({ showSendDialog: !p.showSendDialog })); toggleRequestDialog = () => this.setState(p => ({ showRequestDialog: !p.showRequestDialog })); sendTokens = async () => { const { web3, bZx, token, accounts, updateTrackedTokens } = this.props; const { recipientAddress, sendAmount } = this.state; if (bZx.portalProviderName !== `MetaMask`) { alert(`Please confirm this transaction on your device.`); } const txOpts = { from: accounts[0], gas: 2000000, gasPrice: window.defaultGasPrice.toString() }; const txObj = await bZx.transferToken({ tokenAddress: token.address, to: recipientAddress.toLowerCase(), amount: toBigNumber(sendAmount, 10 ** token.decimals), getObject: true, txOpts }); try { await txObj .estimateGas(txOpts) .then(gas => { console.log(gas); txOpts.gas = window.gasValue(gas); txObj .send(txOpts) .once(`transactionHash`, hash => { alert(`Transaction submitted, transaction hash:`, { component: () => ( <TxHashLink href={`${bZx.etherscanURL}tx/${hash}`}> {hash} </TxHashLink> ) }); this.setState({ showSendDialog: false }); }) .then(() => { alert(`The tokens have been sent.`); updateTrackedTokens(true); }) .catch(error => { console.error(error.message); if ( error.message.includes(`denied transaction signature`) || error.message.includes(`Condition of use not satisfied`) || error.message.includes(`Invalid status`) ) { alert(); } else { alert( `The transaction is failing. Please check the amount and try again.` ); } this.setState({ showSendDialog: false }); }); }) .catch(error => { console.error(error.message); if ( error.message.includes(`denied transaction signature`) || error.message.includes(`Condition of use not satisfied`) || error.message.includes(`Invalid status`) ) { alert(); } else { alert( `The transaction is failing. Please check the amount and try again.` ); } this.setState({ showSendDialog: false }); }); } catch (error) { console.error(error.message); if ( error.message.includes(`denied transaction signature`) || error.message.includes(`Condition of use not satisfied`) || error.message.includes(`Invalid status`) ) { alert(); } else { alert( `The transaction is failing. Please check the amount and try again.` ); } this.setState({ showSendDialog: false }); } }; requestToken = async () => { const { web3, bZx, token, accounts, updateTrackedTokens } = this.props; console.log(`requesting token from testnet faucet`); console.log(token); if (bZx.portalProviderName !== `MetaMask`) { alert(`Please confirm this transaction on your device.`); } const txOpts = { from: accounts[0], // gas: 100000, gasPrice: window.defaultGasPrice.toString() }; const txObj = await bZx.requestFaucetToken({ tokenAddress: token.address, receiverAddress: accounts[0], getObject: true, txOpts }); try { await txObj .estimateGas(txOpts) .then(gas => { console.log(gas); txOpts.gas = window.gasValue(gas); txObj .send(txOpts) .once(`transactionHash`, hash => { alert(`Transaction submitted, transaction hash:`, { component: () => ( <TxHashLink href={`${bZx.etherscanURL}tx/${hash}`}> {hash} </TxHashLink> ) }); this.setState({ showRequestDialog: false }); }) .then(() => { alert(`Your request is complete.`); updateTrackedTokens(true); }) .catch(error => { console.error(error.message); if ( error.message.includes(`denied transaction signature`) || error.message.includes(`Condition of use not satisfied`) || error.message.includes(`Invalid status`) ) { alert(); } else { alert( `The transaction is failing. If you requested from the faucet recently, please try again later.` ); } this.setState({ showRequestDialog: false }); }); }) .catch(error => { console.error(error.message); if ( error.message.includes(`denied transaction signature`) || error.message.includes(`Condition of use not satisfied`) || error.message.includes(`Invalid status`) ) { alert(); } else { alert( `The transaction is failing. If you requested from the faucet recently, please try again later.` ); } this.setState({ showRequestDialog: false }); }); } catch (error) { console.error(error.message); if ( error.message.includes(`denied transaction signature`) || error.message.includes(`Condition of use not satisfied`) || error.message.includes(`Invalid status`) ) { alert(); } else { alert( `The transaction is failing. If you requested from the faucet recently, please try again later.` ); } this.setState({ showRequestDialog: false }); } }; handleRemoveToken = () => { removeTrackedToken(this.props.tokens, this.props.token.address); this.props.updateTrackedTokens(); }; approve = async () => { const { bZx, token, web3, accounts } = this.props; console.log(`approving allowance`); console.log(token.name, token.address); this.setState({ approvalLoading: true }); if (bZx.portalProviderName !== `MetaMask`) { alert(`Please confirm this transaction on your device.`); } const txOpts = { from: accounts[0], // gas: 1000000, gasPrice: window.defaultGasPrice.toString() }; const txObj = await bZx.setAllowanceUnlimited({ tokenAddress: token.address, ownerAddress: accounts[0].toLowerCase(), getObject: true, txOpts }); try { await txObj .estimateGas(txOpts) .then(gas => { console.log(gas); txOpts.gas = window.gasValue(gas); txObj .send(txOpts) .once(`transactionHash`, hash => { alert(`Transaction submitted, transaction hash:`, { component: () => ( <TxHashLink href={`${bZx.etherscanURL}tx/${hash}`}> {hash} </TxHashLink> ) }); }) .then(() => { alert(`Your token is approved.`); this.checkAllowance(); }) .catch(error => { console.error(error.message); if ( error.message.includes(`denied transaction signature`) || error.message.includes(`Condition of use not satisfied`) || error.message.includes(`Invalid status`) ) { alert(); } else { alert(`The transaction is failing. Please try again later.`); } this.setState({ approvalLoading: false }); }); }) .catch(error => { console.error(error.message); if ( error.message.includes(`denied transaction signature`) || error.message.includes(`Condition of use not satisfied`) || error.message.includes(`Invalid status`) ) { alert(); } else { alert(`The transaction is failing. Please try again later.`); } this.setState({ approvalLoading: false }); }); } catch (error) { console.error(error.message); if ( error.message.includes(`denied transaction signature`) || error.message.includes(`Condition of use not satisfied`) || error.message.includes(`Invalid status`) ) { alert(); } else { alert(`The transaction is failing. Please try again later.`); } this.setState({ approvalLoading: false }); } }; unapprove = async () => { const { bZx, token, web3, accounts } = this.props; console.log(`unapproving allowance`); console.log(token.name, token.address); this.setState({ approvalLoading: true }); if (bZx.portalProviderName !== `MetaMask`) { alert(`Please confirm this transaction on your device.`); } const txOpts = { from: accounts[0], // gas: 1000000, gasPrice: window.defaultGasPrice.toString() }; const txObj = await bZx.resetAllowance({ tokenAddress: token.address, ownerAddress: accounts[0].toLowerCase(), getObject: true }); try { await txObj .estimateGas(txOpts) .then(gas => { console.log(gas); txOpts.gas = window.gasValue(gas); txObj .send(txOpts) .once(`transactionHash`, hash => { alert(`Transaction submitted, transaction hash:`, { component: () => ( <TxHashLink href={`${bZx.etherscanURL}tx/${hash}`}> {hash} </TxHashLink> ) }); }) .then(() => { alert(`Your token is un-approved.`); this.checkAllowance(); }) .catch(error => { console.error(error.message); if ( error.message.includes(`denied transaction signature`) || error.message.includes(`Condition of use not satisfied`) || error.message.includes(`Invalid status`) ) { alert(); } else { alert(`The transaction is failing. Please try again later.`); } this.setState({ approvalLoading: false }); }); }) .catch(error => { console.error(error.message); if ( error.message.includes(`denied transaction signature`) || error.message.includes(`Condition of use not satisfied`) || error.message.includes(`Invalid status`) ) { alert(); } else { alert(`The transaction is failing. Please try again later.`); } this.setState({ approvalLoading: false }); }); } catch (error) { console.error(error.message); if ( error.message.includes(`denied transaction signature`) || error.message.includes(`Condition of use not satisfied`) || error.message.includes(`Invalid status`) ) { alert(); } else { alert(`The transaction is failing. Please try again later.`); } this.setState({ approvalLoading: false }); } }; renderAllowance = () => { const { approved, approvalLoading } = this.state; if (approved === null) { return <div>Checking</div>; } if (approved === true) { return ( <Button variant="raised" onClick={this.unapprove} disabled={approvalLoading} > Un-approve </Button> ); } return ( <Button variant="raised" onClick={this.approve} disabled={approvalLoading} > Approve </Button> ); }; render() { const { name, symbol, iconUrl, address } = this.props.token; const { balance } = this.state; const isPermaToken = PERMA_TOKEN_SYMBOLS.includes(symbol); const isFaucetToken = FAUCET_TOKEN_SYMBOLS[this.props.bZx.networkName] !== undefined && FAUCET_TOKEN_SYMBOLS[this.props.bZx.networkName].includes(symbol); return ( <Container> <TokenInfo href={`${this.props.bZx.etherscanURL}token/${address}?a=${this.props.accounts[0]}`}> <TokenIcon src={iconUrl} /> <Name>{name}</Name> </TokenInfo> {balance !== null ? ( <Tooltip title={<TooltipText>{address}</TooltipText>}> <BalanceAmount> {balance.toString()} {symbol} </BalanceAmount> </Tooltip> ) : ( <div>loading...</div> )} {` `} {symbol === `WETH` ? ( <TokenInfoLink href={`https://weth.io`}> More Info </TokenInfoLink> ) : ``} {symbol === `WBTC` ? ( <TokenInfoLink href={`https://www.wbtc.network`}> More Info </TokenInfoLink> ) : ``} <ButtonGroup> <Button variant="raised" onClick={this.toggleRequestDialog} style={{ visibility: !isFaucetToken ? `hidden` : `unset`, display: !isFaucetToken ? `none` : `unset` }} > Request </Button> {this.renderAllowance()} <Button variant="raised" color="primary" onClick={this.toggleSendDialog} style={{ marginLeft: `12px` }} disabled={symbol === `BZRX`} > Send </Button> <IconButton onClick={this.handleRemoveToken} style={{ visibility: isPermaToken ? `hidden` : `unset` }} > <Icon>close</Icon> </IconButton> </ButtonGroup> <Dialog open={this.state.showSendDialog} onClose={this.toggleSendDialog} > <DialogTitle>Send {name}</DialogTitle> <DialogContent> <DialogContentText> This token will be sent to another account. Please specify the recipient address and amount to send. </DialogContentText> <TextField autoFocus margin="normal" label="Recipient Address" fullWidth value={this.state.recipientAddress} onChange={this.setStateForInput(`recipientAddress`)} /> <FormControl fullWidth> <InputLabel>Send Amount</InputLabel> <Input value={this.state.sendAmount} type="number" onChange={this.setStateForInput(`sendAmount`)} endAdornment={ <InputAdornment position="end">{symbol}</InputAdornment> } /> </FormControl> </DialogContent> <DialogActions> <Button onClick={this.toggleSendDialog}>Cancel</Button> <Button onClick={this.sendTokens} color="primary"> Send </Button> </DialogActions> </Dialog> <Dialog open={this.state.showRequestDialog} onClose={this.toggleRequestDialog} > <DialogTitle>Request {name} from Faucet</DialogTitle> <DialogContent> <DialogContentText> This will request that 1 (one) {symbol} be transfered to your wallet. If you have requested this token recently, then this transaction may fail. </DialogContentText> </DialogContent> <DialogActions> <Button onClick={this.toggleRequestDialog}>Cancel</Button> <Button onClick={this.requestToken} color="primary"> Request </Button> </DialogActions> </Dialog> </Container> ); } }