From 9edaac82df774a5f7b915b48960e8e5345832b26 Mon Sep 17 00:00:00 2001 From: Dylan Mooers Date: Fri, 10 Dec 2021 22:41:21 -0800 Subject: [PATCH 1/3] deposit ui for clabs --- src/components/earn/OpenSumDepositModal.tsx | 317 ++++++++++++++++++++ src/constants/ConstantSum.ts | 68 +++-- src/pages/App.tsx | 2 + src/pages/OpenSum/Deposit.tsx | 155 ++++++++++ src/state/openSum/reducer.ts | 1 + 5 files changed, 511 insertions(+), 32 deletions(-) create mode 100644 src/components/earn/OpenSumDepositModal.tsx create mode 100644 src/pages/OpenSum/Deposit.tsx diff --git a/src/components/earn/OpenSumDepositModal.tsx b/src/components/earn/OpenSumDepositModal.tsx new file mode 100644 index 00000000000..7f7758ebab8 --- /dev/null +++ b/src/components/earn/OpenSumDepositModal.tsx @@ -0,0 +1,317 @@ +import { TransactionResponse } from '@ethersproject/providers' +import { JSBI, Token, TokenAmount } from '@ubeswap/sdk' +import CurrencyLogo from 'components/CurrencyLogo' +import React, { useState } from 'react' +import { ConstantSumPool } from 'state/openSum/reducer' +import { tryParseAmount } from 'state/swap/hooks' +import styled from 'styled-components' + +import { useActiveContractKit } from '../../hooks' +import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback' +import { useStableSwapContract } from '../../hooks/useContract' +import useTransactionDeadline from '../../hooks/useTransactionDeadline' +import { useTransactionAdder } from '../../state/transactions/hooks' +import { useCurrencyBalance } from '../../state/wallet/hooks' +import { CloseIcon, TYPE } from '../../theme' +import { ButtonError, ButtonPrimary } from '../Button' +import { AutoColumn } from '../Column' +import Modal from '../Modal' +import { LoadingView, SubmittedView } from '../ModalViews' +import { Input as NumericalInput } from '../NumericalInput' +import QuestionHelper from '../QuestionHelper' +import { RowBetween, RowFixed } from '../Row' +import Toggle from '../Toggle' + +const ContentWrapper = styled(AutoColumn)` + width: 100%; + padding: 1rem; +` + +const ApprovalButton = styled(ButtonPrimary)` + margin-right: 0.25rem; + margin-left: 0.25rem; +` + +interface OpenSumDepositModalProps { + isOpen: boolean + onDismiss: () => void + poolInfo: ConstantSumPool +} + +const dumbyToken = new Token(4220, '0x34deFd314fa23821a87FCbF5393311Bc5B7608C1', 18) + +export default function OpenSumDepositModal({ isOpen, onDismiss, poolInfo }: OpenSumDepositModalProps) { + const { account, network } = useActiveContractKit() + // monitor call to help UI loading state + const addTransaction = useTransactionAdder() + const { tokens } = poolInfo + const [hash, setHash] = useState() + const [attempting, setAttempting] = useState(false) + const [approving, setApproving] = useState(false) + const [input, setInput] = useState<(string | undefined)[]>(new Array(tokens.length).fill(undefined)) + const [useEqualAmount, setUseEqualAmount] = useState(false) + const deadline = useTransactionDeadline() + + const sumAmount = tokens + .map((t, i) => + JSBI.multiply( + tryParseAmount(input[i], t)?.raw ?? JSBI.BigInt(0), + JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18 - t.decimals)) + ) + ) + .reduce((acc, cur) => JSBI.add(acc, cur), JSBI.BigInt(0)) + + const expectedLPTokens = new TokenAmount(dumbyToken, sumAmount) + const selectedAmounts = input.map((val, i) => tryParseAmount(val, tokens[i]) ?? new TokenAmount(tokens[i], '0')) + + const approvals = [ + useApproveCallback(selectedAmounts[0], poolInfo.address), + useApproveCallback(selectedAmounts[1], poolInfo.address), + ] + const toApprove = approvals + .map(([approvalState], i) => { + if (approvalState !== ApprovalState.APPROVED) return i + else return null + }) + .filter((x) => x !== null) + function wrappedOndismiss() { + setHash(undefined) + setAttempting(false) + onDismiss() + } + + const stakingContract = useStableSwapContract(poolInfo.address) + async function onDeposit() { + const allValid = selectedAmounts.reduce((accum, cur) => accum && !!cur && !!cur.raw, true) + if (stakingContract) { + setAttempting(true) + const tokenAmounts = selectedAmounts.map((amount) => BigInt(amount.raw.toString())) + await stakingContract + .addLiquidity(tokenAmounts, expectedLPTokens.toString(), deadline, { gasLimit: 10000000 }) + .then((response: TransactionResponse) => { + addTransaction(response, { + summary: `Deposit Liquidity into ${poolInfo.address}`, + }) + setHash(response.hash) + }) + .catch((error: any) => { + setAttempting(false) + console.log(error) + }) + } + } + + let error: string | undefined + if (!account) { + error = 'Connect Wallet' + } + + return ( + + {!attempting && !hash && ( + + + Deposit to {poolInfo.name} + + + + + + Equal Amount + + + + setUseEqualAmount(!useEqualAmount)} + /> + + {poolInfo.tokens.map((token, i) => ( +
+ { + if (useEqualAmount) { + setInput(new Array(tokens.length).fill(val)) + } else { + setInput([...input.slice(0, i), val, ...input.slice(i + 1)]) + } + }} + // setUsingInsufficientFunds={setInsufficientFunds} + /> + {i !== selectedAmounts.length - 1 && ( + + + )} +
+ ))} + + Expected Lp Tokens Received: {expectedLPTokens.toFixed(2)} + + {toApprove.length > 0 && expectedLPTokens.greaterThan('0') && ( +
+ {toApprove.map((i) => ( + { + setApproving(true) + await approvals[i][1]() + await new Promise((resolve) => setTimeout(resolve, 20000)) + setApproving(false) + }} + > + Approve {tokens[i].symbol} + + ))} +
+ )} + {toApprove.length === 0 && ( + + {error ?? 'Deposit'} + + )} +
+ )} + {attempting && !hash && ( + + + Depositing + Claiming {expectedLPTokens.toSignificant(4)} LP Tokens + + + )} + {hash && ( + + + Transaction Submitted + Claimed LP Tokens! + + + )} +
+ ) +} + +type CurrencyRowProps = { + tokenAmount: TokenAmount + setInput: (amount: string) => void + input: string + setUsingInsufficientFunds: (isInsufficient: boolean) => void +} + +const InputRowLeft = styled.div`` + +const TokenInfo = styled.div`` + +const InputRow = styled.div<{ selected: boolean }>` + ${({ theme }) => theme.flexRowNoWrap}; + align-items: center; + justify-content: space-between; + padding: ${({ selected }) => (selected ? '0.75rem 0.5rem 0.75rem 1rem' : '0.75rem 0.75rem 0.75rem 1rem')}; +` + +const InputDiv = styled.div` + display: flex; + min-width: 40%; +` + +const Aligner = styled.span` + display: flex; + align-items: center; + justify-content: space-between; +` + +const InputPanel = styled.div<{ hideInput?: boolean }>` + ${({ theme }) => theme.flexColumnNoWrap} + position: relative; + border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')}; + background-color: ${({ theme }) => theme.bg1}; + z-index: 1; + width: 100%; +` + +const Container = styled.div<{ hideInput: boolean }>` + border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')}; + border: 1px solid ${({ theme }) => theme.bg2}; + background-color: ${({ theme }) => theme.bg1}; + padding: 0.5rem; +` + +const StyledTokenName = styled.span<{ active?: boolean }>` + ${({ active }) => (active ? ' margin: 0 0.25rem 0 0.75rem;' : ' margin: 0 0.25rem 0 0.25rem;')} + font-size: ${({ active }) => (active ? '20px' : '16px')}; + color: ${({ theme }) => theme.text1}; +` +const BalanceText = styled(TYPE.subHeader)` + cursor: pointer; +` + +const CurrencyRow = ({ tokenAmount, setInput, input, setUsingInsufficientFunds }: CurrencyRowProps) => { + const { account } = useActiveContractKit() + const currency = tokenAmount.currency + const tokenBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined) + const TEN = JSBI.BigInt('10') + const ZERO_TOK = new TokenAmount(currency, JSBI.BigInt('0')) + + const scaledDown = (num: JSBI) => JSBI.divide(num, JSBI.exponentiate(TEN, JSBI.BigInt(currency.decimals))) + const scaleUp = (num: JSBI) => JSBI.multiply(num, JSBI.exponentiate(TEN, JSBI.BigInt(currency.decimals))) + + const decimalPlacesForBalance = tokenBalance?.greaterThan( + '1' //JSBI.exponentiate(JSBI.BigInt('10'), JSBI.BigInt(tokenBalance.token.decimals - 2)).toString() + ) + ? 2 + : tokenBalance?.greaterThan('0') + ? 6 + : 2 + + const mainRow = ( + +
+ + + + {(currency && currency.symbol && currency.symbol.length > 20 + ? currency.symbol.slice(0, 4) + + '...' + + currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length) + : currency?.symbol) || ''} + + +
+ + { + setInput(val) + }} + /> + +
+ ) + const balanceRow = ( +
+ setInput(tokenBalance?.toFixed(5) ?? '')}> + Balance: {tokenBalance?.toFixed(decimalPlacesForBalance) ?? 'Loading...'} + +
+ ) + + return ( +
+ {balanceRow} + {mainRow} +
+ ) +} + +const insertDecimal = (tokenAmount: TokenAmount) => { + const { token } = tokenAmount + const amount = tokenAmount.divide( + new TokenAmount(token, JSBI.exponentiate(JSBI.BigInt('10'), JSBI.BigInt(token.decimals))) + ) + return amount.toFixed(2) +} diff --git a/src/constants/ConstantSum.ts b/src/constants/ConstantSum.ts index a8995622613..4397f9daf2b 100644 --- a/src/constants/ConstantSum.ts +++ b/src/constants/ConstantSum.ts @@ -2,48 +2,22 @@ import { ChainId } from '@ubeswap/sdk' import { WrappedTokenInfo } from 'state/lists/hooks' export type ConstantSumInfo = { + name: string address: string tokens: [WrappedTokenInfo, WrappedTokenInfo] } export const ConstantSum: { [K in ChainId]: ConstantSumInfo[] | undefined } = { [ChainId.MAINNET]: [ - // { - // // wETH v1/v2 pool - // address: '0xb1a0BDe36341065cA916c9f5619aCA82A43659A3', - // tokens: [ - // new WrappedTokenInfo( - // { - // chainId: ChainId.MAINNET, - // address: '0xE919F65739c26a42616b7b8eedC6b5524d1e3aC4', - // decimals: 18, - // symbol: 'wETHxV1', - // name: 'Wrapped Ether (Optics Bridge)', - // logoURI: 'https://etherscan.io/token/images/weth_28.png', - // }, - // [] - // ), - // new WrappedTokenInfo( - // { - // chainId: ChainId.MAINNET, - // address: '0x122013fd7dF1C6F636a5bb8f03108E876548b455', - // decimals: 18, - // symbol: 'wETH', - // name: 'Wrapped Ether (Optics Bridge)', - // logoURI: 'https://etherscan.io/token/images/weth_28.png', - // }, - // [] - // ), - // ], - // }, { // wETH v1/v2 pool - address: '0x7e0B5284864916A866Fc391454ac2f452F91a336', + name: 'wETH v1/v2 pool', + address: '0xb1a0BDe36341065cA916c9f5619aCA82A43659A3', tokens: [ new WrappedTokenInfo( { chainId: ChainId.MAINNET, - address: '0xD68536297a01DBB4739a4e2cC1E79a8CFA2E3A3E', + address: '0xE919F65739c26a42616b7b8eedC6b5524d1e3aC4', decimals: 18, symbol: 'wETHxV1', name: 'Wrapped Ether (Optics Bridge)', @@ -54,7 +28,7 @@ export const ConstantSum: { [K in ChainId]: ConstantSumInfo[] | undefined } = { new WrappedTokenInfo( { chainId: ChainId.MAINNET, - address: '0xb909F71b53C621e467Ee9ECD387E6662CA4f15eF', + address: '0x122013fd7dF1C6F636a5bb8f03108E876548b455', decimals: 18, symbol: 'wETH', name: 'Wrapped Ether (Optics Bridge)', @@ -64,9 +38,38 @@ export const ConstantSum: { [K in ChainId]: ConstantSumInfo[] | undefined } = { ), ], }, - + // { + // // wETH v1/v2 pool + // name: 'wETH v1/v2 pool', + // address: '0x7e0B5284864916A866Fc391454ac2f452F91a336', + // tokens: [ + // new WrappedTokenInfo( + // { + // chainId: ChainId.MAINNET, + // address: '0xD68536297a01DBB4739a4e2cC1E79a8CFA2E3A3E', + // decimals: 18, + // symbol: 'wETHxV1', + // name: 'Wrapped Ether (Optics Bridge)', + // logoURI: 'https://etherscan.io/token/images/weth_28.png', + // }, + // [] + // ), + // new WrappedTokenInfo( + // { + // chainId: ChainId.MAINNET, + // address: '0xb909F71b53C621e467Ee9ECD387E6662CA4f15eF', + // decimals: 18, + // symbol: 'wETH', + // name: 'Wrapped Ether (Optics Bridge)', + // logoURI: 'https://etherscan.io/token/images/weth_28.png', + // }, + // [] + // ), + // ], + // }, { // BTC v1/v2 + name: 'wBTC v1/v2 pool', address: '0xd5ab1BA8b2Ec70752068d1d728e728eAd0E19CBA', tokens: [ new WrappedTokenInfo( @@ -95,6 +98,7 @@ export const ConstantSum: { [K in ChainId]: ConstantSumInfo[] | undefined } = { }, { // USDC v1/v2 + name: 'USDC v1/v2 pool', address: '0x70bfA1C8Ab4e42B9BE74f65941EFb6e5308148c7', tokens: [ new WrappedTokenInfo( diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 138a2b6894d..bae2d1e9056 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -20,6 +20,7 @@ import ApeViewer from './ApeViewer' import Claim from './Claim' import Mento from './Mento' import OpenSum from './OpenSum' +import OpenSumDeposit from './OpenSum/Deposit' import Pool from './Pool' import Manage from './Pool/Manage' import Reset from './Reset' @@ -131,6 +132,7 @@ export default function App() { + {/* */} diff --git a/src/pages/OpenSum/Deposit.tsx b/src/pages/OpenSum/Deposit.tsx new file mode 100644 index 00000000000..c12da5983a2 --- /dev/null +++ b/src/pages/OpenSum/Deposit.tsx @@ -0,0 +1,155 @@ +import React, { useState } from 'react' +import Countdown from 'react-countdown' +import { NavLink } from 'react-router-dom' +import styled from 'styled-components' + +import { ButtonConfirmed } from '../../components/Button' +import OpenSumDepositModal from '../../components/earn/OpenSumDepositModal' +import { useOpenPools } from '../../state/openSum/hooks' +import { ConstantSumPool } from '../../state/openSum/reducer' +import { colors } from '../../theme' + +const { primary1: mobiGreen, bg4 } = colors(false) + +const StyledCountdown = styled(Countdown)` + padding: 2rem; + font-size: 3rem; +` + +const Container = styled.div` + padding-top: 4rem; + width: min(1080px, 90vw); + display: flex; + flex-direction: column; + ${({ theme }) => theme.mediaWidth.upToSmall` + padding: 1.25rem; + padding-top: 0; + margin-top: 0; +`} + overflow: none; +` + +const Footer = styled.div` + display: flex; + justify-content: space-around; +` + +const ExternalLink = styled.a` + margin: 0.5rem; + height: 3rem; + width: 3rem; + border-radius: 2rem; + background: white; + cursor: pointer; +` + +const LogoContainer = styled.div` + width: min(25rem, 95%); + margin-top: 2rem; +` + +export const StyledMenuButton = styled(NavLink)` + display: flex; + align-items: center; + justify-content: center; + position: relative; + width: min(15rem, 50%); + height: 100%; + border: none; + background-color: ${mobiGreen}; + margin: 2rem; + margin-top: 0.5rem; + padding: 1rem; + height: 35px; + border-radius: 0.5rem; + font-size: 1rem; + font-weight: 1000; + color: black; + text-decoration: none; + + :hover, + :focus { + cursor: pointer; + outline: none; + background-color: ${({ theme }) => theme.primary2}; + } + + svg { + margin-top: 2px; + } + > * { + stroke: ${({ theme }) => theme.text1}; + } +` + +const Divider = styled.div` + width: 100%; + background: ${({ theme, show }) => show && theme.primary3}; + height: 1px; + margin: auto; + margin-top: 1rem; + margin-bottom: 2.5rem; +` + +export const ComingSoon = styled.a` + text-align: center; + display: flex; + align-items: center; + justify-content: center; + position: relative; + width: min(15rem, 70%); + height: 100%; + border: none; + background-color: ${bg4}; + margin: 2rem; + margin-top: 0; + padding: 1.5rem; + height: 35px; + border-radius: 0.5rem; + font-size: 1rem; + font-weight: 1000; + color: black; + text-decoration: none; + + :hover, + :focus { + cursor: pointer; + outline: none; + } + + svg { + margin-top: 2px; + } + > * { + stroke: ${({ theme }) => theme.text1}; + } +` + +// const NETWORK_LABELS: { [chainId in ChainId]?: string } = { +// [ChainId.ALFAJORES]: 'Alfajores', +// [ChainId.BAKLAVA]: 'Baklava', +// } + +export default function OpenSumDeposit() { + const pools = useOpenPools() + return ( + + {pools.map((p) => ( + + ))} + + ) +} + +function OpenPoolCard({ poolInfo }: { poolInfo: ConstantSumPool }) { + const [openModal, setOpenModal] = useState(false) + return ( + <> + setOpenModal(false)} poolInfo={poolInfo} /> + setOpenModal(true)} + >{`Deposit into ${poolInfo.name} pool`} + + ) +} diff --git a/src/state/openSum/reducer.ts b/src/state/openSum/reducer.ts index fc1cdcfdcc6..150c8a414df 100644 --- a/src/state/openSum/reducer.ts +++ b/src/state/openSum/reducer.ts @@ -7,6 +7,7 @@ import { WrappedTokenInfo } from 'state/lists/hooks' import { updateBalances } from './actions' export type ConstantSumPool = { + name: string address: string tokens: [WrappedTokenInfo, WrappedTokenInfo] balances?: JSBI[] From 916dc91dcdf86c780289c79e3cfed3bf8bbe575f Mon Sep 17 00:00:00 2001 From: Dylan Mooers Date: Sat, 11 Dec 2021 11:59:23 -0800 Subject: [PATCH 2/3] Fixed deposit button --- src/components/earn/OpenSumDepositModal.tsx | 8 +- src/constants/ConstantSum.ts | 2 +- src/constants/abis/OpenSumSwap.json | 504 ++++++++++++++++++++ 3 files changed, 509 insertions(+), 5 deletions(-) create mode 100644 src/constants/abis/OpenSumSwap.json diff --git a/src/components/earn/OpenSumDepositModal.tsx b/src/components/earn/OpenSumDepositModal.tsx index 7f7758ebab8..36025cd52c6 100644 --- a/src/components/earn/OpenSumDepositModal.tsx +++ b/src/components/earn/OpenSumDepositModal.tsx @@ -8,7 +8,7 @@ import styled from 'styled-components' import { useActiveContractKit } from '../../hooks' import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback' -import { useStableSwapContract } from '../../hooks/useContract' +import { useConstantSumContract } from '../../hooks/useContract' import useTransactionDeadline from '../../hooks/useTransactionDeadline' import { useTransactionAdder } from '../../state/transactions/hooks' import { useCurrencyBalance } from '../../state/wallet/hooks' @@ -80,14 +80,14 @@ export default function OpenSumDepositModal({ isOpen, onDismiss, poolInfo }: Ope onDismiss() } - const stakingContract = useStableSwapContract(poolInfo.address) + const stakingContract = useConstantSumContract(poolInfo.address) async function onDeposit() { const allValid = selectedAmounts.reduce((accum, cur) => accum && !!cur && !!cur.raw, true) if (stakingContract) { setAttempting(true) - const tokenAmounts = selectedAmounts.map((amount) => BigInt(amount.raw.toString())) + const tokenAmounts = selectedAmounts.map((amount) => amount?.raw.toString() ?? '0') await stakingContract - .addLiquidity(tokenAmounts, expectedLPTokens.toString(), deadline, { gasLimit: 10000000 }) + .addLiquidity(tokenAmounts, expectedLPTokens.raw.toString()) .then((response: TransactionResponse) => { addTransaction(response, { summary: `Deposit Liquidity into ${poolInfo.address}`, diff --git a/src/constants/ConstantSum.ts b/src/constants/ConstantSum.ts index 4397f9daf2b..358f92ce4f3 100644 --- a/src/constants/ConstantSum.ts +++ b/src/constants/ConstantSum.ts @@ -40,7 +40,7 @@ export const ConstantSum: { [K in ChainId]: ConstantSumInfo[] | undefined } = { }, // { // // wETH v1/v2 pool - // name: 'wETH v1/v2 pool', + // name: 'TEST v1/v2 pool', // address: '0x7e0B5284864916A866Fc391454ac2f452F91a336', // tokens: [ // new WrappedTokenInfo( diff --git a/src/constants/abis/OpenSumSwap.json b/src/constants/abis/OpenSumSwap.json new file mode 100644 index 00000000000..0aa84874c29 --- /dev/null +++ b/src/constants/abis/OpenSumSwap.json @@ -0,0 +1,504 @@ +[ + { + "inputs": [ + { + "internalType": "contract IERC20[]", + "name": "_tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_decimals", + "type": "uint256[]" + }, + { + "internalType": "string", + "name": "_lpTokenName", + "type": "string" + }, + { + "internalType": "string", + "name": "_lpTokenSymbol", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "provider", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "tokenAmounts", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "lpTokenSupply", + "type": "uint256" + } + ], + "name": "AddLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "provider", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "tokenAmounts", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "lpTokenSupply", + "type": "uint256" + } + ], + "name": "RemoveLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "provider", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "lpTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "lpTokenSupply", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "boughtId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokensBought", + "type": "uint256" + } + ], + "name": "RemoveLiquidityOne", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "buyer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokensSold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokensBought", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "soldId", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "boughtId", + "type": "uint128" + } + ], + "name": "TokenSwap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "minToMint", + "type": "uint256" + } + ], + "name": "addLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "calculateRemoveLiquidity", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + } + ], + "name": "calculateRemoveLiquidityOneToken", + "outputs": [ + { + "internalType": "uint256", + "name": "availableTokenAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBalances", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLpToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "index", + "type": "uint8" + } + ], + "name": "getToken", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "index", + "type": "uint8" + } + ], + "name": "getTokenBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenIndex", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "minAmounts", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "removeLiquidity", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "minAmount", + "type": "uint256" + } + ], + "name": "removeLiquidityOneToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenFrom", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenTo", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] From a63a07ef9001f38fc9d0c6bfa68a74d709a91962 Mon Sep 17 00:00:00 2001 From: Dylan Mooers Date: Tue, 28 Dec 2021 14:54:35 -0800 Subject: [PATCH 3/3] added withdraw ui --- src/components/earn/OpenSumWithdrawModal.tsx | 124 +++++++++++++++++++ src/constants/ConstantSum.ts | 9 +- src/pages/OpenSum/Deposit.tsx | 18 ++- src/state/openSum/reducer.ts | 3 +- 4 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 src/components/earn/OpenSumWithdrawModal.tsx diff --git a/src/components/earn/OpenSumWithdrawModal.tsx b/src/components/earn/OpenSumWithdrawModal.tsx new file mode 100644 index 00000000000..341beb58638 --- /dev/null +++ b/src/components/earn/OpenSumWithdrawModal.tsx @@ -0,0 +1,124 @@ +import { TransactionResponse } from '@ethersproject/providers' +import { JSBI, TokenAmount } from '@ubeswap/sdk' +import { useMobi } from 'hooks/Tokens' +import { ApprovalState, useApproveCallback } from 'hooks/useApproveCallback' +import React, { useState } from 'react' +import { ConstantSumPool } from 'state/openSum/reducer' +import { useTokenBalance } from 'state/wallet/hooks' +import styled from 'styled-components' + +import { useActiveContractKit } from '../../hooks' +import { useConstantSumContract } from '../../hooks/useContract' +import { useTransactionAdder } from '../../state/transactions/hooks' +import { CloseIcon, TYPE } from '../../theme' +import { ButtonError } from '../Button' +import { AutoColumn } from '../Column' +import Modal from '../Modal' +import { LoadingView, SubmittedView } from '../ModalViews' +import { RowBetween } from '../Row' + +const ContentWrapper = styled(AutoColumn)` + width: 100%; + padding: 1rem; +` + +interface StakingModalProps { + isOpen: boolean + onDismiss: () => void + poolInfo: ConstantSumPool +} + +export default function OpenSumWithdrawModal({ isOpen, onDismiss, poolInfo }: StakingModalProps) { + const { account, chainId } = useActiveContractKit() + + // monitor call to help UI loading state + const addTransaction = useTransactionAdder() + const [hash, setHash] = useState() + const [attempting, setAttempting] = useState(false) + + function wrappedOndismiss() { + setHash(undefined) + setAttempting(false) + onDismiss() + } + + const stakingContract = useConstantSumContract(poolInfo.address) + const mobi = useMobi() + const withdrawFunction = stakingContract?.removeLiquidityOneToken + const v1Balance = useTokenBalance(poolInfo.address, poolInfo.tokens[0]) + const lpAmount = new TokenAmount( + poolInfo.lpToken, + JSBI.multiply( + v1Balance?.raw ?? JSBI.BigInt('0'), + JSBI.exponentiate(JSBI.BigInt('10'), JSBI.BigInt(poolInfo.lpToken.decimals - (v1Balance?.token.decimals ?? 0))) + ) + ) + const [approvalStatus, approvalCallback] = useApproveCallback(lpAmount, poolInfo.address) + + async function onWithdraw() { + if (stakingContract && v1Balance && withdrawFunction) { + setAttempting(true) + await withdrawFunction(lpAmount.raw.toString(), v1Balance.token.address, v1Balance.raw.toString(), { + gasLimit: 3000000, + }) + .then((response: TransactionResponse) => { + addTransaction(response, { + summary: `Withdraw ${v1Balance.toFixed(2)} ${v1Balance.token.symbol}`, + }) + setHash(response.hash) + }) + .catch((error: any) => { + setAttempting(false) + console.log(error) + }) + } + } + + let error: string | undefined + if (!account) { + error = 'Connect Wallet' + } + return ( + + {!attempting && !hash && ( + + + Withdraw from {poolInfo.name} + + + + Withdraw {v1Balance?.toFixed(2)} {v1Balance?.token.symbol} + + {approvalStatus === ApprovalState.APPROVED ? ( + + {error ?? 'Withdraw'} + + ) : ( + + Approve {lpAmount.toFixed(2)} lp token + + )} + + )} + {attempting && !hash && ( + + + + Withdrawing {v1Balance?.toFixed(2)} {v1Balance?.token.symbol} + + + + )} + {hash && ( + + + Transaction Submitted + + Withdrew {v1Balance?.toFixed(2)} {v1Balance?.token.symbol}! + + + + )} + + ) +} diff --git a/src/constants/ConstantSum.ts b/src/constants/ConstantSum.ts index 358f92ce4f3..00db69c3fb3 100644 --- a/src/constants/ConstantSum.ts +++ b/src/constants/ConstantSum.ts @@ -1,10 +1,11 @@ -import { ChainId } from '@ubeswap/sdk' +import { ChainId, Token } from '@ubeswap/sdk' import { WrappedTokenInfo } from 'state/lists/hooks' export type ConstantSumInfo = { name: string address: string tokens: [WrappedTokenInfo, WrappedTokenInfo] + lpToken: Token } export const ConstantSum: { [K in ChainId]: ConstantSumInfo[] | undefined } = { @@ -13,6 +14,7 @@ export const ConstantSum: { [K in ChainId]: ConstantSumInfo[] | undefined } = { // wETH v1/v2 pool name: 'wETH v1/v2 pool', address: '0xb1a0BDe36341065cA916c9f5619aCA82A43659A3', + lpToken: new Token(ChainId.MAINNET, '0x772bf80363d40c6a8305da935c063cd3203d16cc', 18), tokens: [ new WrappedTokenInfo( { @@ -39,9 +41,10 @@ export const ConstantSum: { [K in ChainId]: ConstantSumInfo[] | undefined } = { ], }, // { - // // wETH v1/v2 pool + // // wETH v1/v2 pool 0x4a4cd1df17c79ef74733e46a84e37b7f6b9ea914 // name: 'TEST v1/v2 pool', // address: '0x7e0B5284864916A866Fc391454ac2f452F91a336', + // lpToken: new Token(ChainId.MAINNET, '0x4a4cd1df17c79ef74733e46a84e37b7f6b9ea914', 18), // tokens: [ // new WrappedTokenInfo( // { @@ -71,6 +74,7 @@ export const ConstantSum: { [K in ChainId]: ConstantSumInfo[] | undefined } = { // BTC v1/v2 name: 'wBTC v1/v2 pool', address: '0xd5ab1BA8b2Ec70752068d1d728e728eAd0E19CBA', + lpToken: new Token(ChainId.MAINNET, '0x301897c5fc341ec0daab2b61893cb8e5d314a1b1', 18), tokens: [ new WrappedTokenInfo( { @@ -100,6 +104,7 @@ export const ConstantSum: { [K in ChainId]: ConstantSumInfo[] | undefined } = { // USDC v1/v2 name: 'USDC v1/v2 pool', address: '0x70bfA1C8Ab4e42B9BE74f65941EFb6e5308148c7', + lpToken: new Token(ChainId.MAINNET, '0xe440a01eaf67b88381f2baa7de4538f8503683a0', 18), tokens: [ new WrappedTokenInfo( { diff --git a/src/pages/OpenSum/Deposit.tsx b/src/pages/OpenSum/Deposit.tsx index c12da5983a2..a330a984cdf 100644 --- a/src/pages/OpenSum/Deposit.tsx +++ b/src/pages/OpenSum/Deposit.tsx @@ -1,3 +1,5 @@ +import OpenSumWithdrawModal from 'components/earn/OpenSumWithdrawModal' +import { RowBetween } from 'components/Row' import React, { useState } from 'react' import Countdown from 'react-countdown' import { NavLink } from 'react-router-dom' @@ -143,13 +145,21 @@ export default function OpenSumDeposit() { function OpenPoolCard({ poolInfo }: { poolInfo: ConstantSumPool }) { const [openModal, setOpenModal] = useState(false) + const [openWithdraw, setOpenWithdraw] = useState(false) return ( <> setOpenModal(false)} poolInfo={poolInfo} /> - setOpenModal(true)} - >{`Deposit into ${poolInfo.name} pool`} + setOpenWithdraw(false)} poolInfo={poolInfo} /> + + setOpenModal(true)} + >{`Deposit into ${poolInfo.name} pool`} + setOpenWithdraw(true)} + >{`Withdraw from ${poolInfo.name} pool`} + ) } diff --git a/src/state/openSum/reducer.ts b/src/state/openSum/reducer.ts index 150c8a414df..0b1dda0cf4b 100644 --- a/src/state/openSum/reducer.ts +++ b/src/state/openSum/reducer.ts @@ -1,5 +1,5 @@ import { createReducer } from '@reduxjs/toolkit' -import { JSBI } from '@ubeswap/sdk' +import { JSBI, Token } from '@ubeswap/sdk' import { NETWORK_CHAIN_ID } from 'connectors' import { ConstantSum } from 'constants/ConstantSum' import { WrappedTokenInfo } from 'state/lists/hooks' @@ -9,6 +9,7 @@ import { updateBalances } from './actions' export type ConstantSumPool = { name: string address: string + lpToken: Token tokens: [WrappedTokenInfo, WrappedTokenInfo] balances?: JSBI[] }