Add page MVP UI + v3 pool hooks and state (#35)

* WIP start usePool and useDerivedMint hooks

* naming updates

* Use real tick and pool math

* rate updates

* WIP start usePool and useDerivedMint hooks

* naming updates

* Use real tick and pool math

* rate updates

* fix warnings

* fix incorrect import

* clean up state, fix preview

* same token check

* amoutn parse update

* update hard coded chain id

* fix price creation in util

* update 1 amount in price calculation

* update comments

* update tick spacing input

* fix label on counter

* update rate label on range select

* update labels

* fixing pool hook

* clean pool hook

* preserve working rate switching

* reset values on rate switch

* clean up derived hook - setup for testnet

* format slippage amounts and support ETH

* fix import error

* fix package.json dependencies

* silence warnings

* silence more warnings

* bump multicodec and multihashes

* update migrator constants

* update txn to use sdk calldata

* fix txn formatting, update summary

* Squashed commit of the following:

commit b81ff7ca9e57bb8c3823be4c54827e5955fb3d15
Author: ianlapham <ianlapham@gmail.com>
Date:   Mon Apr 12 23:46:09 2021 -0400

    fix txn formatting, update summary

commit b9f91b0746c546602d336c8fd6f614ec9b4f3f19
Author: ianlapham <ianlapham@gmail.com>
Date:   Mon Apr 12 19:29:12 2021 -0400

    update txn to use sdk calldata

commit 20acf704c67cfd4f597494c8cb9c672c6270ae02
Merge: 4431914 2462901
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Apr 11 20:33:39 2021 -0400

    Merge branch 'minting' of https://github.com/Uniswap/v3-interface into minting

commit 44319146372e1c373b025741ae896fa2476e5765
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Apr 11 20:32:35 2021 -0400

    update migrator constants

commit 35e0618de06ba316d3a3f327075625760414ab83
Merge: 8927882 c3f65e3
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Apr 11 20:13:36 2021 -0400

    Merge branch 'main' of https://github.com/Uniswap/v3-interface into minting

commit 24629019e80c368c337a2679a51d4acb1097171c
Author: Noah Zinsmeister <noahwz@gmail.com>
Date:   Fri Apr 9 15:56:25 2021 -0400

    bump multicodec and multihashes

commit 9b5dd1876a64acbf6694d208b608bb0b429e317f
Author: Noah Zinsmeister <noahwz@gmail.com>
Date:   Fri Apr 9 14:59:09 2021 -0400

    silence more warnings

commit 140ddc1b54c7fbdd7ead2fa64bcc302f201d69f5
Author: Noah Zinsmeister <noahwz@gmail.com>
Date:   Fri Apr 9 14:57:58 2021 -0400

    silence warnings

commit 5a2726ebdd4ffaacfb3d8ec7903a944042c1bd9d
Author: Noah Zinsmeister <noahwz@gmail.com>
Date:   Fri Apr 9 14:35:01 2021 -0400

    fix package.json dependencies

commit 7c4d0a40931338de9a6197652b82fdab773483e3
Author: Noah Zinsmeister <noahwz@gmail.com>
Date:   Fri Apr 9 14:21:46 2021 -0400

    fix import error

commit e49ef19cbef7fbdf1737787a439e7cb78ba295b4
Merge: 8927882 c3f65e3
Author: Noah Zinsmeister <noahwz@gmail.com>
Date:   Fri Apr 9 14:08:34 2021 -0400

    Merge branch 'main' into minting

commit 89278825bd798a87d6010a74f8fc1d2b34a8ece1
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 8 15:18:40 2021 -0400

    format slippage amounts and support ETH

commit 9a90b19e9a759cbc0c3e903a983660730c8833ad
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Apr 7 19:43:43 2021 -0400

    clean up derived hook - setup for testnet

commit dc034bc78a147f95f47b077d28a7d6e3165cedd7
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Apr 7 00:48:24 2021 -0400

    reset values on rate switch

commit bb5ccb2c853f7b2c27ec8d2f34f42a1b06f845b9
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Apr 7 00:38:39 2021 -0400

    preserve working rate switching

commit 5312d0ae7015150da48ba304de8c7a02b7d8925c
Author: ianlapham <ianlapham@gmail.com>
Date:   Mon Apr 5 13:52:46 2021 -0400

    clean pool hook

commit 5222de14834e76c37755225be17214a6e798d872
Merge: b2ba466 24521f0
Author: ianlapham <ianlapham@gmail.com>
Date:   Mon Apr 5 12:20:34 2021 -0400

    Merge branch 'main' of https://github.com/Uniswap/v3-interface into minting

commit b2ba46684a7b0bd8a8362f5990f4a208bfeff2dd
Author: ianlapham <ianlapham@gmail.com>
Date:   Mon Apr 5 12:19:20 2021 -0400

    fixing pool hook

commit b10742af99a725e04c1b756aa20f99e995f8cfeb
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 16:53:52 2021 -0400

    update labels

commit 05abd395949245596c95090a9d5d77c7c272dbd3
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 15:34:17 2021 -0400

    update rate label on range select

commit f098d01b6f4dc1dcb99e0fa314dde93647a19bb6
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 15:26:30 2021 -0400

    fix label on counter

commit 16ffe61e8ee2b677adf5d468efa9d7aa8d7e092e
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 15:06:50 2021 -0400

    update tick spacing input

commit 0fa2c8a15821dd32ec978750991a962ecb8f7344
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 14:53:18 2021 -0400

    update comments

commit 1fccf57a1ef081ef6ba9790dc20e0ed604ac2b09
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 14:52:37 2021 -0400

    update 1 amount in price calculation

commit b0e5d22bf8c57b3eacd75f077f68aaca4a9f975a
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 14:46:41 2021 -0400

    fix price creation in util

commit 1ce246e85372e4f120f983ca18a1eb3d16e8647e
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 13:55:14 2021 -0400

    update hard coded chain id

commit 2360b2d0a3233b604956e89de4bd7b09c0506875
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 13:09:21 2021 -0400

    amoutn parse update

commit 6a99a7b71fe446fe77cb2741adce4c067862ca4a
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 13:05:41 2021 -0400

    same token check

commit 83a1fd5a9ff02c6a49532cb54a57770b52fc052e
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 12:31:21 2021 -0400

    clean up state, fix preview

commit 8592383b8386d7adbbaeaa2c6f9c36bb121d1c65
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 22:47:56 2021 -0400

    fix incorrect import

commit ce526fd545e52142f847dbf3caec1ca37bb0650b
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 22:36:10 2021 -0400

    fix warnings

commit 572770fd3e000ce31cd3a6c5c5c91eac92cc8c5c
Merge: a9e5b6c 2677491
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 22:16:30 2021 -0400

    Merge branch 'minting' of https://github.com/Uniswap/v3-interface into minting

commit a9e5b6c5e5983e279a640886783f97c33b713125
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 22:12:43 2021 -0400

    rate updates

commit b88cab6c06176eefe5cf71f7cc3e3664d9f514ab
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 16:15:08 2021 -0400

    Use real tick and pool math

commit ed933cfd17141174c03b0bcac5f41cf75ff9b258
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Mar 28 22:42:05 2021 -0400

    naming updates

commit 50c0a0ece5c6c66a603508529c5e7a28f45db632
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Mar 28 22:36:08 2021 -0400

    WIP start usePool and useDerivedMint hooks

commit 2677491e2128e1318a0dd4307e63069e0f8e1dfe
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 22:12:43 2021 -0400

    rate updates

commit c2f59d6c61068a2bf4d34d102d5d28c9863ce982
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 16:15:08 2021 -0400

    Use real tick and pool math

commit 7d53e5c7e979be19fc5c63eb52307f302328c4eb
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Mar 28 22:42:05 2021 -0400

    naming updates

commit 9022650d391682f97e71d336021c2db2e5ea5455
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Mar 28 22:36:08 2021 -0400

    WIP start usePool and useDerivedMint hooks

* remove 1337 references

* clean up multicall

* clean up redirects/router

* cleanup

* improve useAllV3Ticks

* fix multicall

* typo

* Fix code style issues with ESLint

* preserve sticky

* reset to non fixed scroll

* fix inputs at 1

* update tests

* fix routes

* sticky sidebar

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
This commit is contained in:
Ian Lapham 2021-04-14 10:12:35 -04:00 committed by GitHub
parent c3f65e3abd
commit 0c0305a53d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 5010 additions and 3842 deletions

3
.env

@ -1,2 +1,3 @@
REACT_APP_CHAIN_ID="1"
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
SKIP_PREFLIGHT_CHECK=true

@ -1,18 +1,18 @@
describe('Add Liquidity', () => {
it('loads the two correct tokens', () => {
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab/500')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH')
})
it('does not crash if ETH is duplicated', () => {
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'ETH')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH')
})
it('token not in storage is loaded', () => {
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR')
})
@ -23,28 +23,4 @@ describe('Add Liquidity', () => {
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
})
it('redirects /add/token-token to add/token/token', () => {
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.url().should(
'contain',
'/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
)
})
it('redirects /add/WETH-token to /add/WETH-address/token', () => {
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.url().should(
'contain',
'/add/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
)
})
it('redirects /add/token-WETH to /add/token/WETH-address', () => {
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.url().should(
'contain',
'/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab'
)
})
})

@ -37,19 +37,20 @@
"@types/styled-components": "^5.1.0",
"@types/testing-library__cypress": "^5.0.5",
"@types/wcag-contrast": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^4.20.0",
"@typescript-eslint/parser": "^4.20.0",
"@typescript-eslint/eslint-plugin": "^4.1.0",
"@typescript-eslint/parser": "^4.1.0",
"@uniswap/default-token-list": "^2.0.0",
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1",
"@uniswap/sdk-core": "^1.0.8",
"@uniswap/sdk-core": "^1.0.9",
"@uniswap/token-lists": "^1.0.0-beta.19",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v2-sdk": "^1.0.6",
"@uniswap/v3-core": "^1.0.0-rc.0",
"@uniswap/v3-periphery": "^1.0.0-beta.12",
"@uniswap/v3-periphery": "^1.0.0-beta.17",
"@uniswap/v3-sdk": "^1.0.0-alpha.11",
"@web3-react/core": "^6.0.9",
"@web3-react/fortmatic-connector": "^6.0.9",
"@web3-react/injected-connector": "^6.0.7",
@ -75,8 +76,8 @@
"lightweight-charts": "^3.3.0",
"lodash.flatmap": "^4.5.0",
"luxon": "^1.25.0",
"multicodec": "^2.0.0",
"multihashes": "^3.0.1",
"multicodec": "^3.0.1",
"multihashes": "^4.0.2",
"node-vibrant": "^3.1.5",
"polished": "^3.3.2",
"prettier": "^2.2.1",
@ -92,7 +93,7 @@
"react-popper": "^2.2.3",
"react-redux": "^7.2.2",
"react-router-dom": "^5.0.0",
"react-scripts": "^4.0.2",
"react-scripts": "^4.0.3",
"react-spring": "^8.0.27",
"react-use-gesture": "^6.0.14",
"react-virtualized-auto-sizer": "^1.0.2",

@ -91,14 +91,14 @@
"selectFee": "Select Fee",
"selectLiquidityRange": "Select Liquidity Range",
"selectPool": "Select Fee Tier",
"inputTokens": "Input Tokens",
"depositAmounts": "Deposit Amounts",
"fee": "fee",
"setLimits": "Set Limits",
"percent": "Percent",
"rate": "Rate",
"currentRate": "Current {{label}} Rate:",
"currentRate": "Current {{label}} Price:",
"inactiveRangeWarning": "Your position will not be active or earn fees until the selected rates come into range.",
"invalidRangeWarning": "Invalid Range",
"invalidRangeWarning": "Invalid Range entered. Lower range must be lower than upper range.",
"connectWallet": "Connect Wallet",
"unsupportedAsset": "Unsupported Asset",
"feePool": "Fee Pool",
@ -109,6 +109,8 @@
"poolType": "Select a fee tier based on your preferred liquidity provider fee.",
"rangeWarning": "Your liquidity will only be active and earning fees when the rate of the pool is within this price range.",
"chooseLiquidityAmount": "Choose an amount of tokens to open this liquidity position. If you dont have enough tokens you can trade for them with a Swap.",
"selectPriceLimits": "Select Price Limits",
"inputTokenDynamic": "Input {{label}}"
"selectRange": "Select Liquidity Range",
"inputTokenDynamic": "Input {{label}}",
"selectStartingPrice": "Select Starting Price",
"newPoolPrice": "Select the market rate for the tokens being added."
}

165
src/abis/multicall2.json Normal file

