import { BigNumber, ethers, utils } from "ethers"; import { PureComponent } from "react"; import { Alert, Button, Container, Form, InputGroup, Spinner } from "react-bootstrap"; import { connect } from "react-redux"; import { addToken } from "../../services/metamask"; import { initBalances, reviewSwap, startAllowanceAndSwapSteps, startSwapStep } from "../../store/actions"; import { getBalances, getGasPrice, getGSNProvider, getPaymasterData, getTokenBalances, getWeb3State } from "../../store/blockchain/selectors"; import { Balance, Paymaster, SwapResponse, Token, TokenBalance, Web3State } from "../../types/blockchain"; import { StoreState } from "../../types/store"; const maxGasUsage = 1600000; enum SwapState { Init, Loading, Done } interface StateProps { ethBalance: Balance, tokensBalance: Array, gsnProvider: ethers.providers.Web3Provider, gasPrice: string, web3State: Web3State, paymasterData: Paymaster } interface DispatchProps { fetchBalances: () => any; // startAllowanceAndSwapSteps: ( token: Token, to: string, amount: string, callback: (res: SwapResponse) => void ) => any; startSwapStep: ( token: Token, amount: string, callback: (res: SwapResponse) => void ) => any; reviewSwap: (amount: string, token: Token) => Promise } interface OwnProps { amount: string, swapState: SwapState, txHash: string, gasUsed: string, selectedToken: string, txPrice: string, errorMessage: string, reviewSwap: boolean, swapReviewResponse: string, deposited: string, withdrawn: string } type Props = StateProps & DispatchProps; class SwapToken extends PureComponent { state = { amount: "", swapState: SwapState.Init, txHash: null, gasUsed: "", selectedToken: process.env.ZRO_TOKEN, txPrice: "", errorMessage: null, reviewSwap: false, swapReviewResponse: "", deposited: "", withdrawn: "" } addToken = async (token: Token) => { try { await addToken(token); } catch(e) { console.log(e) } } onSwap = (res: SwapResponse) => { if(res.error) { return this.setState({swapState: SwapState.Init, errorMessage: res.message}) } this.setState({ txHash: res.data.txHash, gasUsed: res.data.gasUsed, swapState: SwapState.Done, txPrice: res.data.txPrice, deposited: res.data.deposited, withdrawn: res.data.withdrawn }) } reviewSwap = async () => { const { selectedToken, amount } = this.state if(selectedToken === "") return null; const { tokensBalance } = this.props; const tokenBalance = tokensBalance.find(t => t.token.address === selectedToken) const swapReviewResponse = await this.props.reviewSwap(amount, tokenBalance.token) this.setState({swapReviewResponse: ethers.utils.formatEther(swapReviewResponse)}) } swap = async (address: string) => { const { amount, reviewSwap } = this.state; if(!reviewSwap) { this.setState({reviewSwap: true}); return this.reviewSwap() } this.setState({swapState: SwapState.Loading, errorMessage: null}) const { tokensBalance, paymasterData } = this.props const tokenBalance = tokensBalance.find(t => t.token.address === address); const amountBN = utils.parseUnits(amount, tokenBalance.token.decimals); const feeBN = utils.parseUnits(paymasterData.paymentData.fee, tokenBalance.token.decimals) const allowanceBN = utils.parseUnits(tokenBalance.tokenSwapAllowance, tokenBalance.token.decimals) try { let minAllowanceBN = amountBN.add(feeBN) if(allowanceBN.lt(minAllowanceBN)) { await this.props.startAllowanceAndSwapSteps(tokenBalance.token, process.env.GSN_TOKEN_SWAP, amount, this.onSwap); } else { this.props.startSwapStep(tokenBalance.token, amount, this.onSwap) } } catch(e) { const {message} = e; if(message.startsWith("user rejected transaction")){ } else if(message.includes("Not enough to pay for tx")) { this.setState({swapState: SwapState.Init, errorMessage: "Not enough to pay for tx, try swapping more tokens"}) } } } renderTokenInfo = () => { const { selectedToken } = this.state if(selectedToken === "") return null; const { tokensBalance } = this.props; const tokenBalance = tokensBalance.find(t => t.token.address === selectedToken) return ( <>
{tokenBalance.token.symbol} Balance: {Number(utils.formatUnits(tokenBalance.balance)).toLocaleString()}
1 ETH = {Number(utils.formatUnits(tokenBalance.uniswapValue)).toLocaleString()} {tokenBalance.token.symbol}
Total Supply: {Number(tokenBalance.totalSupply).toLocaleString()}
Total Burned: {Number(tokenBalance.burned).toLocaleString()}
) } renderAmountToGet = () => { const { amount, selectedToken, reviewSwap, swapReviewResponse } = this.state; if(amount === "" || isNaN(parseFloat(amount))) return null const title = reviewSwap ? "Swap" : "Review Swap" return (
{swapReviewResponse === "" ? null : {swapReviewResponse} ETH}
) } renderSwapInfo = () => { const { swapState, txHash, gasUsed, selectedToken, errorMessage, deposited, withdrawn} = this.state; if(selectedToken === "") return null; const { tokensBalance } = this.props if(swapState === SwapState.Loading) { return ( ) } else if(swapState === SwapState.Done) { return (

Transaction Mined
View on Goerli
Gas Used: {gasUsed}
Amount Swapped: {deposited} ETH
Amount Gotten: {withdrawn} ETH
Amount Charged: {parseFloat(deposited) - parseFloat(withdrawn)} ETH
) } else if(swapState === SwapState.Init && errorMessage) { return ( this.setState({errorMessage: null})} dismissible>{errorMessage} ) } return (
this.setState({ amount: target.value, errorMessage: null, reviewSwap: false, swapReviewResponse: "" }) } /> {this.renderAmountToGet()}
) } renderUserInfo = () => { const { ethBalance } = this.props return (
ETH Balance: {Number(utils.formatUnits(ethBalance.balance)).toLocaleString()}
) } render() { const { ethBalance, tokensBalance, web3State } = this.props; if(web3State!== Web3State.Done || !ethBalance || tokensBalance.length === 0) return null return (
{this.renderUserInfo()} {this.renderTokenInfo()} {this.renderSwapInfo()}
) } } const mapStateToProps = (state: StoreState): StateProps => { return { ethBalance: getBalances(state), tokensBalance: getTokenBalances(state), gsnProvider: getGSNProvider(state), gasPrice: getGasPrice(state), web3State: getWeb3State(state), paymasterData: getPaymasterData(state) } } const mapDispatchToProps = (dispatch: any): DispatchProps => { return { fetchBalances: () => dispatch(initBalances()), startAllowanceAndSwapSteps: ( token: Token, to: string, amount: string, callback: (res: SwapResponse) => void ) => dispatch(startAllowanceAndSwapSteps(token, to, amount, callback)), startSwapStep: ( token: Token, amount: string, callback: (res: SwapResponse) => void ) => dispatch(startSwapStep(token, amount, callback)), reviewSwap: ( amount: string, token: Token ) => dispatch(reviewSwap(amount, token)) }; }; const SwapTokenContainer = connect(mapStateToProps, mapDispatchToProps)(SwapToken); export { SwapTokenContainer }