@bzxnetwork/portal
Version:
Frontend demo portal for bZx
657 lines (613 loc) • 20.6 kB
JSX
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>
);
}
}