@ -0,0 +1,165 @@
[
{
"inputs": [
{
"components": [
{ "internalType": "address", "name": "target", "type": "address" },
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
],
"internalType": "struct Multicall2.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "aggregate",
"outputs": [
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
{ "internalType": "bytes[]", "name": "returnData", "type": "bytes[]" }
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{ "internalType": "address", "name": "target", "type": "address" },
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
],
"internalType": "struct Multicall2.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "blockAndAggregate",
"outputs": [
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" },
{
"components": [
{ "internalType": "bool", "name": "success", "type": "bool" },
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
],
"internalType": "struct Multicall2.Result[]",
"name": "returnData",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }],
"name": "getBlockHash",
"outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getBlockNumber",
"outputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentBlockCoinbase",
"outputs": [{ "internalType": "address", "name": "coinbase", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentBlockDifficulty",
"outputs": [{ "internalType": "uint256", "name": "difficulty", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentBlockGasLimit",
"outputs": [{ "internalType": "uint256", "name": "gaslimit", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentBlockTimestamp",
"outputs": [{ "internalType": "uint256", "name": "timestamp", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "addr", "type": "address" }],
"name": "getEthBalance",
"outputs": [{ "internalType": "uint256", "name": "balance", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getLastBlockHash",
"outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bool", "name": "requireSuccess", "type": "bool" },
{
"components": [
{ "internalType": "address", "name": "target", "type": "address" },
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
],
"internalType": "struct Multicall2.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "tryAggregate",
"outputs": [
{
"components": [
{ "internalType": "bool", "name": "success", "type": "bool" },
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
],
"internalType": "struct Multicall2.Result[]",
"name": "returnData",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bool", "name": "requireSuccess", "type": "bool" },
{
"components": [
{ "internalType": "address", "name": "target", "type": "address" },
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
],
"internalType": "struct Multicall2.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "tryBlockAndAggregate",
"outputs": [
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" },
{
"components": [
{ "internalType": "bool", "name": "success", "type": "bool" },
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
],
"internalType": "struct Multicall2.Result[]",
"name": "returnData",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]

@ -231,7 +231,8 @@ export const ButtonText = styled(Base)`
text-decoration: underline;
}
&:hover {
text-decoration: underline;
// text-decoration: underline;
opacity: 0.9;
}
&:active {
text-decoration: underline;
@ -377,6 +378,9 @@ const Circle = styled.div`
const CheckboxWrapper = styled.div`
width: 30px;
padding: 0 10px;
position: absolute;
top: 10px;
right: 10px;
`
export function ButtonRadioChecked({ active = false, children, ...rest }: { active?: boolean } & ButtonProps) {

@ -48,5 +48,4 @@ export const BlueCard = styled(Card)`
background-color: ${({ theme }) => theme.primary5};
color: ${({ theme }) => theme.blue2};
border-radius: 12px;
width: fit-content;
`

@ -15,6 +15,8 @@ import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import { useActiveWeb3React } from '../../hooks'
import { useTranslation } from 'react-i18next'
import useTheme from '../../hooks/useTheme'
import { Lock } from 'react-feather'
import { AutoColumn } from 'components/Column'
const InputPanel = styled.div<{ hideInput?: boolean }>`
${({ theme }) => theme.flexColumnNoWrap}
@ -25,6 +27,19 @@ const InputPanel = styled.div<{ hideInput?: boolean }>`
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
`
const FixedContainer = styled.div`
width: 100%;
height: 100%;
position: absolute;
border-radius: 20px;
background-color: ${({ theme }) => theme.bg1};
opacity: 0.95;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
`
const Container = styled.div<{ hideInput: boolean }>`
border-radius: ${({ hideInput }) => (hideInput ? '12px' : '20px')};
border: 1px solid ${({ theme }) => theme.bg2};
@ -134,6 +149,7 @@ interface CurrencyInputPanelProps {
id: string
showCommonBases?: boolean
customBalanceText?: string
locked?: boolean
}
export default function CurrencyInputPanel({
@ -144,14 +160,15 @@ export default function CurrencyInputPanel({
label = 'Input',
onCurrencySelect,
currency,
disableCurrencySelect = false,
hideBalance = false,
pair = null, // used for double token logo
hideInput = false,
otherCurrency,
id,
showCommonBases,
customBalanceText,
disableCurrencySelect = false,
hideBalance = false,
pair = null, // used for double token logo
hideInput = false,
locked = false,
...rest
}: CurrencyInputPanelProps) {
const { t } = useTranslation()
@ -167,6 +184,14 @@ export default function CurrencyInputPanel({
return (
<InputPanel id={id} hideInput={hideInput} {...rest}>
{locked && (
<FixedContainer>
<AutoColumn gap="sm" justify="center">
<Lock />
<TYPE.label fontSize="12px">Single-asset deposit only, price out of range.</TYPE.label>
</AutoColumn>
</FixedContainer>
)}
<Container hideInput={hideInput}>
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableCurrencySelect}>
{!hideInput && (

@ -12,7 +12,7 @@ import { useTotalUniEarned } from '../../state/stake/hooks'
import { useAggregateUniBalance, useTokenBalance } from '../../state/wallet/hooks'
import { ExternalLink, StyledInternalLink, TYPE, UniTokenAnimated } from '../../theme'
import { computeUniCirculation } from '../../utils/computeUniCirculation'
import useUSDCPrice from '../../utils/useUSDCPrice'
import useUSDCPrice from '../../hooks/useUSDCPrice'
import { AutoColumn } from '../Column'
import { RowBetween } from '../Row'
import { Break, CardBGImage, CardNoise, CardSection, DataCard } from '../earn/styled'

@ -1,10 +1,10 @@
import React, { useState, useCallback, useEffect } from 'react'
import { OutlineCard } from 'components/Card'
import { RowBetween } from 'components/Row'
import { ButtonGray } from 'components/Button'
import { TYPE } from 'theme'
import { Input as NumericalInput } from '../NumericalInput'
import styled, { keyframes, css } from 'styled-components'
import { TYPE } from 'theme'
import { AutoColumn } from 'components/Column'
const pulse = (color: string) => keyframes`
0% {
@ -22,7 +22,7 @@ const pulse = (color: string) => keyframes`
const FocusedOutlineCard = styled(OutlineCard)<{ active?: boolean; pulsing?: boolean }>`
border-color: ${({ active, theme }) => active && theme.blue1};
padding: 8px 12px;
padding: 12px;
${({ pulsing, theme }) =>
pulsing &&
@ -33,25 +33,22 @@ const FocusedOutlineCard = styled(OutlineCard)<{ active?: boolean; pulsing?: boo
const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>`
background-color: ${({ theme }) => theme.bg0};
text-align: ${({ usePercent }) => (usePercent ? 'right' : 'center')};
text-align: left;
margin-right: 2px;
`
const ContentWrapper = styled(RowBetween)`
padding: 0 8px;
width: 70%;
width: 92%;
`
interface StepCounterProps {
value: string
onUserInput: (value: string) => void
onIncrement?: () => void
onDecrement?: () => void
usePercent?: boolean
prependSymbol?: string | undefined
label?: string
width?: string
}
const StepCounter = ({ value, onUserInput, usePercent = false, prependSymbol }: StepCounterProps) => {
const StepCounter = ({ value, onUserInput, label, width }: StepCounterProps) => {
// for focus state, styled components doesnt let you select input parent container
const [active, setActive] = useState(false)
@ -85,20 +82,9 @@ const StepCounter = ({ value, onUserInput, usePercent = false, prependSymbol }:
}
}, [localValue, useLocalValue, value])
const handleDeccrement = useCallback(() => {
localValue && setLocalValue((parseFloat(localValue) * 0.997).toString())
}, [localValue])
const handleIncrement = useCallback(() => {
localValue && setLocalValue((parseFloat(localValue) * 1.003).toString())
}, [localValue])
return (
<FocusedOutlineCard pulsing={pulsing} active={active} onFocus={handleOnFocus} onBlur={handleOnBlur}>
<RowBetween>
<ButtonGray padding="2px 0px" borderRadius="8px" onClick={handleDeccrement} width="50px">
<TYPE.label>-</TYPE.label>
</ButtonGray>
<FocusedOutlineCard pulsing={pulsing} active={active} onFocus={handleOnFocus} onBlur={handleOnBlur} width={width}>
<AutoColumn gap="md">
<ContentWrapper>
<StyledInput
className="rate-input-0"
@ -107,15 +93,10 @@ const StepCounter = ({ value, onUserInput, usePercent = false, prependSymbol }:
onUserInput={(val) => {
setLocalValue(val)
}}
prependSymbol={prependSymbol}
usePercent={usePercent}
/>
{usePercent && <TYPE.main>%</TYPE.main>}
</ContentWrapper>
<ButtonGray padding="2px 0px" borderRadius="8px" onClick={handleIncrement} width="50px">
<TYPE.label>+</TYPE.label>
</ButtonGray>
</RowBetween>
{label && <TYPE.label fontSize="12px">{label}</TYPE.label>}
</AutoColumn>
</FocusedOutlineCard>
)
}

@ -1,6 +1,6 @@
import React from 'react'
import { Text } from 'rebass'
import { ChainId, Currency, currencyEquals, ETHER, Token } from '@uniswap/sdk-core'
import { ChainId, Currency, currencyEquals, Token, ETHER } from '@uniswap/sdk-core'
import styled from 'styled-components'
import { SUGGESTED_BASES } from '../../constants'

@ -79,7 +79,9 @@ export function CurrencySearch({
// if they input an address, use it
const isAddressSearch = isAddress(debouncedQuery)
const searchToken = useToken(debouncedQuery)
const searchTokenIsAdded = useIsUserAddedToken(searchToken)
useEffect(() => {

@ -18,8 +18,8 @@ import useAddTokenToMetamask from 'hooks/useAddTokenToMetamask'
const Wrapper = styled.div`
width: 100%;
`
const Section = styled(AutoColumn)`
padding: 24px;
const Section = styled(AutoColumn)<{ inline?: boolean }>`
padding: ${({ inline }) => (inline ? '0' : '24px')};
`
const BottomSection = styled(Section)`
@ -28,8 +28,8 @@ const BottomSection = styled(Section)`
border-bottom-right-radius: 20px;
`
const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0;
const ConfirmedIcon = styled(ColumnCenter)<{ inline?: boolean }>`
padding: ${({ inline }) => (inline ? '20px 0' : '60px 0;')};
`
const StyledLogo = styled.img`
@ -38,19 +38,29 @@ const StyledLogo = styled.img`
margin-left: 6px;
`
function ConfirmationPendingContent({ onDismiss, pendingText }: { onDismiss: () => void; pendingText: string }) {
export function ConfirmationPendingContent({
onDismiss,
pendingText,
inline,
}: {
onDismiss: () => void
pendingText: string
inline?: boolean // not in modal
}) {
return (
<Wrapper>
<Section>
<RowBetween>
<div />
<CloseIcon onClick={onDismiss} />
</RowBetween>
<ConfirmedIcon>
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
<AutoColumn gap="md">
{!inline && (
<RowBetween>
<div />
<CloseIcon onClick={onDismiss} />
</RowBetween>
)}
<ConfirmedIcon inline={inline}>
<CustomLightSpinner src={Circle} alt="loader" size={inline ? '40px' : '90px'} />
</ConfirmedIcon>
<AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={500} fontSize={20}>
<Text fontWeight={500} fontSize={20} textAlign="center">
Waiting For Confirmation
</Text>
<AutoColumn gap="12px" justify={'center'}>
@ -62,21 +72,23 @@ function ConfirmationPendingContent({ onDismiss, pendingText }: { onDismiss: ()
Confirm this transaction in your wallet
</Text>
</AutoColumn>
</Section>
</AutoColumn>
</Wrapper>
)
}
function TransactionSubmittedContent({
export function TransactionSubmittedContent({
onDismiss,
chainId,
hash,
currencyToAdd,
inline,
}: {
onDismiss: () => void
hash: string | undefined
chainId: ChainId
currencyToAdd?: Currency | undefined
inline?: boolean // not in modal
}) {
const theme = useContext(ThemeContext)
@ -86,16 +98,18 @@ function TransactionSubmittedContent({
return (
<Wrapper>
<Section>
<RowBetween>
<div />
<CloseIcon onClick={onDismiss} />
</RowBetween>
<ConfirmedIcon>
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
<Section inline={inline}>
{!inline && (
<RowBetween>
<div />
<CloseIcon onClick={onDismiss} />
</RowBetween>
)}
<ConfirmedIcon inline={inline}>
<ArrowUpCircle strokeWidth={0.5} size={inline ? '40px' : '90px'} color={theme.primary1} />
</ConfirmedIcon>
<AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={500} fontSize={20}>
<Text fontWeight={500} fontSize={20} textAlign="center">
Transaction Submitted
</Text>
{chainId && hash && (
@ -121,7 +135,7 @@ function TransactionSubmittedContent({
)}
<ButtonPrimary onClick={onDismiss} style={{ margin: '20px 0 0 0' }}>
<Text fontWeight={500} fontSize={20}>
Close
{inline ? 'Return' : 'Close'}
</Text>
</ButtonPrimary>
</AutoColumn>
@ -145,7 +159,7 @@ export function ConfirmationModalContent({
<Wrapper>
<Section>
<RowBetween>
<Text fontWeight={500} fontSize={20}>
<Text fontWeight={500} fontSize={16}>
{title}
</Text>
<CloseIcon onClick={onDismiss} />

@ -13,8 +13,8 @@ import { currencyId } from '../../utils/currencyId'
import { Break, CardNoise, CardBGImage } from './styled'
import { unwrappedToken } from '../../utils/wrappedCurrency'
import { useTotalSupply } from '../../data/TotalSupply'
import { usePair } from '../../data/Reserves'
import useUSDCPrice from '../../utils/useUSDCPrice'
import { usePair } from '../../data/V2'
import useUSDCPrice from '../../hooks/useUSDCPrice'
import { BIG_INT_SECONDS_IN_WEEK } from '../../constants'
const StatContainer = styled.div`

@ -4,6 +4,14 @@ import JSBI from 'jsbi'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
export const MULTICALL_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
[ChainId.KOVAN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
[ChainId.RINKEBY]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
[ChainId.GÖRLI]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
}
export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
@ -12,7 +20,7 @@ export { PRELOADED_PROPOSALS } from './proposals'
// a list of tokens by chain
type ChainTokenList = {
readonly [chainId in ChainId]: Token[]
readonly [chainId in ChainId | 1337]: Token[]
}
export const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin')
@ -57,6 +65,7 @@ const WETH_ONLY: ChainTokenList = {
[ChainId.RINKEBY]: [WETH9[ChainId.RINKEBY]],
[ChainId.GÖRLI]: [WETH9[ChainId.GÖRLI]],
[ChainId.KOVAN]: [WETH9[ChainId.KOVAN]],
[1337]: [WETH9[ChainId.KOVAN]],
}
// used to construct intermediary pairs for trading

@ -102,6 +102,23 @@
"internalType": "bytes32",
"name": "blockHash",
"type": "bytes32"
},
{
"components": [
{
"internalType": "bool",
"name": "success",
"type": "bool"
},
{
"internalType": "bytes",
"name": "returnData",
"type": "bytes"
}
],
"internalType": "struct Multicall2.Result[]",
"name": "returnData",
"type": "tuple[]"
}
],
"stateMutability": "view",

@ -1,12 +0,0 @@
import { ChainId } from '@uniswap/sdk-core'
import MULTICALL_ABI from './abi.json'
const MULTICALL_NETWORKS: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441',
[ChainId.ROPSTEN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
[ChainId.KOVAN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
[ChainId.RINKEBY]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
[ChainId.GÖRLI]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
}
export { MULTICALL_ABI, MULTICALL_NETWORKS }

@ -17,6 +17,14 @@
"decimals": 6,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4922a015c4407F87432B179bb209e125432E4a2A/logo.png"
},
{
"name": "Grump Cat",
"address": "0x93B2FfF814FCaEFFB01406e80B4Ecd89Ca6A021b",
"symbol": "GRUMPY",
"decimals": 9,
"chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4922a015c4407F87432B179bb209e125432E4a2A/logo.png"
}
]
}

@ -2,40 +2,40 @@ import { ChainId } from '@uniswap/sdk-core'
export const FACTORY_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '',
[ChainId.ROPSTEN]: '0xb31b9A7b331eA8993bdfC67c650eDbfc9256eC62',
[ChainId.RINKEBY]: '0xD8A6adFB40Ba3B3CdA9F985BF1fdbDc0c1d7591e',
[ChainId.GÖRLI]: '0xb31b9A7b331eA8993bdfC67c650eDbfc9256eC62',
[ChainId.KOVAN]: '',
}
export const TICK_LENS_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '',
[ChainId.ROPSTEN]: '0x8E984b597F19E8D0FDd0b5bAfDb1d0ae4386455f',
[ChainId.RINKEBY]: '0xB1c59e8Ae4B72f63a5a9CB9c25A9253096A4b126',
[ChainId.GÖRLI]: '0x8E984b597F19E8D0FDd0b5bAfDb1d0ae4386455f',
[ChainId.KOVAN]: '',
}
export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '',
[ChainId.ROPSTEN]: '0x29e4bF3bFD649b807B4C752c01023E535094F6Bc',
[ChainId.RINKEBY]: '0xee9e30637f84Bbf929042A9118c6E20023dab833',
[ChainId.GÖRLI]: '0x29e4bF3bFD649b807B4C752c01023E535094F6Bc',
[ChainId.KOVAN]: '',
}
export const NONFUNGIBLE_TOKEN_POSITION_DESCRIPTOR_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '',
[ChainId.ROPSTEN]: '0xa0588c89Fe967e66533aB1A0504C30989f90156f',
[ChainId.RINKEBY]: '0x3431b9Ed12e3204bC6f7039e1c576417B70fdD67',
[ChainId.GÖRLI]: '0xa0588c89Fe967e66533aB1A0504C30989f90156f',
[ChainId.KOVAN]: '',
}
export const SWAP_ROUTER_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '',
[ChainId.ROPSTEN]: '0x71bB3d0e63f2Fa2A5d04d54267211f4Caef7062e',
[ChainId.RINKEBY]: '0xa0588c89Fe967e66533aB1A0504C30989f90156f',
[ChainId.GÖRLI]: '0x71bB3d0e63f2Fa2A5d04d54267211f4Caef7062e',
[ChainId.KOVAN]: '',
}
@ -43,7 +43,7 @@ export const SWAP_ROUTER_ADDRESSES: { [chainId in ChainId]: string } = {
export const V2_MIGRATOR_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '',
[ChainId.RINKEBY]: '0xb31b9A7b331eA8993bdfC67c650eDbfc9256eC62',
[ChainId.GÖRLI]: '0xee9e30637f84Bbf929042A9118c6E20023dab833',
[ChainId.KOVAN]: '',
}

98
src/data/Pools.ts Normal file

@ -0,0 +1,98 @@
import { Tick } from '@uniswap/v3-sdk'
import { ZERO_ADDRESS } from './../constants/index'
import { Currency } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { useActiveWeb3React } from '../hooks'
import { useSingleCallResult } from '../state/multicall/hooks'
import { wrappedCurrency } from '../utils/wrappedCurrency'
import { Pool, FeeAmount, computePoolAddress } from '@uniswap/v3-sdk'
import { useV3Factory, useV3Pool } from 'hooks/useContract'
import { FACTORY_ADDRESSES } from 'constants/v3'
import { useAllV3Ticks } from 'hooks/useAllV3Ticks'
export enum PoolState {
LOADING,
NOT_EXISTS,
EXISTS,
INVALID,
}
export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?: FeeAmount): [PoolState, Pool | null] {
const { chainId } = useActiveWeb3React()
const factoryContract = useV3Factory()
const tokenA = wrappedCurrency(currencyA, chainId)
const tokenB = wrappedCurrency(currencyB, chainId)
// sorted version
const [token0, token1] = useMemo(
() =>
tokenA && tokenB && !tokenA.equals(tokenB)
? tokenA.sortsBefore(tokenB)
? [tokenA, tokenB]
: [tokenB, tokenA]
: [undefined, undefined],
[tokenA, tokenB]
)
// fetch all generated addresses for pools
const poolAddress = useMemo(() => {
try {
return chainId && tokenA && tokenB && feeAmount && !tokenA.equals(tokenB)
? computePoolAddress({
factoryAddress: FACTORY_ADDRESSES[chainId],
tokenA,
tokenB,
fee: feeAmount,
})
: undefined
} catch {
return undefined
}
}, [chainId, feeAmount, tokenA, tokenB])
const poolContract = useV3Pool(poolAddress)
// check factory if pools exists
const addressParams = token0 && token1 && feeAmount ? [token0.address, token1.address, feeAmount] : undefined
const addressFromFactory = useSingleCallResult(addressParams ? factoryContract : undefined, 'getPool', addressParams)
// attempt to fetch pool metadata
const slot0Datas = useSingleCallResult(poolContract, 'slot0')
// fetch additional data to instantiate pools
const liquidityDatas = useSingleCallResult(poolContract, 'liquidity')
const { result: slot0, loading: slot0Loading } = slot0Datas
const { result: liquidityResult, loading: liquidityLoading } = liquidityDatas
const { result: addressesResult, loading: addressesLoading } = addressFromFactory
const liquidity = liquidityResult?.[0]
const poolAddressFromFactory = addressesResult?.[0]
// fetch tick data for pool
const { tickData, loading: tickLoading } = useAllV3Ticks(token0, token1, feeAmount)
// still loading data
if (slot0Loading || addressesLoading || liquidityLoading || tickLoading) return [PoolState.LOADING, null]
// invalid pool setup
if (!tokenA || !tokenB || !feeAmount || tokenA.equals(tokenB)) return [PoolState.INVALID, null]
// pool has not been created or not initialized yet
if (poolAddressFromFactory === ZERO_ADDRESS || !slot0 || !liquidity || slot0.sqrtPriceX96 === 0) {
return [PoolState.NOT_EXISTS, null]
}
const tickList: Tick[] = tickData
.map((tick) => {
return new Tick({
index: tick.tick,
liquidityGross: tick.liquidityGross,
liquidityNet: tick.liquidityNet,
})
})
.sort((tickA, tickB) => (tickA.index > tickB.index ? 1 : -1))
return [PoolState.EXISTS, new Pool(tokenA, tokenB, feeAmount, slot0.sqrtPriceX96, liquidity, slot0.tick, tickList)]
}

@ -1,5 +1,4 @@
import { Pair } from '@uniswap/v2-sdk'
import { TokenAmount, Currency } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { Interface } from '@ethersproject/abi'
@ -7,6 +6,7 @@ import { useActiveWeb3React } from '../hooks'
import { useMultipleContractSingleData } from '../state/multicall/hooks'
import { wrappedCurrency } from '../utils/wrappedCurrency'
import { Currency, TokenAmount } from '@uniswap/sdk-core'
const PAIR_INTERFACE = new Interface(IUniswapV2PairABI)

@ -5,7 +5,7 @@ import flatMap from 'lodash.flatmap'
import { useMemo } from 'react'
import { BASES_TO_CHECK_TRADES_AGAINST, CUSTOM_BASES, BETTER_TRADE_LESS_HOPS_THRESHOLD } from '../constants'
import { PairState, usePairs } from '../data/Reserves'
import { PairState, usePairs } from '../data/V2'
import { wrappedCurrency } from '../utils/wrappedCurrency'
import { useActiveWeb3React } from './index'

@ -1,6 +1,9 @@
import { Token } from '@uniswap/sdk-core'
import { FeeAmount, TICK_SPACINGS } from '@uniswap/v3-sdk'
import { ZERO_ADDRESS } from '../constants'
import { useMemo } from 'react'
import { Result, useSingleContractMultipleData } from 'state/multicall/hooks'
import { useTickLens } from './useContract'
import { Result, useSingleCallResult, useSingleContractMultipleData } from 'state/multicall/hooks'
import { useTickLens, useV3Factory } from './useContract'
// the following should probably all be from the sdk, just mocking it for now
function MIN_TICK(tickSpacing: number) {
@ -25,8 +28,9 @@ interface TickData {
}
export function useAllV3Ticks(
poolAddress: string,
tickSpacing: number
token0: Token | undefined,
token1: Token | undefined,
feeAmount: FeeAmount | undefined
): {
loading: boolean
syncing: boolean
@ -34,22 +38,35 @@ export function useAllV3Ticks(
valid: boolean
tickData: TickData[]
} {
const tickLens = useTickLens()
const tickSpacing = useMemo(() => (feeAmount ? TICK_SPACINGS[feeAmount] : undefined), [feeAmount])
const minIndex = useMemo(() => bitmapIndex(MIN_TICK(tickSpacing), tickSpacing), [tickSpacing])
const maxIndex = useMemo(() => bitmapIndex(MAX_TICK(tickSpacing), tickSpacing), [tickSpacing])
const minIndex = useMemo(() => (tickSpacing ? bitmapIndex(MIN_TICK(tickSpacing), tickSpacing) : undefined), [
tickSpacing,
])
const maxIndex = useMemo(() => (tickSpacing ? bitmapIndex(MAX_TICK(tickSpacing), tickSpacing) : undefined), [
tickSpacing,
])
const tickLensArgs = useMemo(
// fetch the pool address
const factoryContract = useV3Factory()
const addressParams = token0 && token1 && feeAmount ? [token0.address, token1.address, feeAmount] : undefined
const poolAddress = useSingleCallResult(addressParams ? factoryContract : undefined, 'getPool', addressParams)
.result?.[0]
const tickLensArgs: [string, number][] = useMemo(
() =>
new Array(maxIndex - minIndex + 1)
.fill(0)
.map((_, i) => i + minIndex)
.map((wordIndex) => [poolAddress, wordIndex]),
maxIndex && minIndex && poolAddress && poolAddress !== ZERO_ADDRESS
? new Array(maxIndex - minIndex + 1)
.fill(0)
.map((_, i) => i + minIndex)
.map((wordIndex) => [poolAddress, wordIndex])
: [],
[minIndex, maxIndex, poolAddress]
)
const tickLens = useTickLens()
const callStates = useSingleContractMultipleData(
tickLens,
tickLensArgs.length > 0 ? tickLens : undefined,
'getPopulatedTicksInWord',
tickLensArgs,
REFRESH_FREQUENCY,

@ -1,3 +1,4 @@
import { UniswapV3Pool } from './../types/v3/UniswapV3Pool.d'
import { Contract } from '@ethersproject/contracts'
import { abi as GOVERNANCE_ABI } from '@uniswap/governance/build/GovernorAlpha.json'
import { abi as UNI_ABI } from '@uniswap/governance/build/Uni.json'
@ -8,6 +9,7 @@ import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.
import { abi as NFTPositionManagerABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import { abi as V3FactoryABI } from '@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json'
import { abi as TickLensABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/TickLens.sol/TickLens.json'
import { abi as V3PoolABI } from '@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json'
import ARGENT_WALLET_DETECTOR_ABI from 'abis/argent-wallet-detector.json'
import ENS_PUBLIC_RESOLVER_ABI from 'abis/ens-public-resolver.json'
@ -24,8 +26,9 @@ import {
MERKLE_DISTRIBUTOR_ADDRESS,
V1_MIGRATOR_ADDRESS,
UNI,
MULTICALL_ADDRESSES,
} from 'constants/index'
import { MULTICALL_ABI, MULTICALL_NETWORKS } from 'constants/multicall'
import MULTICALL_ABI from 'abis/multicall2.json'
import { V1_EXCHANGE_ABI, V1_FACTORY_ABI, V1_FACTORY_ADDRESSES } from 'constants/v1'
import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, FACTORY_ADDRESSES, TICK_LENS_ADDRESSES } from 'constants/v3'
import { useMemo } from 'react'
@ -35,7 +38,7 @@ import { getContract } from 'utils'
import { useActiveWeb3React } from './index'
// returns null on errors
function useContract(address: string | undefined, ABI: any, withSignerIfPossible = true): Contract | null {
export function useContract(address: string | undefined, ABI: any, withSignerIfPossible = true): Contract | null {
const { library, account } = useActiveWeb3React()
return useMemo(() => {
@ -65,10 +68,10 @@ export function useV1ExchangeContract(address?: string, withSignerIfPossible?: b
export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: boolean): Contract | null {
return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible)
}
export function useWETHContract(withSignerIfPossible?: boolean): Contract | null {
const { chainId } = useActiveWeb3React()
return useContract(chainId ? WETH9[chainId].address : undefined, WETH_ABI, withSignerIfPossible)
const address = chainId && chainId in WETH9 ? WETH9[chainId].address : undefined
return useContract(address, WETH_ABI, withSignerIfPossible)
}
export function useArgentWalletDetectorContract(): Contract | null {
@ -110,7 +113,7 @@ export function usePairContract(pairAddress?: string, withSignerIfPossible?: boo
export function useMulticallContract(): Contract | null {
const { chainId } = useActiveWeb3React()
return useContract(chainId && MULTICALL_NETWORKS[chainId], MULTICALL_ABI, false)
return useContract(chainId && MULTICALL_ADDRESSES[chainId], MULTICALL_ABI, false)
}
export function useMerkleDistributorContract(): Contract | null {
@ -152,6 +155,10 @@ export function useV3Factory(): UniswapV3Factory | null {
return useContract(address, V3FactoryABI) as UniswapV3Factory | null
}
export function useV3Pool(address: string | undefined): UniswapV3Pool | null {
return useContract(address, V3PoolABI) as UniswapV3Pool | null
}
export function useTickLens(): TickLens | null {
const { chainId } = useActiveWeb3React()
const address = chainId ? TICK_LENS_ADDRESSES[chainId] : undefined

@ -0,0 +1,13 @@
import { Token, Price } from '@uniswap/sdk-core'
import { tickToPrice } from '@uniswap/v3-sdk'
export function getTickToPrice(
baseToken: Token | undefined,
quoteToken: Token | undefined,
tick: number | undefined
): Price | undefined {
if (!baseToken || !quoteToken || !tick) {
return undefined
}
return tickToPrice(baseToken, quoteToken, tick)
}

@ -2,9 +2,9 @@ import { ChainId, Currency, currencyEquals, Price, WETH9 } from '@uniswap/sdk-co
import { JSBI } from '@uniswap/v2-sdk'
import { useMemo } from 'react'
import { USDC } from '../constants'
import { PairState, usePairs } from '../data/Reserves'
import { PairState, usePairs } from '../data/V2'
import { useActiveWeb3React } from '../hooks'
import { wrappedCurrency } from './wrappedCurrency'
import { wrappedCurrency } from '../utils/wrappedCurrency'
/**
* Returns the price in USDC of the input currency

@ -12,7 +12,7 @@ import useTheme from 'hooks/useTheme'
import { AlertOctagon } from 'react-feather'
import { ToggleWrapper, ToggleElement } from 'components/Toggle/MultiToggle'
import { useTranslation } from 'react-i18next'
import { Price, Percent, Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { Price, Currency, CurrencyAmount } from '@uniswap/sdk-core'
const Wrapper = styled(AutoColumn)`
padding: 1rem 0;
@ -27,7 +27,6 @@ export function ConfirmContent({
price?: Price
currencies: { [field in Field]?: Currency }
parsedAmounts: { [field in Field]?: CurrencyAmount }
poolTokenPercentage?: Percent
onAdd: () => void
}) {
const currencyA: Currency | undefined = currencies[Field.CURRENCY_A]

@ -0,0 +1,140 @@
import React, { useMemo } from 'react'
import { RowBetween, RowFixed } from '../../components/Row'
import CurrencyLogo from '../../components/CurrencyLogo'
import { Field } from '../../state/mint/actions'
import { TYPE } from '../../theme'
import { AutoColumn } from 'components/Column'
import Card, { DarkGreyCard } from 'components/Card'
import styled from 'styled-components'
import { Break } from 'components/earn/styled'
import { useTranslation } from 'react-i18next'
import { Currency, CurrencyAmount, Price } from '@uniswap/sdk-core'
import { Position } from '@uniswap/v3-sdk'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import { wrappedCurrency } from 'utils/wrappedCurrency'
import { useActiveWeb3React } from 'hooks'
// import QuestionHelper from 'components/QuestionHelper'
// import { WarningBadge } from 'components/Badge/Badge.stories'
// import { AlertCircle } from 'react-feather'
// import useTheme from 'hooks/useTheme'
const Wrapper = styled.div`
padding: 20px;
min-width: 460px;
`
const Badge = styled(Card)<{ inRange?: boolean }>`
width: fit-content;
font-size: 14px;
font-weight: 500;
border-radius: 8px;
padding: 4px 6px;
background-color: ${({ inRange, theme }) => (inRange ? theme.green1 : theme.yellow2)};
`
export function Review({
position,
currencies,
parsedAmounts,
priceLower,
priceUpper,
outOfRange,
}: {
position?: Position
currencies: { [field in Field]?: Currency }
parsedAmounts: { [field in Field]?: CurrencyAmount }
priceLower?: Price
priceUpper?: Price
outOfRange: boolean
}) {
const { t } = useTranslation()
const { chainId } = useActiveWeb3React()
// const theme = useTheme()
const currencyA: Currency | undefined = currencies[Field.CURRENCY_A]
const currencyB: Currency | undefined = currencies[Field.CURRENCY_B]
// formatted with tokens
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
chainId,
currencyA,
currencyB,
])
return (
<Wrapper>
<AutoColumn gap="lg">
<RowBetween>
<RowFixed>
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} size={24} margin={true} />
<TYPE.label ml="10px" fontSize="24px">
{currencyA?.symbol} / {currencyB?.symbol}
</TYPE.label>
</RowFixed>
<Badge inRange={!outOfRange}>{outOfRange ? 'Out of range' : 'In Range'}</Badge>
</RowBetween>
{position && tokenA && tokenB && (
<DarkGreyCard>
<AutoColumn gap="md">
<TYPE.label>Deposit Amounts</TYPE.label>
{parsedAmounts[Field.CURRENCY_A] && (
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyA} />
<TYPE.label ml="8px">{currencyA?.symbol}</TYPE.label>
</RowFixed>
<RowFixed>
<TYPE.label mr="8px">{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</TYPE.label>
</RowFixed>
</RowBetween>
)}
{parsedAmounts[Field.CURRENCY_B] && (
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyB} />
<TYPE.label ml="8px">{currencyB?.symbol}</TYPE.label>
</RowFixed>
<RowFixed>
<TYPE.label mr="8px">{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</TYPE.label>
</RowFixed>
</RowBetween>
)}
<Break />
<RowBetween>
<TYPE.label>{t('feePool')}</TYPE.label>
<TYPE.label>{position?.pool?.fee / 10000}%</TYPE.label>
</RowBetween>
<RowBetween>
<TYPE.label>Current Price</TYPE.label>
<TYPE.label>{`1 ${currencyA?.symbol} = ${position?.pool
?.priceOf(position.pool?.token0.equals(tokenA) ? position.pool?.token0 : position.pool?.token1)
.toSignificant(6)} ${position.pool.token1?.symbol}`}</TYPE.label>
</RowBetween>
<RowBetween>
<TYPE.label>Active Range</TYPE.label>
<TYPE.label>{`1 ${
position.pool?.token0.equals(tokenA) ? currencyA?.symbol : currencyB?.symbol
} = ${priceLower?.toSignificant(4)} ${priceUpper?.toSignificant(6)} ${
position.pool?.token0.equals(tokenA) ? currencyB?.symbol : currencyA?.symbol
}`}</TYPE.label>
</RowBetween>
</AutoColumn>
</DarkGreyCard>
)}
{/* <YellowCard>
<AutoColumn gap="md">
<RowBetween>
<TYPE.label color={theme.text2}>Efficiency Comparison</TYPE.label>
<AlertCircle stroke={theme.text2} />
</RowBetween>
<TYPE.label fontWeight={400} color={theme.text2}>
This liquidity position has an increased capital efficiency relative to an unbounded price limit.
</TYPE.label>
</AutoColumn>
</YellowCard> */}
</AutoColumn>
</Wrapper>
)
}
export default Review

File diff suppressed because it is too large Load Diff

@ -2,32 +2,16 @@ import React from 'react'
import { Redirect, RouteComponentProps } from 'react-router-dom'
import AddLiquidity from './index'
export function RedirectToAddLiquidity() {
return <Redirect to="/add/" />
}
const OLD_PATH_STRUCTURE = /^(0x[a-fA-F0-9]{40})-(0x[a-fA-F0-9]{40})$/
export function RedirectOldAddLiquidityPathStructure(props: RouteComponentProps<{ currencyIdA: string }>) {
const {
match: {
params: { currencyIdA },
},
} = props
const match = currencyIdA.match(OLD_PATH_STRUCTURE)
if (match?.length) {
return <Redirect to={`/add/${match[1]}/${match[2]}`} />
}
return <AddLiquidity {...props} />
}
export function RedirectDuplicateTokenIds(props: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
export function RedirectDuplicateTokenIds(
props: RouteComponentProps<{ currencyIdA: string; currencyIdB: string; feeAmount?: string }>
) {
const {
match: {
params: { currencyIdA, currencyIdB },
},
} = props
if (currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) {
if (currencyIdA && currencyIdB && currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) {
return <Redirect to={`/add/${currencyIdA}`} />
}
return <AddLiquidity {...props} />

@ -2,6 +2,7 @@ import styled from 'styled-components'
import { AutoColumn } from 'components/Column'
import CurrencyInputPanel from 'components/CurrencyInputPanel'
import { DarkGreyCard } from 'components/Card'
import Input from 'components/NumericalInput'
export const ScrollablePage = styled.div`
position: relative;
@ -10,18 +11,21 @@ export const ScrollablePage = styled.div`
`
export const ScrollableContent = styled.div`
margin-right: 24px;
margin-right: 16px;
`
export const FixedPreview = styled.div`
position: relative;
padding: 8px;
padding: 16px;
width: 260px;
height: fit-content;
margin-top: 42px;
background: ${({ theme }) => theme.bg0};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 12px;
position: sticky;
top: 120px;
`
export const DynamicSection = styled(AutoColumn)<{ disabled?: boolean }>`
@ -30,7 +34,7 @@ export const DynamicSection = styled(AutoColumn)<{ disabled?: boolean }>`
`
export const CurrencyDropdown = styled(CurrencyInputPanel)`
width: 49%;
width: 48.5%;
`
export const PreviewCard = styled(DarkGreyCard)<{ disabled?: boolean }>`
@ -42,3 +46,10 @@ export const PreviewCard = styled(DarkGreyCard)<{ disabled?: boolean }>`
align-items: center;
justify-content: center;
`
export const StyledInput = styled(Input)`
background-color: ${({ theme }) => theme.bg0};
text-align: left;
font-size: 18px;
width: 100%;
`

@ -0,0 +1,67 @@
import { Currency, CurrencyAmount, Fraction, Percent } from '@uniswap/sdk-core'
import React from 'react'
import { Text } from 'rebass'
import { ButtonPrimary } from '../../components/Button'
import { RowBetween, RowFixed } from '../../components/Row'
import CurrencyLogo from '../../components/CurrencyLogo'
import { Field } from '../../state/mint/actions'
import { TYPE } from '../../theme'
export function ConfirmAddModalBottom({
noLiquidity,
price,
currencies,
parsedAmounts,
poolTokenPercentage,
onAdd,
}: {
noLiquidity?: boolean
price?: Fraction
currencies: { [field in Field]?: Currency }
parsedAmounts: { [field in Field]?: CurrencyAmount }
poolTokenPercentage?: Percent
onAdd: () => void
}) {
return (
<>
<RowBetween>
<TYPE.body>{currencies[Field.CURRENCY_A]?.symbol} Deposited</TYPE.body>
<RowFixed>
<CurrencyLogo currency={currencies[Field.CURRENCY_A]} style={{ marginRight: '8px' }} />
<TYPE.body>{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</TYPE.body>
</RowFixed>
</RowBetween>
<RowBetween>
<TYPE.body>{currencies[Field.CURRENCY_B]?.symbol} Deposited</TYPE.body>
<RowFixed>
<CurrencyLogo currency={currencies[Field.CURRENCY_B]} style={{ marginRight: '8px' }} />
<TYPE.body>{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</TYPE.body>
</RowFixed>
</RowBetween>
<RowBetween>
<TYPE.body>Rates</TYPE.body>
<TYPE.body>
{`1 ${currencies[Field.CURRENCY_A]?.symbol} = ${price?.toSignificant(4)} ${
currencies[Field.CURRENCY_B]?.symbol
}`}
</TYPE.body>
</RowBetween>
<RowBetween style={{ justifyContent: 'flex-end' }}>
<TYPE.body>
{`1 ${currencies[Field.CURRENCY_B]?.symbol} = ${price?.invert().toSignificant(4)} ${
currencies[Field.CURRENCY_A]?.symbol
}`}
</TYPE.body>
</RowBetween>
<RowBetween>
<TYPE.body>Share of Pool:</TYPE.body>
<TYPE.body>{noLiquidity ? '100' : poolTokenPercentage?.toSignificant(4)}%</TYPE.body>
</RowBetween>
<ButtonPrimary style={{ margin: '20px 0 0 0' }} onClick={onAdd}>
<Text fontWeight={500} fontSize={20}>
{noLiquidity ? 'Create Pool & Supply' : 'Confirm Supply'}
</Text>
</ButtonPrimary>
</>
)
}

@ -0,0 +1,52 @@
import { Currency, Percent, Price } from '@uniswap/sdk-core'
import React, { useContext } from 'react'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components'
import { AutoColumn } from '../../components/Column'
import { AutoRow } from '../../components/Row'
import { ONE_BIPS } from '../../constants'
import { Field } from '../../state/mint/actions'
import { TYPE } from '../../theme'
export function PoolPriceBar({
currencies,
noLiquidity,
poolTokenPercentage,
price,
}: {
currencies: { [field in Field]?: Currency }
noLiquidity?: boolean
poolTokenPercentage?: Percent
price?: Price
}) {
const theme = useContext(ThemeContext)
return (
<AutoColumn gap="md">
<AutoRow justify="space-around" gap="4px">
<AutoColumn justify="center">
<TYPE.black>{price?.toSignificant(6) ?? '-'}</TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
{currencies[Field.CURRENCY_B]?.symbol} per {currencies[Field.CURRENCY_A]?.symbol}
</Text>
</AutoColumn>
<AutoColumn justify="center">
<TYPE.black>{price?.invert()?.toSignificant(6) ?? '-'}</TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
{currencies[Field.CURRENCY_A]?.symbol} per {currencies[Field.CURRENCY_B]?.symbol}
</Text>
</AutoColumn>
<AutoColumn justify="center">
<TYPE.black>
{noLiquidity && price
? '100'
: (poolTokenPercentage?.lessThan(ONE_BIPS) ? '<0.01' : poolTokenPercentage?.toFixed(2)) ?? '0'}
%
</TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
Share of Pool
</Text>
</AutoColumn>
</AutoRow>
</AutoColumn>
)
}

@ -0,0 +1,485 @@
import { BigNumber } from '@ethersproject/bignumber'
import { TransactionResponse } from '@ethersproject/providers'
import { Currency, currencyEquals, ETHER, TokenAmount, WETH9 } from '@uniswap/sdk-core'
import React, { useCallback, useContext, useState } from 'react'
import { Plus } from 'react-feather'
import ReactGA from 'react-ga'
import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components'
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
import { BlueCard, LightCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import DoubleCurrencyLogo from '../../components/DoubleLogo'
import { AddRemoveTabs } from '../../components/NavigationTabs'
import { MinimalPositionCard } from '../../components/PositionCard'
import Row, { RowBetween, RowFlat } from '../../components/Row'
import { ROUTER_ADDRESS } from '../../constants'
import { PairState } from '../../data/V2'
import { useActiveWeb3React } from '../../hooks'
import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { useWalletModalToggle } from '../../state/application/hooks'
import { Field } from '../../state/mint/actions'
import { useMintActionHandlers, useMintState } from '../../state/mint/hooks'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserSlippageTolerance } from '../../state/user/hooks'
import { TYPE } from '../../theme'
import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { wrappedCurrency } from '../../utils/wrappedCurrency'
import AppBody from '../AppBody'
import { Dots, Wrapper } from '../Pool/styleds'
import { ConfirmAddModalBottom } from './ConfirmAddModalBottom'
import { currencyId } from '../../utils/currencyId'
import { PoolPriceBar } from './PoolPriceBar'
import { useIsTransactionUnsupported } from 'hooks/Trades'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import { useV2DerivedMintInfo } from 'state/mint/v2'
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH9[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH9[chainId])))
)
const toggleWalletModal = useWalletModalToggle() // toggle wallet when disconnected
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useV2DerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB || !deadline) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
let estimate,
method: (...args: any) => Promise<TransactionResponse>,
args: Array<string | string[] | number>,
value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsETH = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsETH ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsETH ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadline.toHexString(),
]
value = BigNumber.from((tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadline.toHexString(),
]
value = null
}
setAttemptingTxn(true)
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary:
'Add ' +
parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
' ' +
currencies[Field.CURRENCY_A]?.symbol +
' and ' +
parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) +
' ' +
currencies[Field.CURRENCY_B]?.symbol,
})
setTxHash(response.hash)
ReactGA.event({
category: 'Liquidity',
action: 'Add',
label: [currencies[Field.CURRENCY_A]?.symbol, currencies[Field.CURRENCY_B]?.symbol].join('/'),
})
})
)
.catch((error) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (error?.code !== 4001) {
console.error(error)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<Text fontSize="48px" fontWeight={500} lineHeight="42px" marginRight={10}>
{currencies[Field.CURRENCY_A]?.symbol + '/' + currencies[Field.CURRENCY_B]?.symbol}
</Text>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<Text fontSize="48px" fontWeight={500} lineHeight="42px" marginRight={10}>
{liquidityMinted?.toSignificant(6)}
</Text>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<Text fontSize="24px">
{currencies[Field.CURRENCY_A]?.symbol + '/' + currencies[Field.CURRENCY_B]?.symbol + ' Pool Tokens'}
</Text>
</Row>
<TYPE.italic fontSize={12} textAlign="left" padding={'8px 0 0 0 '}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</TYPE.italic>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currencyA: Currency) => {
const newCurrencyIdA = currencyId(currencyA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currencyB: Currency) => {
const newCurrencyIdB = currencyId(currencyB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA ? currencyIdA : 'ETH'}/${newCurrencyIdB}`)
}
},
[currencyIdA, history, currencyIdB]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
// if there was a tx hash, we want to clear the input
if (txHash) {
onFieldAInput('')
}
setTxHash('')
}, [onFieldAInput, txHash])
const isCreate = history.location.pathname.includes('/create')
const addIsUnsupported = useIsTransactionUnsupported(currencies?.CURRENCY_A, currencies?.CURRENCY_B)
return (
<>
<AppBody>
<AddRemoveTabs creating={isCreate} adding={true} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={noLiquidity ? 'You are creating a pool' : 'You will receive'}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
currencyToAdd={pair?.liquidityToken}
/>
<AutoColumn gap="20px">
{noLiquidity ||
(isCreate ? (
<ColumnCenter>
<BlueCard>
<AutoColumn gap="10px">
<TYPE.link fontWeight={600} color={'primaryText1'}>
You are the first liquidity provider.
</TYPE.link>
<TYPE.link fontWeight={400} color={'primaryText1'}>
The ratio of tokens you add will set the price of this pool.
</TYPE.link>
<TYPE.link fontWeight={400} color={'primaryText1'}>
Once you are happy with the rate click supply to review.
</TYPE.link>
</AutoColumn>
</BlueCard>
</ColumnCenter>
) : (
<ColumnCenter>
<BlueCard>
<AutoColumn gap="10px">
<TYPE.link fontWeight={400} color={'primaryText1'}>
<b>Tip:</b> When you add liquidity, you will receive pool tokens representing your position.
These tokens automatically earn fees proportional to your share of the pool, and can be redeemed
at any time.
</TYPE.link>
</AutoColumn>
</BlueCard>
</ColumnCenter>
))}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases
/>
<ColumnCenter>
<Plus size="16" color={theme.text2} />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<>
<LightCard padding="0px" borderRadius={'20px'}>
<RowBetween padding="1rem">
<TYPE.subHeader fontWeight={500} fontSize={14}>
{noLiquidity ? 'Initial prices' : 'Prices'} and pool share
</TYPE.subHeader>
</RowBetween>{' '}
<LightCard padding="1rem" borderRadius={'20px'}>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</LightCard>
</LightCard>
</>
)}
{addIsUnsupported ? (
<ButtonPrimary disabled={true}>
<TYPE.main mb="4px">Unsupported Asset</TYPE.main>
</ButtonPrimary>
) : !account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
) : (
<AutoColumn gap={'md'}>
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<ButtonPrimary
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
width={approvalB !== ApprovalState.APPROVED ? '48%' : '100%'}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
'Approve ' + currencies[Field.CURRENCY_A]?.symbol
)}
</ButtonPrimary>
)}
{approvalB !== ApprovalState.APPROVED && (
<ButtonPrimary
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
width={approvalA !== ApprovalState.APPROVED ? '48%' : '100%'}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
'Approve ' + currencies[Field.CURRENCY_B]?.symbol
)}
</ButtonPrimary>
)}
</RowBetween>
)}
<ButtonError
onClick={() => {
expertMode ? onAdd() : setShowConfirm(true)
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]}
>
<Text fontSize={20} fontWeight={500}>
{error ?? 'Supply'}
</Text>
</ButtonError>
</AutoColumn>
)}
</AutoColumn>
</Wrapper>
</AppBody>
{!addIsUnsupported ? (
pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', width: '100%', maxWidth: '400px', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null
) : (
<UnsupportedCurrencyFooter
show={addIsUnsupported}
currencies={[currencies.CURRENCY_A, currencies.CURRENCY_B]}
/>
)}
</>
)
}

@ -0,0 +1,20 @@
import React from 'react'
import { Redirect, RouteComponentProps } from 'react-router-dom'
import AddLiquidityV2 from './index'
export function RedirectToAddLiquidityV2() {
return <Redirect to="/add/v2/ETH" />
}
export function RedirectDuplicateTokenIdsV2(props: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const {
match: {
params: { currencyIdA, currencyIdB },
},
} = props
if (currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) {
return <Redirect to={`/add/V2/${currencyIdA}`} />
}
return <AddLiquidityV2 {...props} />
}

@ -11,12 +11,7 @@ import Web3ReactManager from '../components/Web3ReactManager'
import { ApplicationModal } from '../state/application/actions'
import { useModalOpen, useToggleModal } from '../state/application/hooks'
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
import AddLiquidity from './AddLiquidity'
import {
RedirectDuplicateTokenIds,
RedirectOldAddLiquidityPathStructure,
RedirectToAddLiquidity,
} from './AddLiquidity/redirects'
import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects'
import Earn from './Earn'
import Manage from './Earn/Manage'
import MigrateV1 from './MigrateV1'
@ -34,12 +29,15 @@ import Swap from './Swap'
import { OpenClaimAddressModalAndRedirectToSwap, RedirectPathToSwapOnly, RedirectToSwap } from './Swap/redirects'
import Vote from './Vote'
import VotePage from './Vote/VotePage'
import { RedirectDuplicateTokenIdsV2 } from './AddLiquidityV2/redirects'
import AddLiquidity from './AddLiquidity'
import AddLiquidityV2 from './AddLiquidityV2'
const AppWrapper = styled.div`
display: flex;
flex-flow: column;
align-items: flex-start;
overflow-x: hidden;
/* overflow-x: hidden; */
`
const BodyWrapper = styled.div`
@ -49,8 +47,8 @@ const BodyWrapper = styled.div`
padding-top: 160px;
align-items: center;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
/* overflow-y: auto; */
/* overflow-x: hidden; */
${({ theme }) => theme.mediaWidth.upToSmall`
padding: 16px;
@ -108,13 +106,20 @@ export default function App() {
<Route exact strict path="/find" component={PoolFinder} />
<Route exact strict path="/pool/v2" component={PoolV2} />
<Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/create" component={RedirectToAddLiquidity} />
<Route exact path="/add" component={AddLiquidity} />
<Route exact path="/add/:currencyIdA" component={RedirectOldAddLiquidityPathStructure} />
<Route exact path="/add/:currencyIdA/:currencyIdB" component={RedirectDuplicateTokenIds} />
<Route exact path="/create" component={AddLiquidity} />
<Route exact path="/create/:currencyIdA" component={RedirectOldAddLiquidityPathStructure} />
<Route exact path="/create/:currencyIdA/:currencyIdB" component={RedirectDuplicateTokenIds} />
<Route exact path="/add/v2/" component={AddLiquidityV2} />
<Route exact path="/add/v2/:currencyIdA" component={AddLiquidityV2} />
<Route exact path="/add/:currencyIdA" component={AddLiquidity} />
<Route exact strict path="/add/v2/:currencyIdA?/:currencyIdB?" component={RedirectDuplicateTokenIdsV2} />
<Route
exact
strict
path="/add/:currencyIdA?/:currencyIdB?/:feeAmount?"
component={RedirectDuplicateTokenIds}
/>
<Route exact strict path="/remove/v1/:address" component={RemoveV1Exchange} />
<Route exact strict path="/remove/v2/:tokens" component={RedirectOldRemoveLiquidityPathStructure} />
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />

@ -1,8 +1,9 @@
import React from 'react'
import styled from 'styled-components'
export const BodyWrapper = styled.div`
export const BodyWrapper = styled.div<{ margin?: string }>`
position: relative;
margin-top: ${({ margin }) => margin ?? '0px'};
max-width: 460px;
width: 100%;
background: ${({ theme }) => theme.bg0};
@ -14,6 +15,6 @@ export const BodyWrapper = styled.div`
/**
* The styled container element that wraps the content of most pages and the tabs.
*/
export default function AppBody({ children }: { children: React.ReactNode }) {
return <BodyWrapper>{children}</BodyWrapper>
export default function AppBody({ children, ...rest }: { children: React.ReactNode }) {
return <BodyWrapper {...rest}>{children}</BodyWrapper>
}

@ -26,9 +26,9 @@ import { CountUp } from 'use-count-up'
import { wrappedCurrency } from '../../utils/wrappedCurrency'
import { currencyId } from '../../utils/currencyId'
import { useTotalSupply } from '../../data/TotalSupply'
import { usePair } from '../../data/Reserves'
import { usePair } from '../../data/V2'
import usePrevious from '../../hooks/usePrevious'
import useUSDCPrice from '../../utils/useUSDCPrice'
import useUSDCPrice from '../../hooks/useUSDCPrice'
import { BIG_INT_ZERO, BIG_INT_SECONDS_IN_WEEK } from '../../constants'
const PageWrapper = styled(AutoColumn)`

@ -14,13 +14,12 @@ import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount'
import QuestionHelper from '../../components/QuestionHelper'
import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
import { Dots } from '../../components/swap/styleds'
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE, V1_MIGRATOR_ADDRESS } from 'constants/index'
import { PairState, usePair } from '../../data/Reserves'
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE, V1_MIGRATOR_ADDRESS } from '../../constants'
import { PairState, usePair } from '../../data/V2'
import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks'
import { useToken } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { useV1ExchangeContract, useV1MigratorContract } from '../../hooks/useContract'
import { NEVER_RELOAD, useSingleCallResult } from '../../state/multicall/hooks'
import { useIsTransactionPending, useTransactionAdder } from '../../state/transactions/hooks'
import { useETHBalances, useTokenBalance } from '../../state/wallet/hooks'
@ -28,6 +27,7 @@ import { BackArrow, ExternalLink, TYPE } from '../../theme'
import { getEtherscanLink, isAddress } from '../../utils'
import { BodyWrapper } from '../AppBody'
import { EmptyState } from './EmptyState'
import { useV1ExchangeContract, useV1MigratorContract } from 'hooks/useContract'
const WEI_DENOM = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))
const ZERO = JSBI.BigInt(0)

@ -1,43 +1,32 @@
import { TransactionResponse } from '@ethersproject/abstract-provider'
import { AddressZero } from '@ethersproject/constants'
import { Currency, CurrencyAmount, Fraction, Percent, Price, Token, TokenAmount, WETH9 } from '@uniswap/sdk-core'
import React, { useMemo } from 'react'
import { Currency, CurrencyAmount, Price, Token, TokenAmount, WETH9 } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk'
import React, { useCallback, useMemo, useState } from 'react'
import ReactGA from 'react-ga'
import { Redirect, RouteComponentProps } from 'react-router'
import { Text } from 'rebass'
import { ButtonConfirmed } from '../../components/Button'
import { LightCard, PinkCard, YellowCard } from '../../components/Card'
import { AutoColumn } from '../../components/Column'
import CurrencyLogo from '../../components/CurrencyLogo'
import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount'
import QuestionHelper from '../../components/QuestionHelper'
import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
import { Dots } from '../../components/swap/styleds'
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE, V1_MIGRATOR_ADDRESS } from 'constants/index'
import { PairState, usePair } from '../../data/Reserves'
import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks'
import { useToken } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { usePairContract, useV1MigratorContract } from '../../hooks/useContract'
import { usePairContract } from '../../hooks/useContract'
import { NEVER_RELOAD, useSingleCallResult } from '../../state/multicall/hooks'
import { useIsTransactionPending, useTransactionAdder } from '../../state/transactions/hooks'
import { useETHBalances, useTokenBalance } from '../../state/wallet/hooks'
import { useTokenBalance } from '../../state/wallet/hooks'
import { BackArrow, ExternalLink, TYPE } from '../../theme'
import { getEtherscanLink, isAddress } from '../../utils'
import { BodyWrapper } from '../AppBody'
import { EmptyState } from '../MigrateV1/EmptyState'
import { toV2LiquidityToken } from 'state/user/hooks'
import { V2_MIGRATOR_ADDRESSES } from 'constants/v3'
// TODO the whole file
const WEI_DENOM = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))
const ZERO = JSBI.BigInt(0)
const ONE = JSBI.BigInt(1)
const ZERO_FRACTION = new Fraction(ZERO, ONE)
const ALLOWED_OUTPUT_MIN_PERCENT = new Percent(JSBI.BigInt(10000 - INITIAL_ALLOWED_SLIPPAGE), JSBI.BigInt(10000))
// const WEI_DENOM = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))
// const ZERO = JSBI.BigInt(0)
// const ONE = JSBI.BigInt(1)
// const ZERO_FRACTION = new Fraction(ZERO, ONE)
// const ALLOWED_OUTPUT_MIN_PERCENT = new Percent(JSBI.BigInt(10000 - INITIAL_ALLOWED_SLIPPAGE), JSBI.BigInt(10000))
export function V2LiquidityInfo({
token,
@ -105,7 +94,7 @@ function V2PairMigration({
token0: Token
token1: Token
}) {
const { account, chainId } = useActiveWeb3React()
const { chainId } = useActiveWeb3React()
// this is just getLiquidityValue with the fee off, but for the passed pair
const token0Value = useMemo(
@ -119,6 +108,8 @@ function V2PairMigration({
const v2SpotPrice = new Price(token0, token1, reserve0.raw, reserve1.raw)
console.log(token0Value, token1Value, v2SpotPrice)
// const isFirstLiquidityProvider: boolean = false // check for v3 pair existence
// const [confirmingMigration, setConfirmingMigration] = useState<boolean>(false)

@ -13,8 +13,8 @@ import { EmptyState } from '../MigrateV1/EmptyState'
import QuestionHelper from '../../components/QuestionHelper'
import { Dots } from '../../components/swap/styleds'
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
import { usePairs } from 'data/Reserves'
import MigrateV2PositionCard from 'components/PositionCard/V2'
import { usePairs } from 'data/V2'
// TODO there's a bug in loading where "No V2 Liquidity found" flashes
// TODO add support for more pairs

@ -15,7 +15,7 @@ import { ButtonPrimary, ButtonSecondary } from '../../components/Button'
import { AutoColumn } from '../../components/Column'
import { useActiveWeb3React } from '../../hooks'
import { usePairs } from '../../data/Reserves'
import { usePairs } from '../../data/V2'
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
import { Dots } from '../../components/swap/styleds'
import { CardSection, DataCard, CardNoise, CardBGImage } from '../../components/earn/styled'

@ -11,7 +11,7 @@ import { FindPoolTabs } from '../../components/NavigationTabs'
import { MinimalPositionCard } from '../../components/PositionCard'
import Row from '../../components/Row'
import CurrencySearchModal from '../../components/SearchModal/CurrencySearchModal'
import { PairState, usePair } from '../../data/Reserves'
import { PairState, usePair } from '../../data/V2'
import { useActiveWeb3React } from '../../hooks'
import { usePairAdder } from '../../state/user/hooks'
import { useTokenBalance } from '../../state/wallet/hooks'

@ -8,5 +8,6 @@ export default function RemoveLiquidityV3({
params: { currencyIdA, currencyIdB, fee },
},
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string; fee: string }>) {
console.log(currencyIdA, currencyIdB, fee)
return <AppBody>TODO</AppBody>
}

@ -31,7 +31,7 @@ import { useTransactionAdder } from '../../state/transactions/hooks'
import { StyledInternalLink, TYPE } from '../../theme'
import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
import { currencyId } from '../../utils/currencyId'
import useDebouncedChangeHandler from '../../utils/useDebouncedChangeHandler'
import useDebouncedChangeHandler from '../../hooks/useDebouncedChangeHandler'
import { wrappedCurrency } from '../../utils/wrappedCurrency'
import AppBody from '../AppBody'
import { ClickableText, MaxButton, Wrapper } from '../Pool/styleds'

6
src/pages/styled.tsx Normal file

@ -0,0 +1,6 @@
import styled from 'styled-components'
export const StandardPageWrapper = styled.div`
padding-top: 160px;
width: 100%;
`

@ -2,7 +2,7 @@ import { Currency, CurrencyAmount, Percent, TokenAmount } from '@uniswap/sdk-cor
import { JSBI, Pair } from '@uniswap/v2-sdk'
import { useCallback } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { usePair } from '../../data/Reserves'
import { usePair } from '../../data/V2'
import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks'

@ -42,7 +42,6 @@ const EMPTY_LIST: TokenAddressMap = {
[ChainId.ROPSTEN]: {},
[ChainId.GÖRLI]: {},
[ChainId.MAINNET]: {},
[1337]: {},
}
const listCache: WeakMap<TokenList, TokenAddressMap> | null =
@ -99,7 +98,6 @@ function combineMaps(map1: TokenAddressMap, map2: TokenAddressMap): TokenAddress
4: { ...map1[4], ...map2[4] },
5: { ...map1[5], ...map2[5] },
42: { ...map1[42], ...map2[42] },
1337: { ...map1[1337], ...map2[1337] },
}
}

@ -6,11 +6,11 @@ export enum Field {
}
export enum Bound {
CURRENT = 'CURRENT',
LOWER = 'LOWER',
UPPER = 'UPPER',
}
// save for % inputs
export enum RangeType {
PERCENT = 'PERCENT',
RATE = 'RATE',
@ -19,5 +19,5 @@ export enum RangeType {
export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('mint/typeInputMint')
export const typeLowerRangeInput = createAction<{ typedValue: string }>('mint/typeLowerRangeInput')
export const typeUpperRangeInput = createAction<{ typedValue: string }>('mint/typeUpperRangeInput')
export const updateRangeType = createAction<{ rangeType: RangeType }>('mint/updateRangeType')
export const typeStartPriceInput = createAction<{ typedValue: string }>('mint/typeStartPriceInput')
export const resetMintState = createAction<void>('mint/resetMintState')

@ -1,27 +1,19 @@
import { Currency, CurrencyAmount, ETHER, Percent, Price, TokenAmount } from '@uniswap/sdk-core'
import { JSBI, Pair } from '@uniswap/v2-sdk'
import { BIG_INT_ZERO } from './../../constants/index'
import { getTickToPrice } from 'utils/getTickToPrice'
import JSBI from 'jsbi'
import { PoolState } from './../../data/Pools'
import { Pool, FeeAmount, Position, priceToClosestTick, TickMath } from '@uniswap/v3-sdk/dist/'
import { Currency, CurrencyAmount, ETHER, Price } from '@uniswap/sdk-core'
import { useCallback, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { PairState, usePair } from '../../data/Reserves'
import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks'
import { wrappedCurrency, wrappedCurrencyAmount } from '../../utils/wrappedCurrency'
import { AppDispatch, AppState } from '../index'
import { tryParseAmount } from '../swap/hooks'
import { useCurrencyBalances } from '../wallet/hooks'
import {
Field,
Bound,
typeInput,
typeLowerRangeInput,
typeUpperRangeInput,
RangeType,
updateRangeType as updateRangeTypeAction,
} from './actions'
import { Field, Bound, typeInput, typeLowerRangeInput, typeUpperRangeInput, typeStartPriceInput } from './actions'
import { tryParseTick } from './utils'
const ZERO = JSBI.BigInt(0)
import { usePool } from 'data/Pools'
export function useMintState(): AppState['mint'] {
return useSelector<AppState, AppState['mint']>((state) => state.mint)
@ -34,7 +26,7 @@ export function useMintActionHandlers(
onFieldBInput: (typedValue: string) => void
onLowerRangeInput: (typedValue: string) => void
onUpperRangeInput: (typedValue: string) => void
updateRangeType: (rangetype: RangeType) => void
onStartPriceInput: (typedValue: string) => void
} {
const dispatch = useDispatch<AppDispatch>()
@ -66,9 +58,9 @@ export function useMintActionHandlers(
[dispatch]
)
const updateRangeType = useCallback(
(rangeType: RangeType) => {
dispatch(updateRangeTypeAction({ rangeType }))
const onStartPriceInput = useCallback(
(typedValue: string) => {
dispatch(typeStartPriceInput({ typedValue }))
},
[dispatch]
)
@ -78,39 +70,48 @@ export function useMintActionHandlers(
onFieldBInput,
onLowerRangeInput,
onUpperRangeInput,
updateRangeType,
onStartPriceInput,
}
}
// dummy entity
export interface Tick {
rate: number
}
export function useDerivedMintInfo(
currencyA: Currency | undefined,
currencyB: Currency | undefined
currencyB: Currency | undefined,
feeAmount: FeeAmount | undefined
): {
dependentField: Field
currencies: { [field in Field]?: Currency }
pair?: Pair | null
pairState: PairState
currencyBalances: { [field in Field]?: CurrencyAmount }
parsedAmounts: { [field in Field]?: CurrencyAmount }
ticks: { [bound in Bound]?: Tick }
pool?: Pool | null
poolState: PoolState
ticks: { [bound in Bound]?: number | undefined }
price?: Price
pricesAtTicks: {
[bound in Bound]?: Price | undefined
}
currencies: { [field in Field]?: Currency }
currencyBalances: { [field in Field]?: CurrencyAmount }
dependentField: Field
parsedAmounts: { [field in Field]?: CurrencyAmount }
position: Position | undefined
noLiquidity?: boolean
liquidityMinted?: TokenAmount
poolTokenPercentage?: Percent
error?: string
errorMessage?: string
invalidPool: boolean
outOfRange: boolean
invalidRange: boolean
depositADisabled: boolean
depositBDisabled: boolean
} {
const { account, chainId } = useActiveWeb3React()
const { independentField, typedValue, otherTypedValue, lowerRangeTypedValue, upperRangeTypedValue } = useMintState()
const {
independentField,
typedValue,
lowerRangeTypedValue,
upperRangeTypedValue,
startPriceTypedValue,
} = useMintState()
const dependentField = independentField === Field.CURRENCY_A ? Field.CURRENCY_B : Field.CURRENCY_A
// tokens
// currencies
const currencies: { [field in Field]?: Currency } = useMemo(
() => ({
[Field.CURRENCY_A]: currencyA ?? undefined,
@ -119,48 +120,152 @@ export function useDerivedMintInfo(
[currencyA, currencyB]
)
// pair
const [pairState, pair] = usePair(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B])
const totalSupply = useTotalSupply(pair?.liquidityToken)
const noLiquidity: boolean =
pairState === PairState.NOT_EXISTS || Boolean(totalSupply && JSBI.equal(totalSupply.raw, ZERO))
// formatted with tokens
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
chainId,
currencyA,
currencyB,
])
// balances
const balances = useCurrencyBalances(account ?? undefined, [
currencies[Field.CURRENCY_A],
currencies[Field.CURRENCY_B],
])
const currencyBalances: { [field in Field]?: CurrencyAmount } = {
[Field.CURRENCY_A]: balances[0],
[Field.CURRENCY_B]: balances[1],
}
// amounts
const independentAmount: CurrencyAmount | undefined = tryParseAmount(typedValue, currencies[independentField])
const dependentAmount: CurrencyAmount | undefined = useMemo(() => {
// pool
const [poolState, pool] = usePool(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B], feeAmount)
const noLiquidity = poolState === PoolState.NOT_EXISTS
const price = useMemo(() => {
// if no liquidity use typed value
if (noLiquidity) {
if (otherTypedValue && currencies[dependentField]) {
return tryParseAmount(otherTypedValue, currencies[dependentField])
}
return undefined
} else if (independentAmount) {
// we wrap the currencies just to get the price in terms of the other token
const wrappedIndependentAmount = wrappedCurrencyAmount(independentAmount, chainId)
const [tokenA, tokenB] = [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)]
if (tokenA && tokenB && wrappedIndependentAmount && pair) {
const dependentCurrency = dependentField === Field.CURRENCY_B ? currencyB : currencyA
const dependentTokenAmount =
dependentField === Field.CURRENCY_B
? pair.priceOf(tokenA).quote(wrappedIndependentAmount)
: pair.priceOf(tokenB).quote(wrappedIndependentAmount)
return dependentCurrency === ETHER ? CurrencyAmount.ether(dependentTokenAmount.raw) : dependentTokenAmount
const parsedAmount = tryParseAmount(startPriceTypedValue, tokenA)
if (parsedAmount && tokenA && tokenB) {
const amountOne = tryParseAmount('1', tokenA)
return amountOne ? new Price(tokenA, tokenB, amountOne.raw, parsedAmount.raw) : undefined
}
return undefined
} else {
// get the amount of quote currency
return pool && tokenA ? pool.priceOf(tokenA) : undefined
}
}, [noLiquidity, startPriceTypedValue, tokenA, tokenB, pool])
// used for ratio calculation when pool not initialized
const mockPool = useMemo(() => {
if (tokenA && tokenB && feeAmount && price) {
const currentTick = priceToClosestTick(price)
const currentSqrt = TickMath.getSqrtRatioAtTick(currentTick)
return new Pool(tokenA, tokenB, feeAmount, currentSqrt, JSBI.BigInt(0), currentTick, [])
} else {
return undefined
}
}, [noLiquidity, otherTypedValue, currencies, dependentField, independentAmount, currencyA, chainId, currencyB, pair])
}, [feeAmount, price, tokenA, tokenB])
// if pool exists use it, if not use the mock pool
const poolForPosition: Pool | undefined = pool ?? mockPool
// parse typed range values and determine closest ticks
const ticks: {
[key: string]: number | undefined
} = useMemo(() => {
return {
[Bound.LOWER]: poolForPosition ? tryParseTick(tokenA, tokenB, poolForPosition, lowerRangeTypedValue) : undefined,
[Bound.UPPER]: poolForPosition ? tryParseTick(tokenA, tokenB, poolForPosition, upperRangeTypedValue) : undefined,
}
}, [lowerRangeTypedValue, poolForPosition, tokenA, tokenB, upperRangeTypedValue])
const { [Bound.LOWER]: tickLower, [Bound.UPPER]: tickUpper } = ticks || {}
const sortedTicks = useMemo(
() =>
tickLower !== undefined && tickUpper !== undefined
? tickLower < tickUpper
? [tickLower, tickUpper]
: [tickUpper, tickLower]
: undefined,
[tickLower, tickUpper]
)
const pricesAtTicks = useMemo(() => {
return {
[Bound.LOWER]: poolForPosition ? getTickToPrice(tokenA, tokenB, ticks[Bound.LOWER]) : undefined,
[Bound.UPPER]: poolForPosition ? getTickToPrice(tokenA, tokenB, ticks[Bound.UPPER]) : undefined,
}
}, [poolForPosition, ticks, tokenA, tokenB])
const { [Bound.LOWER]: lowerPrice, [Bound.UPPER]: upperPrice } = pricesAtTicks
// mark invalid range
const invalidRange = Boolean(lowerPrice && upperPrice && lowerPrice.greaterThan(upperPrice))
// amounts
const independentAmount: CurrencyAmount | undefined = tryParseAmount(typedValue, currencies[independentField])
const dependentAmount: CurrencyAmount | undefined = useMemo(() => {
// we wrap the currencies just to get the price in terms of the other token
const wrappedIndependentAmount = wrappedCurrencyAmount(independentAmount, chainId)
const dependentCurrency = dependentField === Field.CURRENCY_B ? currencyB : currencyA
if (
feeAmount &&
independentAmount &&
tokenA &&
tokenB &&
wrappedIndependentAmount &&
price &&
lowerPrice &&
upperPrice &&
sortedTicks &&
poolForPosition
) {
// if price is out of range or invalid range - retun 0 (single deposit with be independent)
if (price.lessThan(lowerPrice) || price.greaterThan(upperPrice) || invalidRange) {
return undefined
}
const position: Position | undefined = wrappedIndependentAmount.token.equals(poolForPosition.token0)
? Position.fromAmount0({
pool: poolForPosition,
tickLower: sortedTicks[0],
tickUpper: sortedTicks[1],
amount0: independentAmount.raw,
})
: Position.fromAmount1({
pool: poolForPosition,
tickLower: sortedTicks[0],
tickUpper: sortedTicks[1],
amount1: independentAmount.raw,
})
const dependentTokenAmount = wrappedIndependentAmount.token.equals(poolForPosition.token0)
? position.amount1
: position.amount0
return dependentCurrency === ETHER ? CurrencyAmount.ether(dependentTokenAmount.raw) : dependentTokenAmount
}
return undefined
}, [
independentAmount,
chainId,
dependentField,
currencyB,
currencyA,
feeAmount,
tokenA,
tokenB,
price,
sortedTicks,
lowerPrice,
upperPrice,
poolForPosition,
invalidRange,
])
const parsedAmounts: { [field in Field]: CurrencyAmount | undefined } = useMemo(() => {
return {
@ -169,87 +274,101 @@ export function useDerivedMintInfo(
}
}, [dependentAmount, independentAmount, independentField])
const price = useMemo(() => {
if (noLiquidity) {
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
if (currencyAAmount && currencyBAmount) {
return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw)
}
// single deposit only if price is out of range
const deposit1Disabled = Boolean(sortedTicks && poolForPosition && poolForPosition.tickCurrent <= sortedTicks[0])
const deposit0Disabled = Boolean(sortedTicks && poolForPosition && poolForPosition.tickCurrent >= sortedTicks[1])
// sorted for token order
const depositADisabled = Boolean(
(deposit0Disabled && poolForPosition && tokenA && poolForPosition.token0.equals(tokenA)) ||
(deposit1Disabled && poolForPosition && tokenA && poolForPosition.token1.equals(tokenA))
)
const depositBDisabled = Boolean(
(deposit0Disabled && poolForPosition && tokenB && poolForPosition.token0.equals(tokenB)) ||
(deposit1Disabled && poolForPosition && tokenB && poolForPosition.token1.equals(tokenB))
)
// create position entity based on users selection
const position: Position | undefined = useMemo(() => {
if (!poolForPosition || !tokenA || !tokenB || !sortedTicks) {
return undefined
} else {
const wrappedCurrencyA = wrappedCurrency(currencyA, chainId)
return pair && wrappedCurrencyA ? pair.priceOf(wrappedCurrencyA) : undefined
}
}, [chainId, currencyA, noLiquidity, pair, parsedAmounts])
// parse typed range values and determine closest ticks, dummy rn
const ticks = {
[Bound.CURRENT]: {
rate: parseFloat(
independentField === Field.CURRENCY_A ? price?.toFixed(6) ?? '0' : price?.invert().toFixed(6) ?? '0'
),
},
[Bound.LOWER]: tryParseTick(lowerRangeTypedValue),
[Bound.UPPER]: tryParseTick(upperRangeTypedValue),
}
// mark as 0 if disbaled because out of range
const amount0 = !deposit0Disabled
? parsedAmounts?.[tokenA.equals(poolForPosition.token0) ? Field.CURRENCY_A : Field.CURRENCY_B]?.raw
: BIG_INT_ZERO
const amount1 = !deposit1Disabled
? parsedAmounts?.[tokenA.equals(poolForPosition.token0) ? Field.CURRENCY_B : Field.CURRENCY_A]?.raw
: BIG_INT_ZERO
// liquidity minted
const liquidityMinted = useMemo(() => {
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
const [tokenAmountA, tokenAmountB] = [
wrappedCurrencyAmount(currencyAAmount, chainId),
wrappedCurrencyAmount(currencyBAmount, chainId),
]
if (pair && totalSupply && tokenAmountA && tokenAmountB) {
return pair.getLiquidityMinted(totalSupply, tokenAmountA, tokenAmountB)
if (amount0 !== undefined && amount1 !== undefined) {
return Position.fromAmounts({
pool: poolForPosition,
tickLower: sortedTicks[0],
tickUpper: sortedTicks[1],
amount0: amount0,
amount1: amount1,
})
} else {
return undefined
}
}, [parsedAmounts, chainId, pair, totalSupply])
}, [parsedAmounts, poolForPosition, sortedTicks, tokenA, tokenB, deposit0Disabled, deposit1Disabled])
const poolTokenPercentage = useMemo(() => {
if (liquidityMinted && totalSupply) {
return new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw)
} else {
return undefined
}
}, [liquidityMinted, totalSupply])
// liquiidty range warning
const outOfRange = Boolean(
price &&
lowerPrice &&
upperPrice &&
!invalidRange &&
(lowerPrice.greaterThan(price) || price.greaterThan(upperPrice))
)
let error: string | undefined
let errorMessage: string | undefined
if (!account) {
error = 'Connect Wallet'
errorMessage = 'Connect Wallet'
}
if (pairState === PairState.INVALID) {
error = error ?? 'Invalid pair'
if (poolState === PoolState.INVALID) {
errorMessage = errorMessage ?? 'Invalid pair'
}
if (!parsedAmounts[Field.CURRENCY_A] || !parsedAmounts[Field.CURRENCY_B]) {
error = error ?? 'Enter an amount'
if (
(!parsedAmounts[Field.CURRENCY_A] && !depositADisabled) ||
(!parsedAmounts[Field.CURRENCY_B] && !depositBDisabled)
) {
errorMessage = errorMessage ?? 'Enter an amount'
}
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
if (currencyAAmount && currencyBalances?.[Field.CURRENCY_A]?.lessThan(currencyAAmount)) {
error = 'Insufficient ' + currencies[Field.CURRENCY_A]?.symbol + ' balance'
errorMessage = 'Insufficient ' + currencies[Field.CURRENCY_A]?.symbol + ' balance'
}
if (currencyBAmount && currencyBalances?.[Field.CURRENCY_B]?.lessThan(currencyBAmount)) {
error = 'Insufficient ' + currencies[Field.CURRENCY_B]?.symbol + ' balance'
errorMessage = 'Insufficient ' + currencies[Field.CURRENCY_B]?.symbol + ' balance'
}
const invalidPool = poolState === PoolState.INVALID
return {
dependentField,
currencies,
pair,
pairState,
pool,
poolState,
currencyBalances,
parsedAmounts,
ticks,
price,
pricesAtTicks,
position,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
errorMessage,
invalidPool,
invalidRange,
outOfRange,
depositADisabled,
depositBDisabled,
}
}

@ -5,37 +5,30 @@ import {
typeInput,
typeLowerRangeInput,
typeUpperRangeInput,
RangeType,
updateRangeType,
typeStartPriceInput,
} from './actions'
export interface MintState {
readonly independentField: Field
readonly typedValue: string
readonly otherTypedValue: string // for the case when there's no liquidity
readonly startPriceTypedValue: string // for the case when there's no liquidity
readonly lowerRangeTypedValue: string
readonly upperRangeTypedValue: string
readonly rangeType: RangeType
}
export const initialState: MintState = {
independentField: Field.CURRENCY_A,
typedValue: '',
otherTypedValue: '',
startPriceTypedValue: '',
lowerRangeTypedValue: '',
upperRangeTypedValue: '',
rangeType: RangeType.RATE,
}
export default createReducer<MintState>(initialState, (builder) =>
builder
.addCase(resetMintState, () => initialState)
.addCase(updateRangeType, (state, { payload: { rangeType } }) => {
return {
...state,
rangeType,
}
})
.addCase(typeLowerRangeInput, (state, { payload: { typedValue } }) => {
return {
...state,
@ -48,6 +41,12 @@ export default createReducer<MintState>(initialState, (builder) =>
upperRangeTypedValue: typedValue,
}
})
.addCase(typeStartPriceInput, (state, { payload: { typedValue } }) => {
return {
...state,
startPriceTypedValue: typedValue,
}
})
.addCase(typeInput, (state, { payload: { field, typedValue, noLiquidity } }) => {
if (noLiquidity) {
// they're typing into the field they've last typed in

@ -1,18 +1,27 @@
import { Tick } from './hooks'
/**
* @todo
* udpate to actually parse input and calculate next tick
*/
export function tryParseTick(value?: string): Tick | undefined {
if (!value) {
import { Pool, priceToClosestTick, nearestUsableTick } from '@uniswap/v3-sdk/dist/'
import { Price, Token } from '@uniswap/sdk-core'
import { tryParseAmount } from 'state/swap/hooks'
export function tryParseTick(
baseToken: Token | undefined,
quoteToken: Token | undefined,
pool: Pool | undefined,
value?: string
): number | undefined {
if (!value || !baseToken || !quoteToken || !pool) {
return undefined
}
try {
return { rate: parseFloat(value) * 0.999 }
} catch (error) {
console.debug(`Failed to parse range amount: "${value}"`, error)
}
const amount = tryParseAmount(value, quoteToken)
return undefined
const amountOne = tryParseAmount('1', baseToken)
if (!amount || !amountOne) return undefined
// parse the typed value into a price, token0 should always be base currency based on url
const price = new Price(baseToken, quoteToken, amountOne.raw, amount.raw)
const tick = priceToClosestTick(price)
return nearestUsableTick(tick, pool.tickSpacing)
}

169
src/state/mint/v2.ts Normal file

@ -0,0 +1,169 @@
import { useMemo } from 'react'
import { Pair } from '@uniswap/v2-sdk'
import { Currency, CurrencyAmount, ETHER, Percent, Price, TokenAmount } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import { PairState, usePair } from '../../data/V2'
import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks'
import { wrappedCurrency, wrappedCurrencyAmount } from '../../utils/wrappedCurrency'
import { tryParseAmount } from '../swap/hooks'
import { useCurrencyBalances } from '../wallet/hooks'
import { Field } from './actions'
import { useMintState } from './hooks'
const ZERO = JSBI.BigInt(0)
export function useV2DerivedMintInfo(
currencyA: Currency | undefined,
currencyB: Currency | undefined
): {
dependentField: Field
currencies: { [field in Field]?: Currency }
pair?: Pair | null
pairState: PairState
currencyBalances: { [field in Field]?: CurrencyAmount }
parsedAmounts: { [field in Field]?: CurrencyAmount }
price?: Price
noLiquidity?: boolean
liquidityMinted?: TokenAmount
poolTokenPercentage?: Percent
error?: string
} {
const { account, chainId } = useActiveWeb3React()
const { independentField, typedValue, otherTypedValue } = useMintState()
const dependentField = independentField === Field.CURRENCY_A ? Field.CURRENCY_B : Field.CURRENCY_A
// tokens
const currencies: { [field in Field]?: Currency } = useMemo(
() => ({
[Field.CURRENCY_A]: currencyA ?? undefined,
[Field.CURRENCY_B]: currencyB ?? undefined,
}),
[currencyA, currencyB]
)
// pair
const [pairState, pair] = usePair(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B])
const totalSupply = useTotalSupply(pair?.liquidityToken)
const noLiquidity: boolean =
pairState === PairState.NOT_EXISTS || Boolean(totalSupply && JSBI.equal(totalSupply.raw, ZERO))
// balances
const balances = useCurrencyBalances(account ?? undefined, [
currencies[Field.CURRENCY_A],
currencies[Field.CURRENCY_B],
])
const currencyBalances: { [field in Field]?: CurrencyAmount } = {
[Field.CURRENCY_A]: balances[0],
[Field.CURRENCY_B]: balances[1],
}
// amounts
const independentAmount: CurrencyAmount | undefined = tryParseAmount(typedValue, currencies[independentField])
const dependentAmount: CurrencyAmount | undefined = useMemo(() => {
if (noLiquidity) {
if (otherTypedValue && currencies[dependentField]) {
return tryParseAmount(otherTypedValue, currencies[dependentField])
}
return undefined
} else if (independentAmount) {
// we wrap the currencies just to get the price in terms of the other token
const wrappedIndependentAmount = wrappedCurrencyAmount(independentAmount, chainId)
const [tokenA, tokenB] = [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)]
if (tokenA && tokenB && wrappedIndependentAmount && pair) {
const dependentCurrency = dependentField === Field.CURRENCY_B ? currencyB : currencyA
const dependentTokenAmount =
dependentField === Field.CURRENCY_B
? pair.priceOf(tokenA).quote(wrappedIndependentAmount)
: pair.priceOf(tokenB).quote(wrappedIndependentAmount)
return dependentCurrency === ETHER ? CurrencyAmount.ether(dependentTokenAmount.raw) : dependentTokenAmount
}
return undefined
} else {
return undefined
}
}, [noLiquidity, otherTypedValue, currencies, dependentField, independentAmount, currencyA, chainId, currencyB, pair])
const parsedAmounts: { [field in Field]: CurrencyAmount | undefined } = useMemo(() => {
return {
[Field.CURRENCY_A]: independentField === Field.CURRENCY_A ? independentAmount : dependentAmount,
[Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount,
}
}, [dependentAmount, independentAmount, independentField])
const price = useMemo(() => {
if (noLiquidity) {
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
if (currencyAAmount && currencyBAmount) {
return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw)
}
return undefined
} else {
const wrappedCurrencyA = wrappedCurrency(currencyA, chainId)
return pair && wrappedCurrencyA ? pair.priceOf(wrappedCurrencyA) : undefined
}
}, [chainId, currencyA, noLiquidity, pair, parsedAmounts])
// liquidity minted
const liquidityMinted = useMemo(() => {
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
const [tokenAmountA, tokenAmountB] = [
wrappedCurrencyAmount(currencyAAmount, chainId),
wrappedCurrencyAmount(currencyBAmount, chainId),
]
if (pair && totalSupply && tokenAmountA && tokenAmountB) {
return pair.getLiquidityMinted(totalSupply, tokenAmountA, tokenAmountB)
} else {
return undefined
}
}, [parsedAmounts, chainId, pair, totalSupply])
const poolTokenPercentage = useMemo(() => {
if (liquidityMinted && totalSupply) {
return new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw)
} else {
return undefined
}
}, [liquidityMinted, totalSupply])
let error: string | undefined
if (!account) {
error = 'Connect Wallet'
}
if (pairState === PairState.INVALID) {
error = error ?? 'Invalid pair'
}
if (!parsedAmounts[Field.CURRENCY_A] || !parsedAmounts[Field.CURRENCY_B]) {
error = error ?? 'Enter an amount'
}
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
if (currencyAAmount && currencyBalances?.[Field.CURRENCY_A]?.lessThan(currencyAAmount)) {
error = 'Insufficient ' + currencies[Field.CURRENCY_A]?.symbol + ' balance'
}
if (currencyBAmount && currencyBalances?.[Field.CURRENCY_B]?.lessThan(currencyBAmount)) {
error = 'Insufficient ' + currencies[Field.CURRENCY_B]?.symbol + ' balance'
}
return {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
}
}

@ -171,7 +171,7 @@ export function useSingleContractMultipleData(
const calls = useMemo(
() =>
contract && fragment && callInputs && callInputs.length > 0
contract && fragment && callInputs?.length > 0 && callInputs.every((inputs) => isValidMethodArgs(inputs))
? callInputs.map<Call>((inputs) => {
return {
address: contract.address,
@ -180,7 +180,7 @@ export function useSingleContractMultipleData(
}
})
: [],
[callInputs, contract, fragment, gasRequired]
[contract, fragment, callInputs, gasRequired]
)
const results = useCallsData(calls, options)

@ -53,6 +53,7 @@ export function colors(darkMode: boolean): Colors {
bg3: darkMode ? '#40444F' : '#EDEEF2',
bg4: darkMode ? '#565A69' : '#CED0D9',
bg5: darkMode ? '#6C7284' : '#888D9B',
bg6: darkMode ? '#1A2028' : '#888D9B',
//specialty colors
modalBG: darkMode ? 'rgba(0,0,0,.425)' : 'rgba(0,0,0,0.3)',

@ -20,6 +20,7 @@ export interface Colors {
bg3: Color
bg4: Color
bg5: Color
bg6: Color
modalBG: Color
advancedBG: Color

@ -1,11 +1,4 @@
import { BigNumberish } from '@ethersproject/bignumber'
import { basisPointsToPercent } from 'utils'
const FEE_BIPS = {
FIVE: basisPointsToPercent(5),
THIRTY: basisPointsToPercent(30),
ONE_HUNDRED: basisPointsToPercent(100),
}
export interface Position {
feesEarned: Record<string, BigNumberish>

@ -12,7 +12,7 @@ export function hexToUint8Array(hex: string): Uint8Array {
return arr
}
const UTF_8_DECODER = new TextDecoder()
const UTF_8_DECODER = new TextDecoder('utf-8')
/**
* Returns the URI representation of the content hash for supported codecs

@ -0,0 +1,13 @@
import { Token, Price } from '@uniswap/sdk-core'
import { tickToPrice } from '@uniswap/v3-sdk'
export function getTickToPrice(
baseToken: Token | undefined,
quoteToken: Token | undefined,
tick: number | undefined
): Price | undefined {
if (!baseToken || !quoteToken || tick === undefined) {
return undefined
}
return tickToPrice(baseToken, quoteToken, tick)
}

@ -1,9 +1,11 @@
import { FeeAmount } from '@uniswap/v3-sdk'
import { Contract } from '@ethersproject/contracts'
import { getAddress } from '@ethersproject/address'
import { AddressZero } from '@ethersproject/constants'
import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
import { BigNumber } from '@ethersproject/bignumber'
import { abi as IUniswapV2Router02ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router02.json'
import { ROUTER_ADDRESS } from '../constants'
import { ChainId, Percent, Token, CurrencyAmount, Currency, ETHER } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk'
@ -123,3 +125,7 @@ export function supportedChainId(chainId: number): ChainId | undefined {
}
return undefined
}
export const formattedFeeAmount = (feeAmount: FeeAmount): number => {
return feeAmount / 10000
}

@ -2,7 +2,13 @@ import { supportedChainId } from 'utils'
import { ChainId, Currency, CurrencyAmount, ETHER, Token, TokenAmount, WETH9 } from '@uniswap/sdk-core'
export function wrappedCurrency(currency: Currency | undefined, chainId: ChainId | undefined): Token | undefined {
return chainId && currency === ETHER ? WETH9[chainId] : currency instanceof Token ? currency : undefined
return chainId && currency === ETHER
? chainId === 1337
? new Token(1337, '0xbBca0fFBFE60F60071630A8c80bb6253dC9D6023', 18, 'WETH', 'WETH9')
: WETH9[chainId]
: currency instanceof Token
? currency
: undefined
}
export function wrappedCurrencyAmount(

5657
yarn.lock

File diff suppressed because it is too large Load Diff