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

1
.env

@ -1,2 +1,3 @@
REACT_APP_CHAIN_ID="1" 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', () => { describe('Add Liquidity', () => {
it('loads the two correct tokens', () => { 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-tokena .token-symbol-container').should('contain.text', 'MKR')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH') cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH')
}) })
it('does not crash if ETH is duplicated', () => { 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-tokena .token-symbol-container').should('contain.text', 'ETH')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH') cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH')
}) })
it('token not in storage is loaded', () => { 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-tokena .token-symbol-container').should('contain.text', 'SKL')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR') 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.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR') 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/styled-components": "^5.1.0",
"@types/testing-library__cypress": "^5.0.5", "@types/testing-library__cypress": "^5.0.5",
"@types/wcag-contrast": "^3.0.0", "@types/wcag-contrast": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^4.20.0", "@typescript-eslint/eslint-plugin": "^4.1.0",
"@typescript-eslint/parser": "^4.20.0", "@typescript-eslint/parser": "^4.1.0",
"@uniswap/default-token-list": "^2.0.0", "@uniswap/default-token-list": "^2.0.0",
"@uniswap/governance": "^1.0.2", "@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2", "@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1", "@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/token-lists": "^1.0.0-beta.19",
"@uniswap/v2-core": "1.0.0", "@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0", "@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v2-sdk": "^1.0.6", "@uniswap/v2-sdk": "^1.0.6",
"@uniswap/v3-core": "^1.0.0-rc.0", "@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/core": "^6.0.9",
"@web3-react/fortmatic-connector": "^6.0.9", "@web3-react/fortmatic-connector": "^6.0.9",
"@web3-react/injected-connector": "^6.0.7", "@web3-react/injected-connector": "^6.0.7",
@ -75,8 +76,8 @@
"lightweight-charts": "^3.3.0", "lightweight-charts": "^3.3.0",
"lodash.flatmap": "^4.5.0", "lodash.flatmap": "^4.5.0",
"luxon": "^1.25.0", "luxon": "^1.25.0",
"multicodec": "^2.0.0", "multicodec": "^3.0.1",
"multihashes": "^3.0.1", "multihashes": "^4.0.2",
"node-vibrant": "^3.1.5", "node-vibrant": "^3.1.5",
"polished": "^3.3.2", "polished": "^3.3.2",
"prettier": "^2.2.1", "prettier": "^2.2.1",
@ -92,7 +93,7 @@
"react-popper": "^2.2.3", "react-popper": "^2.2.3",
"react-redux": "^7.2.2", "react-redux": "^7.2.2",
"react-router-dom": "^5.0.0", "react-router-dom": "^5.0.0",
"react-scripts": "^4.0.2", "react-scripts": "^4.0.3",
"react-spring": "^8.0.27", "react-spring": "^8.0.27",
"react-use-gesture": "^6.0.14", "react-use-gesture": "^6.0.14",
"react-virtualized-auto-sizer": "^1.0.2", "react-virtualized-auto-sizer": "^1.0.2",

@ -91,14 +91,14 @@
"selectFee": "Select Fee", "selectFee": "Select Fee",
"selectLiquidityRange": "Select Liquidity Range", "selectLiquidityRange": "Select Liquidity Range",
"selectPool": "Select Fee Tier", "selectPool": "Select Fee Tier",
"inputTokens": "Input Tokens", "depositAmounts": "Deposit Amounts",
"fee": "fee", "fee": "fee",
"setLimits": "Set Limits", "setLimits": "Set Limits",
"percent": "Percent", "percent": "Percent",
"rate": "Rate", "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.", "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", "connectWallet": "Connect Wallet",
"unsupportedAsset": "Unsupported Asset", "unsupportedAsset": "Unsupported Asset",
"feePool": "Fee Pool", "feePool": "Fee Pool",
@ -109,6 +109,8 @@
"poolType": "Select a fee tier based on your preferred liquidity provider fee.", "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.", "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.", "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", "selectRange": "Select Liquidity Range",
"inputTokenDynamic": "Input {{label}}" "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; text-decoration: underline;
} }
&:hover { &:hover {
text-decoration: underline; // text-decoration: underline;
opacity: 0.9;
} }
&:active { &:active {
text-decoration: underline; text-decoration: underline;
@ -377,6 +378,9 @@ const Circle = styled.div`
const CheckboxWrapper = styled.div` const CheckboxWrapper = styled.div`
width: 30px; width: 30px;
padding: 0 10px; padding: 0 10px;
position: absolute;
top: 10px;
right: 10px;
` `
export function ButtonRadioChecked({ active = false, children, ...rest }: { active?: boolean } & ButtonProps) { export function ButtonRadioChecked({ active = false, children, ...rest }: { active?: boolean } & ButtonProps) {

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

@ -15,6 +15,8 @@ import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useTheme from '../../hooks/useTheme' import useTheme from '../../hooks/useTheme'
import { Lock } from 'react-feather'
import { AutoColumn } from 'components/Column'
const InputPanel = styled.div<{ hideInput?: boolean }>` const InputPanel = styled.div<{ hideInput?: boolean }>`
${({ theme }) => theme.flexColumnNoWrap} ${({ theme }) => theme.flexColumnNoWrap}
@ -25,6 +27,19 @@ const InputPanel = styled.div<{ hideInput?: boolean }>`
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')}; 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 }>` const Container = styled.div<{ hideInput: boolean }>`
border-radius: ${({ hideInput }) => (hideInput ? '12px' : '20px')}; border-radius: ${({ hideInput }) => (hideInput ? '12px' : '20px')};
border: 1px solid ${({ theme }) => theme.bg2}; border: 1px solid ${({ theme }) => theme.bg2};
@ -134,6 +149,7 @@ interface CurrencyInputPanelProps {
id: string id: string
showCommonBases?: boolean showCommonBases?: boolean
customBalanceText?: string customBalanceText?: string
locked?: boolean
} }
export default function CurrencyInputPanel({ export default function CurrencyInputPanel({
@ -144,14 +160,15 @@ export default function CurrencyInputPanel({
label = 'Input', label = 'Input',
onCurrencySelect, onCurrencySelect,
currency, currency,
disableCurrencySelect = false,
hideBalance = false,
pair = null, // used for double token logo
hideInput = false,
otherCurrency, otherCurrency,
id, id,
showCommonBases, showCommonBases,
customBalanceText, customBalanceText,
disableCurrencySelect = false,
hideBalance = false,
pair = null, // used for double token logo
hideInput = false,
locked = false,
...rest ...rest
}: CurrencyInputPanelProps) { }: CurrencyInputPanelProps) {
const { t } = useTranslation() const { t } = useTranslation()
@ -167,6 +184,14 @@ export default function CurrencyInputPanel({
return ( return (
<InputPanel id={id} hideInput={hideInput} {...rest}> <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}> <Container hideInput={hideInput}>
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableCurrencySelect}> <InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableCurrencySelect}>
{!hideInput && ( {!hideInput && (

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

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

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import { Text } from 'rebass' 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 styled from 'styled-components'
import { SUGGESTED_BASES } from '../../constants' import { SUGGESTED_BASES } from '../../constants'

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

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

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

@ -4,6 +4,14 @@ import JSBI from 'jsbi'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors' 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 ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
@ -12,7 +20,7 @@ export { PRELOADED_PROPOSALS } from './proposals'
// a list of tokens by chain // a list of tokens by chain
type ChainTokenList = { 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') 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.RINKEBY]: [WETH9[ChainId.RINKEBY]],
[ChainId.GÖRLI]: [WETH9[ChainId.GÖRLI]], [ChainId.GÖRLI]: [WETH9[ChainId.GÖRLI]],
[ChainId.KOVAN]: [WETH9[ChainId.KOVAN]], [ChainId.KOVAN]: [WETH9[ChainId.KOVAN]],
[1337]: [WETH9[ChainId.KOVAN]],
} }
// used to construct intermediary pairs for trading // used to construct intermediary pairs for trading

@ -102,6 +102,23 @@
"internalType": "bytes32", "internalType": "bytes32",
"name": "blockHash", "name": "blockHash",
"type": "bytes32" "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", "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, "decimals": 6,
"chainId": 1, "chainId": 1,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4922a015c4407F87432B179bb209e125432E4a2A/logo.png" "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 } = { export const FACTORY_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '', [ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '', [ChainId.ROPSTEN]: '0xb31b9A7b331eA8993bdfC67c650eDbfc9256eC62',
[ChainId.RINKEBY]: '', [ChainId.RINKEBY]: '0xD8A6adFB40Ba3B3CdA9F985BF1fdbDc0c1d7591e',
[ChainId.GÖRLI]: '0xb31b9A7b331eA8993bdfC67c650eDbfc9256eC62', [ChainId.GÖRLI]: '0xb31b9A7b331eA8993bdfC67c650eDbfc9256eC62',
[ChainId.KOVAN]: '', [ChainId.KOVAN]: '',
} }
export const TICK_LENS_ADDRESSES: { [chainId in ChainId]: string } = { export const TICK_LENS_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '', [ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '', [ChainId.ROPSTEN]: '0x8E984b597F19E8D0FDd0b5bAfDb1d0ae4386455f',
[ChainId.RINKEBY]: '', [ChainId.RINKEBY]: '0xB1c59e8Ae4B72f63a5a9CB9c25A9253096A4b126',
[ChainId.GÖRLI]: '0x8E984b597F19E8D0FDd0b5bAfDb1d0ae4386455f', [ChainId.GÖRLI]: '0x8E984b597F19E8D0FDd0b5bAfDb1d0ae4386455f',
[ChainId.KOVAN]: '', [ChainId.KOVAN]: '',
} }
export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: { [chainId in ChainId]: string } = { export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '', [ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '', [ChainId.ROPSTEN]: '0x29e4bF3bFD649b807B4C752c01023E535094F6Bc',
[ChainId.RINKEBY]: '', [ChainId.RINKEBY]: '0xee9e30637f84Bbf929042A9118c6E20023dab833',
[ChainId.GÖRLI]: '0x29e4bF3bFD649b807B4C752c01023E535094F6Bc', [ChainId.GÖRLI]: '0x29e4bF3bFD649b807B4C752c01023E535094F6Bc',
[ChainId.KOVAN]: '', [ChainId.KOVAN]: '',
} }
export const NONFUNGIBLE_TOKEN_POSITION_DESCRIPTOR_ADDRESSES: { [chainId in ChainId]: string } = { export const NONFUNGIBLE_TOKEN_POSITION_DESCRIPTOR_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '', [ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '', [ChainId.ROPSTEN]: '0xa0588c89Fe967e66533aB1A0504C30989f90156f',
[ChainId.RINKEBY]: '', [ChainId.RINKEBY]: '0x3431b9Ed12e3204bC6f7039e1c576417B70fdD67',
[ChainId.GÖRLI]: '0xa0588c89Fe967e66533aB1A0504C30989f90156f', [ChainId.GÖRLI]: '0xa0588c89Fe967e66533aB1A0504C30989f90156f',
[ChainId.KOVAN]: '', [ChainId.KOVAN]: '',
} }
export const SWAP_ROUTER_ADDRESSES: { [chainId in ChainId]: string } = { export const SWAP_ROUTER_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '', [ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '', [ChainId.ROPSTEN]: '0x71bB3d0e63f2Fa2A5d04d54267211f4Caef7062e',
[ChainId.RINKEBY]: '', [ChainId.RINKEBY]: '0xa0588c89Fe967e66533aB1A0504C30989f90156f',
[ChainId.GÖRLI]: '0x71bB3d0e63f2Fa2A5d04d54267211f4Caef7062e', [ChainId.GÖRLI]: '0x71bB3d0e63f2Fa2A5d04d54267211f4Caef7062e',
[ChainId.KOVAN]: '', [ChainId.KOVAN]: '',
} }
@ -43,7 +43,7 @@ export const SWAP_ROUTER_ADDRESSES: { [chainId in ChainId]: string } = {
export const V2_MIGRATOR_ADDRESSES: { [chainId in ChainId]: string } = { export const V2_MIGRATOR_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '', [ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '', [ChainId.ROPSTEN]: '',
[ChainId.RINKEBY]: '', [ChainId.RINKEBY]: '0xb31b9A7b331eA8993bdfC67c650eDbfc9256eC62',
[ChainId.GÖRLI]: '0xee9e30637f84Bbf929042A9118c6E20023dab833', [ChainId.GÖRLI]: '0xee9e30637f84Bbf929042A9118c6E20023dab833',
[ChainId.KOVAN]: '', [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 { Pair } from '@uniswap/v2-sdk'
import { TokenAmount, Currency } from '@uniswap/sdk-core'
import { useMemo } from 'react' import { useMemo } from 'react'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json' import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { Interface } from '@ethersproject/abi' import { Interface } from '@ethersproject/abi'
@ -7,6 +6,7 @@ import { useActiveWeb3React } from '../hooks'
import { useMultipleContractSingleData } from '../state/multicall/hooks' import { useMultipleContractSingleData } from '../state/multicall/hooks'
import { wrappedCurrency } from '../utils/wrappedCurrency' import { wrappedCurrency } from '../utils/wrappedCurrency'
import { Currency, TokenAmount } from '@uniswap/sdk-core'
const PAIR_INTERFACE = new Interface(IUniswapV2PairABI) const PAIR_INTERFACE = new Interface(IUniswapV2PairABI)

@ -5,7 +5,7 @@ import flatMap from 'lodash.flatmap'
import { useMemo } from 'react' import { useMemo } from 'react'
import { BASES_TO_CHECK_TRADES_AGAINST, CUSTOM_BASES, BETTER_TRADE_LESS_HOPS_THRESHOLD } from '../constants' 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 { wrappedCurrency } from '../utils/wrappedCurrency'
import { useActiveWeb3React } from './index' 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 { useMemo } from 'react'
import { Result, useSingleContractMultipleData } from 'state/multicall/hooks' import { Result, useSingleCallResult, useSingleContractMultipleData } from 'state/multicall/hooks'
import { useTickLens } from './useContract' import { useTickLens, useV3Factory } from './useContract'
// the following should probably all be from the sdk, just mocking it for now // the following should probably all be from the sdk, just mocking it for now
function MIN_TICK(tickSpacing: number) { function MIN_TICK(tickSpacing: number) {
@ -25,8 +28,9 @@ interface TickData {
} }
export function useAllV3Ticks( export function useAllV3Ticks(
poolAddress: string, token0: Token | undefined,
tickSpacing: number token1: Token | undefined,
feeAmount: FeeAmount | undefined
): { ): {
loading: boolean loading: boolean
syncing: boolean syncing: boolean
@ -34,22 +38,35 @@ export function useAllV3Ticks(
valid: boolean valid: boolean
tickData: TickData[] tickData: TickData[]
} { } {
const tickLens = useTickLens() const tickSpacing = useMemo(() => (feeAmount ? TICK_SPACINGS[feeAmount] : undefined), [feeAmount])
const minIndex = useMemo(() => bitmapIndex(MIN_TICK(tickSpacing), tickSpacing), [tickSpacing]) const minIndex = useMemo(() => (tickSpacing ? bitmapIndex(MIN_TICK(tickSpacing), tickSpacing) : undefined), [
const maxIndex = useMemo(() => bitmapIndex(MAX_TICK(tickSpacing), tickSpacing), [tickSpacing]) 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) maxIndex && minIndex && poolAddress && poolAddress !== ZERO_ADDRESS
? new Array(maxIndex - minIndex + 1)
.fill(0) .fill(0)
.map((_, i) => i + minIndex) .map((_, i) => i + minIndex)
.map((wordIndex) => [poolAddress, wordIndex]), .map((wordIndex) => [poolAddress, wordIndex])
: [],
[minIndex, maxIndex, poolAddress] [minIndex, maxIndex, poolAddress]
) )
const tickLens = useTickLens()
const callStates = useSingleContractMultipleData( const callStates = useSingleContractMultipleData(
tickLens, tickLensArgs.length > 0 ? tickLens : undefined,
'getPopulatedTicksInWord', 'getPopulatedTicksInWord',
tickLensArgs, tickLensArgs,
REFRESH_FREQUENCY, REFRESH_FREQUENCY,

@ -1,3 +1,4 @@
import { UniswapV3Pool } from './../types/v3/UniswapV3Pool.d'
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { abi as GOVERNANCE_ABI } from '@uniswap/governance/build/GovernorAlpha.json' import { abi as GOVERNANCE_ABI } from '@uniswap/governance/build/GovernorAlpha.json'
import { abi as UNI_ABI } from '@uniswap/governance/build/Uni.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 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 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 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 ARGENT_WALLET_DETECTOR_ABI from 'abis/argent-wallet-detector.json'
import ENS_PUBLIC_RESOLVER_ABI from 'abis/ens-public-resolver.json' import ENS_PUBLIC_RESOLVER_ABI from 'abis/ens-public-resolver.json'
@ -24,8 +26,9 @@ import {
MERKLE_DISTRIBUTOR_ADDRESS, MERKLE_DISTRIBUTOR_ADDRESS,
V1_MIGRATOR_ADDRESS, V1_MIGRATOR_ADDRESS,
UNI, UNI,
MULTICALL_ADDRESSES,
} from 'constants/index' } 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 { 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 { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, FACTORY_ADDRESSES, TICK_LENS_ADDRESSES } from 'constants/v3'
import { useMemo } from 'react' import { useMemo } from 'react'
@ -35,7 +38,7 @@ import { getContract } from 'utils'
import { useActiveWeb3React } from './index' import { useActiveWeb3React } from './index'
// returns null on errors // 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() const { library, account } = useActiveWeb3React()
return useMemo(() => { return useMemo(() => {
@ -65,10 +68,10 @@ export function useV1ExchangeContract(address?: string, withSignerIfPossible?: b
export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: boolean): Contract | null { export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: boolean): Contract | null {
return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible) return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible)
} }
export function useWETHContract(withSignerIfPossible?: boolean): Contract | null { export function useWETHContract(withSignerIfPossible?: boolean): Contract | null {
const { chainId } = useActiveWeb3React() 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 { export function useArgentWalletDetectorContract(): Contract | null {
@ -110,7 +113,7 @@ export function usePairContract(pairAddress?: string, withSignerIfPossible?: boo
export function useMulticallContract(): Contract | null { export function useMulticallContract(): Contract | null {
const { chainId } = useActiveWeb3React() 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 { export function useMerkleDistributorContract(): Contract | null {
@ -152,6 +155,10 @@ export function useV3Factory(): UniswapV3Factory | null {
return useContract(address, V3FactoryABI) as 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 { export function useTickLens(): TickLens | null {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const address = chainId ? TICK_LENS_ADDRESSES[chainId] : undefined 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 { JSBI } from '@uniswap/v2-sdk'
import { useMemo } from 'react' import { useMemo } from 'react'
import { USDC } from '../constants' import { USDC } from '../constants'
import { PairState, usePairs } from '../data/Reserves' import { PairState, usePairs } from '../data/V2'
import { useActiveWeb3React } from '../hooks' import { useActiveWeb3React } from '../hooks'
import { wrappedCurrency } from './wrappedCurrency' import { wrappedCurrency } from '../utils/wrappedCurrency'
/** /**
* Returns the price in USDC of the input currency * Returns the price in USDC of the input currency

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

@ -1,32 +1,29 @@
import { BigNumber } from '@ethersproject/bignumber'
import { TransactionResponse } from '@ethersproject/providers' import { TransactionResponse } from '@ethersproject/providers'
import { Currency, ETHER, TokenAmount } from '@uniswap/sdk-core' import { Currency, TokenAmount, Token, Percent, ETHER } from '@uniswap/sdk-core'
import React, { useCallback, useContext, useState } from 'react' import React, { useCallback, useContext, useState, useMemo } from 'react'
import { Link2, AlertTriangle, LifeBuoy, Circle } from 'react-feather' import { Link2, AlertTriangle } from 'react-feather'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { useV3NFTPositionManagerContract } from '../../hooks/useContract'
import { RouteComponentProps } from 'react-router-dom' import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { ButtonError, ButtonLight, ButtonPrimary, ButtonRadioChecked, ButtonText } from '../../components/Button' import { ButtonError, ButtonLight, ButtonPrimary, ButtonRadioChecked, ButtonText } from '../../components/Button'
import { YellowCard } from '../../components/Card' import { YellowCard, OutlineCard, BlueCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column' import { AutoColumn, ColumnCenter } from '../../components/Column'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal' import { TransactionSubmittedContent, ConfirmationPendingContent } from '../../components/TransactionConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { RowBetween, RowFixed, AutoRow } from '../../components/Row' import { AutoRow, RowBetween } from '../../components/Row'
import ConfirmContent from './ConfirmContent' import Review from './Review'
import { ROUTER_ADDRESS } from '../../constants'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useCurrency } from '../../hooks/Tokens' import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback' import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import useTransactionDeadline from '../../hooks/useTransactionDeadline' import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { useWalletModalToggle } from '../../state/application/hooks' import { useWalletModalToggle } from '../../state/application/hooks'
import { Field, Bound, RangeType } from '../../state/mint/actions' import { Field, Bound } from '../../state/mint/actions'
import { useDerivedMintInfo, useMintActionHandlers, useMintState } from '../../state/mint/hooks'
import { useTransactionAdder } from '../../state/transactions/hooks' import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserSlippageTolerance } from '../../state/user/hooks' import { useIsExpertMode, useUserSlippageTolerance } from '../../state/user/hooks'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
import { maxAmountSpend } from '../../utils/maxAmountSpend' import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { wrappedCurrency } from '../../utils/wrappedCurrency' import { wrappedCurrency } from '../../utils/wrappedCurrency'
import AppBody from '../AppBody' import AppBody from '../AppBody'
@ -39,58 +36,71 @@ import StepCounter from 'components/InputStepCounter/InputStepCounter'
import { import {
DynamicSection, DynamicSection,
CurrencyDropdown, CurrencyDropdown,
ScrollablePage,
ScrollableContent, ScrollableContent,
StyledInput,
FixedPreview, FixedPreview,
PreviewCard, ScrollablePage,
} from './styled' } from './styled'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import CurrencyLogo from 'components/CurrencyLogo' import { useMintState, useMintActionHandlers, useDerivedMintInfo } from 'state/mint/hooks'
import QuestionHelper from 'components/QuestionHelper' import { FeeAmount, NonfungiblePositionManager } from '@uniswap/v3-sdk'
import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES } from 'constants/v3'
import JSBI from 'jsbi'
export default function AddLiquidity({ export default function AddLiquidity({
match: { match: {
params: { currencyIdA, currencyIdB }, params: { currencyIdA, currencyIdB, feeAmount: feeAmountFromUrl },
}, },
history, history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) { }: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string; feeAmount?: string }>) {
const { t } = useTranslation() const { t } = useTranslation()
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, library } = useActiveWeb3React()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const toggleWalletModal = useWalletModalToggle() // toggle wallet when disconnected
const expertMode = useIsExpertMode()
const addTransaction = useTransactionAdder()
const positionManager = useV3NFTPositionManagerContract()
// fee selection from url
const feeAmount =
feeAmountFromUrl && Object.values(FeeAmount).includes(parseFloat(feeAmountFromUrl))
? parseFloat(feeAmountFromUrl)
: undefined
const currencyA = useCurrency(currencyIdA) const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB) 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 // mint state
const { independentField, typedValue, otherTypedValue, rangeType } = useMintState() const { independentField, typedValue, startPriceTypedValue } = useMintState()
const { const {
dependentField,
currencies,
pair,
// pairState,
currencyBalances,
parsedAmounts,
ticks, ticks,
dependentField,
price, price,
pricesAtTicks,
parsedAmounts,
currencyBalances,
position,
noLiquidity, noLiquidity,
poolTokenPercentage, currencies,
error, errorMessage,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined) invalidPool,
invalidRange,
outOfRange,
depositADisabled,
depositBDisabled,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined, feeAmount)
const { onFieldAInput, onFieldBInput, onLowerRangeInput, onUpperRangeInput } = useMintActionHandlers(noLiquidity) const {
onFieldAInput,
onFieldBInput,
onLowerRangeInput,
onUpperRangeInput,
onStartPriceInput,
} = useMintActionHandlers(noLiquidity)
const isValid = !error const isValid = !errorMessage
// modal and loading // modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false) const [showConfirm, setShowConfirm] = useState<boolean>(false)
@ -99,12 +109,13 @@ export default function AddLiquidity({
// txn values // txn values
const deadline = useTransactionDeadline() // custom from users settings const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const fractionalizedTolerance = new Percent(JSBI.BigInt(allowedSlippage), JSBI.BigInt(10000))
const [txHash, setTxHash] = useState<string>('') const [txHash, setTxHash] = useState<string>('')
// get formatted amounts // get formatted amounts
const formattedAmounts = { const formattedAmounts = {
[independentField]: typedValue, [independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '', [dependentField]: parsedAmounts[dependentField]?.toSignificant(6) ?? '',
} }
// get the max amounts user can add // get the max amounts user can add
@ -129,88 +140,73 @@ export default function AddLiquidity({
) )
// check whether the user has approved the router on the tokens // check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS) const [approvalA, approveACallback] = useApproveCallback(
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS) parsedAmounts[Field.CURRENCY_A],
chainId ? NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId] : undefined
const addTransaction = useTransactionAdder() )
const [approvalB, approveBCallback] = useApproveCallback(
parsedAmounts[Field.CURRENCY_B],
chainId ? NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId] : undefined
)
async function onAdd() { async function onAdd() {
if (!chainId || !library || !account) return if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts if (!positionManager || !currencyA || !currencyB) {
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB || !deadline) {
return return
} }
const amountsMin = { if (position && account && deadline && fractionalizedTolerance) {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0], const { calldata, value } = NonfungiblePositionManager.mintCallParameters(position, {
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0], slippageTolerance: fractionalizedTolerance,
} recipient: account,
deadline: deadline.toNumber(),
useEther: currencyA === ETHER || currencyB === ETHER,
createPool: noLiquidity,
})
let estimate, const txn = {
method: (...args: any) => Promise<TransactionResponse>, to: NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId],
args: Array<string | string[] | number>, data: calldata,
value: BigNumber | null value,
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) setAttemptingTxn(true)
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
library
.getSigner()
.estimateGas(txn)
.then((estimate) => {
const newTxn = {
...txn,
gasLimit: estimate,
}
library
.getSigner()
.sendTransaction(newTxn)
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, { addTransaction(response, {
summary: summary: noLiquidity
'Add ' + ? 'Create Pool + '
parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) + : '' + 'Add ' + !depositADisabled
? parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
' ' + ' ' +
currencies[Field.CURRENCY_A]?.symbol + currencies[Field.CURRENCY_A]?.symbol +
' and ' + !outOfRange
parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) + : ''
' ' + ? ' and '
currencies[Field.CURRENCY_B]?.symbol, : '' + !depositBDisabled
? parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) + ' ' + currencies[Field.CURRENCY_B]?.symbol
: '',
}) })
setTxHash(response.hash) setTxHash(response.hash)
ReactGA.event({ ReactGA.event({
category: 'Liquidity', category: 'Liquidity',
action: 'Add', action: 'Add',
label: [currencies[Field.CURRENCY_A]?.symbol, currencies[Field.CURRENCY_B]?.symbol].join('/'), label: [currencies[Field.CURRENCY_A]?.symbol, currencies[Field.CURRENCY_B]?.symbol].join('/'),
}) })
}) })
)
.catch((error) => { .catch((error) => {
setAttemptingTxn(false) setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx // we only care if the error is something _other_ than the user rejected the tx
@ -218,24 +214,17 @@ export default function AddLiquidity({
console.error(error) console.error(error)
} }
}) })
})
} else {
return
}
} }
const modalContent = () => { const pendingText = `Supplying ${!depositADisabled ? parsedAmounts[Field.CURRENCY_A]?.toSignificant(6) : ''} ${
return ( !depositADisabled ? currencies[Field.CURRENCY_A]?.symbol : ''
<ConfirmContent } ${!outOfRange ? 'and' : ''} ${!depositBDisabled ? parsedAmounts[Field.CURRENCY_B]?.toSignificant(6) : ''} ${
price={price} !depositBDisabled ? currencies[Field.CURRENCY_B]?.symbol : ''
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( const handleCurrencyASelect = useCallback(
(currencyA: Currency) => { (currencyA: Currency) => {
@ -243,7 +232,7 @@ export default function AddLiquidity({
if (newCurrencyIdA === currencyIdB) { if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`) history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else { } else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`) history.push(`/add/${newCurrencyIdA}/${currencyIdB ?? 'ETH'}`)
} }
}, },
[currencyIdB, history, currencyIdA] [currencyIdB, history, currencyIdA]
@ -258,12 +247,19 @@ export default function AddLiquidity({
history.push(`/add/${newCurrencyIdB}`) history.push(`/add/${newCurrencyIdB}`)
} }
} else { } else {
history.push(`/add/${currencyIdA ? currencyIdA : 'ETH'}/${newCurrencyIdB}`) history.push(`/add/${currencyIdA ?? 'ETH'}/${newCurrencyIdB}`)
} }
}, },
[currencyIdA, history, currencyIdB] [currencyIdA, history, currencyIdB]
) )
const handleFeePoolSelect = useCallback(
(newFeeAmount: FeeAmount) => {
history.push(`/add/${currencyIdA}/${currencyIdB}/${newFeeAmount}`)
},
[currencyIdA, currencyIdB, history]
)
const handleDismissConfirmation = useCallback(() => { const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false) setShowConfirm(false)
// if there was a tx hash, we want to clear the input // if there was a tx hash, we want to clear the input
@ -277,13 +273,6 @@ export default function AddLiquidity({
const addIsUnsupported = useIsTransactionUnsupported(currencies?.CURRENCY_A, currencies?.CURRENCY_B) const addIsUnsupported = useIsTransactionUnsupported(currencies?.CURRENCY_A, currencies?.CURRENCY_B)
/**
*
* dummy values for v3 prototype
*
*/
const [feeLevel, setFeeLevel] = useState<number>(4)
const clearAll = useCallback(() => { const clearAll = useCallback(() => {
onFieldAInput('') onFieldAInput('')
onFieldBInput('') onFieldBInput('')
@ -292,38 +281,96 @@ export default function AddLiquidity({
history.push(`/add/`) history.push(`/add/`)
}, [history, onFieldAInput, onFieldBInput, onLowerRangeInput, onUpperRangeInput]) }, [history, onFieldAInput, onFieldBInput, onLowerRangeInput, onUpperRangeInput])
const currentTick = ticks[Bound.CURRENT] // get value and prices at ticks
const lowerTick = ticks[Bound.LOWER] const { [Bound.LOWER]: tickLower, [Bound.UPPER]: tickUpper } = ticks
const upperTick = ticks[Bound.UPPER] const { [Bound.LOWER]: priceLower, [Bound.UPPER]: priceUpper } = pricesAtTicks
const [rateCurrencyBase, setRateCurrencyBase] = useState<Currency | null | undefined>(currencyA) // used sort sorted toggle
const tokenA = wrappedCurrency(currencyA ?? undefined, chainId)
const tokenB = wrappedCurrency(currencyB ?? undefined, chainId)
const sortedTokens: Token[] | undefined = useMemo(
() =>
tokenA && tokenB && !tokenA.equals(tokenB)
? tokenA.sortsBefore(tokenB)
? [tokenA, tokenB]
: [tokenB, tokenA]
: undefined,
[tokenA, tokenB]
)
const invalidRange = lowerTick && upperTick && lowerTick.rate > upperTick.rate const handleRateToggle = useCallback(() => {
const outOfRange = if (currencyA && currencyB) {
currentTick && const currencyIdA = currencyId(currencyA)
lowerTick && const currencyIdB = currencyId(currencyB)
upperTick && // reset inputs
!invalidRange && onLowerRangeInput('')
(lowerTick.rate > currentTick.rate || upperTick.rate < currentTick.rate) onUpperRangeInput('')
onStartPriceInput('')
onFieldAInput('')
onFieldBInput('')
history.push(`/add/${currencyIdB}/${currencyIdA}/${feeAmount ?? ''}`)
}
}, [
currencyA,
currencyB,
feeAmount,
history,
onFieldAInput,
onFieldBInput,
onLowerRangeInput,
onStartPriceInput,
onUpperRangeInput,
])
const RateToggle = () => {
return sortedTokens && currencyB && currencyA ? (
<ToggleWrapper width="fit-content">
<ToggleElement isActive={tokenA === sortedTokens[0]} fontSize="12px" onClick={handleRateToggle}>
{tokenA === sortedTokens[0] ? currencyB.symbol : currencyA?.symbol} {t('rate')}
</ToggleElement>
<ToggleElement isActive={tokenB === sortedTokens[0]} fontSize="12px" onClick={handleRateToggle}>
{tokenB === sortedTokens[0] ? currencyB.symbol : currencyA?.symbol} {t('rate')}
</ToggleElement>
</ToggleWrapper>
) : null
}
return ( return (
<ScrollablePage> <ScrollablePage>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={'Review Position'}
onDismiss={handleDismissConfirmation}
topContent={modalContent}
/>
)}
pendingText={pendingText}
currencyToAdd={pair?.liquidityToken}
/>
<ScrollableContent> <ScrollableContent>
<AutoRow marginBottom="20px">
<ButtonText opacity={'0.4'} onClick={() => history.push('/pool')}>
Pool
</ButtonText>
<TYPE.label margin="0 10px" opacity={'0.4'}>
{' > '}
</TYPE.label>
<ButtonText opacity={showConfirm ? '0.4' : '1'} onClick={() => (showConfirm ? setShowConfirm(false) : null)}>
Configure
</ButtonText>
<TYPE.label margin="0 10px" opacity={'0.4'}>
{' > '}
</TYPE.label>
<ButtonText
opacity={showConfirm ? '1' : '0.4'}
onClick={() => (!showConfirm ? setShowConfirm(true) : null)}
disabled={!isValid}
>
Review
</ButtonText>
</AutoRow>
{showConfirm ? (
<AppBody>
<Review
currencies={currencies}
parsedAmounts={parsedAmounts}
position={position}
priceLower={priceLower}
priceUpper={priceUpper}
outOfRange={outOfRange}
/>
</AppBody>
) : (
<AppBody> <AppBody>
<Wrapper> <Wrapper>
<AutoColumn gap="40px"> <AutoColumn gap="40px">
@ -334,9 +381,9 @@ export default function AddLiquidity({
<TYPE.blue fontSize="12px">Clear All</TYPE.blue> <TYPE.blue fontSize="12px">Clear All</TYPE.blue>
</ButtonText> </ButtonText>
</RowBetween> </RowBetween>
<TYPE.main fontWeight={400} fontSize="14px"> {/* <TYPE.main fontWeight={400} fontSize="14px">
{t('selectAPool')} {t('selectAPool')}
</TYPE.main> </TYPE.main> */}
<RowBetween> <RowBetween>
<CurrencyDropdown <CurrencyDropdown
value={formattedAmounts[Field.CURRENCY_A]} value={formattedAmounts[Field.CURRENCY_A]}
@ -367,106 +414,158 @@ export default function AddLiquidity({
/> />
</RowBetween> </RowBetween>
</AutoColumn> </AutoColumn>
<AutoColumn gap="16px"> <AutoColumn gap="16px">
<DynamicSection gap="md" disabled={!currencyB || !currencyA}> <DynamicSection gap="md" disabled={!currencyB || !currencyA}>
<TYPE.label>{t('selectPool')}</TYPE.label> <TYPE.label>{t('selectPool')}</TYPE.label>
<TYPE.main fontWeight={400} fontSize="14px"> {/* <TYPE.main fontWeight={400} fontSize="14px">
{t('poolType')} {t('poolType')}
</TYPE.main> </TYPE.main> */}
<RowBetween> <RowBetween>
<ButtonRadioChecked width="32%" active={feeLevel === 0} onClick={() => setFeeLevel(0)}> <ButtonRadioChecked
<TYPE.label>0.1% {t('fee')}</TYPE.label> width="32%"
active={feeAmount === FeeAmount.LOW}
onClick={() => handleFeePoolSelect(FeeAmount.LOW)}
>
<AutoColumn gap="sm" justify="flex-start">
<TYPE.label>0.05% {t('fee')}</TYPE.label>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
Optimized for stable assets.
</TYPE.main>
</AutoColumn>
</ButtonRadioChecked> </ButtonRadioChecked>
<ButtonRadioChecked active={feeLevel === 1} width="32%" onClick={() => setFeeLevel(1)}> <ButtonRadioChecked
width="32%"
active={feeAmount === FeeAmount.MEDIUM}
onClick={() => handleFeePoolSelect(FeeAmount.MEDIUM)}
>
<AutoColumn gap="sm" justify="flex-start">
<TYPE.label>0.3% {t('fee')}</TYPE.label> <TYPE.label>0.3% {t('fee')}</TYPE.label>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
The classic Uniswap pool fee.
</TYPE.main>
</AutoColumn>
</ButtonRadioChecked> </ButtonRadioChecked>
<ButtonRadioChecked width="32%" active={feeLevel === 2} onClick={() => setFeeLevel(2)}> <ButtonRadioChecked
<TYPE.label>0.5% {t('fee')}</TYPE.label> width="32%"
active={feeAmount === FeeAmount.HIGH}
onClick={() => handleFeePoolSelect(FeeAmount.HIGH)}
>
<AutoColumn gap="sm" justify="flex-start">
<TYPE.label>1% {t('fee')}</TYPE.label>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
Best for volatile assets.
</TYPE.main>
</AutoColumn>
</ButtonRadioChecked> </ButtonRadioChecked>
</RowBetween> </RowBetween>
</DynamicSection> </DynamicSection>
</AutoColumn> </AutoColumn>
<DynamicSection gap="md" disabled={feeLevel >= 4}> {noLiquidity && (
<DynamicSection disabled={!currencyA || !currencyB}>
<AutoColumn gap="md">
<BlueCard width="100%" padding="1rem">
You are the first to provide liquidity to this pool.
</BlueCard>
<RowBetween>
<TYPE.label>{t('selectStartingPrice')}</TYPE.label>
{tokenA && tokenB && <RateToggle />}
</RowBetween>
{/* <TYPE.main fontWeight={400} fontSize="14px">
{t('newPoolPrice')}
</TYPE.main> */}
<OutlineCard padding="12px">
<StyledInput
className="start-price-input"
value={startPriceTypedValue}
onUserInput={onStartPriceInput}
/>
</OutlineCard>
<RowBetween style={{ backgroundColor: theme.bg6, padding: '12px', borderRadius: '12px' }}>
<TYPE.main>Starting Price</TYPE.main>
{price ? (
<TYPE.main>
1 {currencyA?.symbol} = {price?.toSignificant(8)} {currencyB?.symbol}
</TYPE.main>
) : (
'-'
)}
</RowBetween>
</AutoColumn>
</DynamicSection>
)}
<DynamicSection gap="md" disabled={!feeAmount || invalidPool || (noLiquidity && !startPriceTypedValue)}>
<RowBetween> <RowBetween>
<TYPE.label>{t('selectLiquidityRange')}</TYPE.label> <TYPE.label>{t('selectLiquidityRange')}</TYPE.label>
{currencyA && currencyB && ( {tokenA && tokenB && !noLiquidity && <RateToggle />}
<ToggleWrapper width="fit-content">
<ToggleElement
isActive={rateCurrencyBase === currencyA}
fontSize="12px"
onClick={() => setRateCurrencyBase(rateCurrencyBase === currencyB ? currencyA : currencyB)}
>
{currencyA.symbol} {t('rate')}
</ToggleElement>
<ToggleElement
fontSize="12px"
isActive={currencyB === rateCurrencyBase}
onClick={() => setRateCurrencyBase(rateCurrencyBase === currencyB ? currencyA : currencyB)}
>
{currencyB.symbol} Rate
</ToggleElement>
</ToggleWrapper>
)}
</RowBetween> </RowBetween>
<TYPE.main fontWeight={400} fontSize="14px"> {/* <TYPE.main fontWeight={400} fontSize="14px">
{t('rangeWarning')} {t('rangeWarning')}
</TYPE.main> </TYPE.main> */}
{price && rateCurrencyBase && ( {price && currencyA && !noLiquidity && (
<RowBetween style={{ backgroundColor: theme.bg3, padding: '8px', borderRadius: '12px' }}> <RowBetween style={{ backgroundColor: theme.bg6, padding: '12px', borderRadius: '12px' }}>
<TYPE.main>{t('currentRate', { label: rateCurrencyBase.symbol })}</TYPE.main> <TYPE.main>{t('currentRate', { label: currencyA.symbol })}</TYPE.main>
<TYPE.main> <TYPE.main>
{rateCurrencyBase === currencyA ? price.toSignificant(3) : price.invert().toSignificant(3)}{' '} {price.toSignificant(3)} {currencyB?.symbol}
{rateCurrencyBase === currencyB ? currencyA?.symbol : currencyB?.symbol}
</TYPE.main> </TYPE.main>
</RowBetween> </RowBetween>
)} )}
<RowBetween> <RowBetween>
<StepCounter <StepCounter
value={lowerTick?.rate?.toString() ?? ''} value={priceLower?.toSignificant(5) ?? ''}
onUserInput={onLowerRangeInput} onUserInput={onLowerRangeInput}
usePercent={rangeType === RangeType.PERCENT} width="48%"
prependSymbol={rangeType === RangeType.PERCENT ? '-' : undefined} label={
priceLower && currencyA && currencyB
? '1 ' + currencyA?.symbol + ' / ' + priceLower?.toSignificant(4) + ' ' + currencyB?.symbol
: '-'
}
/> />
<Link2 style={{ margin: '0 10px' }} size={40} />
<StepCounter <StepCounter
value={upperTick?.rate?.toString() ?? ''} value={priceUpper?.toSignificant(5) ?? ''}
onUserInput={onUpperRangeInput} onUserInput={onUpperRangeInput}
usePercent={rangeType === RangeType.PERCENT} width="48%"
prependSymbol={rangeType === RangeType.PERCENT ? '+' : undefined} label={
priceUpper && currencyA && currencyB
? '1 ' + currencyA?.symbol + ' / ' + priceUpper?.toSignificant(4) + ' ' + currencyB?.symbol
: '-'
}
/> />
</RowBetween> </RowBetween>
{outOfRange && ( {outOfRange ? (
<YellowCard> <YellowCard padding="8px 12px" borderRadius="12px">
<RowBetween> <RowBetween>
<AlertTriangle stroke={theme.yellow3} size="24px" /> <AlertTriangle stroke={theme.yellow3} size="16px" />
<TYPE.yellow ml="12px" fontSize="12px"> <TYPE.yellow ml="12px" fontSize="12px">
{t('inactiveRangeWarning')} {t('inactiveRangeWarning')}
</TYPE.yellow> </TYPE.yellow>
</RowBetween> </RowBetween>
</YellowCard> </YellowCard>
)} ) : null}
{invalidRange && ( {invalidRange ? (
<YellowCard> <YellowCard padding="8px 12px" borderRadius="12px">
<RowBetween> <RowBetween>
<AlertTriangle stroke={theme.yellow3} size="24px" /> <AlertTriangle stroke={theme.yellow3} size="16px" />
<TYPE.yellow ml="12px" fontSize="12px"> <TYPE.yellow ml="12px" fontSize="12px">
{t('invalidRangeWarning')} {t('invalidRangeWarning')}
</TYPE.yellow> </TYPE.yellow>
</RowBetween> </RowBetween>
</YellowCard> </YellowCard>
)} ) : null}
</DynamicSection> </DynamicSection>
<DynamicSection disabled={!lowerTick || !upperTick}> <DynamicSection
disabled={tickLower === undefined || tickUpper === undefined || invalidPool || invalidRange}
>
<AutoColumn gap="md"> <AutoColumn gap="md">
<TYPE.label>{t('inputTokens')}</TYPE.label> <TYPE.label>{t('depositAmounts')}</TYPE.label>
<TYPE.main fontWeight={400} fontSize="14px"> {/* <TYPE.main fontWeight={400} fontSize="14px">
{t('chooseLiquidityAmount')} {t('chooseLiquidityAmount')}
</TYPE.main> </TYPE.main> */}
<CurrencyInputPanel <CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]} value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput} onUserInput={onFieldAInput}
@ -479,11 +578,11 @@ export default function AddLiquidity({
currency={currencies[Field.CURRENCY_A]} currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena" id="add-liquidity-input-tokena"
showCommonBases showCommonBases
locked={depositADisabled}
/> />
<ColumnCenter> <ColumnCenter>
<Link2 stroke={theme.text2} size={'24px'} /> <Link2 stroke={theme.text2} size={'24px'} />
</ColumnCenter> </ColumnCenter>
<CurrencyInputPanel <CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]} value={formattedAmounts[Field.CURRENCY_B]}
disableCurrencySelect={true} disableCurrencySelect={true}
@ -496,12 +595,14 @@ export default function AddLiquidity({
currency={currencies[Field.CURRENCY_B]} currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb" id="add-liquidity-input-tokenb"
showCommonBases showCommonBases
locked={depositBDisabled}
/> />
</AutoColumn> </AutoColumn>
</DynamicSection> </DynamicSection>
</AutoColumn> </AutoColumn>
</Wrapper> </Wrapper>
</AppBody> </AppBody>
)}
</ScrollableContent> </ScrollableContent>
{addIsUnsupported && ( {addIsUnsupported && (
<UnsupportedCurrencyFooter <UnsupportedCurrencyFooter
@ -510,103 +611,23 @@ export default function AddLiquidity({
/> />
)} )}
<FixedPreview> <FixedPreview>
{attemptingTxn ? (
<ConfirmationPendingContent onDismiss={handleDismissConfirmation} pendingText={pendingText} inline={true} />
) : txHash && chainId ? (
<TransactionSubmittedContent
chainId={chainId}
hash={txHash}
onDismiss={handleDismissConfirmation}
inline={true}
/>
) : (
<AutoColumn gap="md"> <AutoColumn gap="md">
<TYPE.main fontSize="12px">Position Preview</TYPE.main> <TYPE.label fontSize="16px">{showConfirm ? 'Review and submit' : 'Configure Position'}</TYPE.label>
<PreviewCard> <TYPE.main fontWeight={400} fontSize="14px">
{!currencyA || !currencyB ? ( Learn more about Uniswap V3 liquidity pools.
<RowBetween> </TYPE.main>
<TYPE.label>Select a pair to begin</TYPE.label> {showConfirm ? (
<QuestionHelper text="Select a pair to begin" size={20} /> <div>
</RowBetween>
) : (
<AutoRow gap="4px">
<CurrencyLogo currency={currencyA} size={'20px'} />
<TYPE.label ml="4px">{currencyA.symbol}</TYPE.label>
<TYPE.main>/</TYPE.main>
<CurrencyLogo currency={currencyB} size={'20px'} />
<TYPE.label ml="4px">{currencyB.symbol}</TYPE.label>
</AutoRow>
)}
</PreviewCard>
<PreviewCard disabled={!currencyA || !currencyB}>
<RowBetween>
<TYPE.label>
{feeLevel >= 4
? t('selectPool')
: `${feeLevel === 0 ? '0.1' : feeLevel === 1 ? '0.3' : '0.5'}% fee pool`}
</TYPE.label>
<LifeBuoy size="20px" />
</RowBetween>
</PreviewCard>
<PreviewCard disabled={feeLevel >= 4}>
{!lowerTick || !upperTick ? (
<RowBetween>
<TYPE.label>{t('selectPriceLimits')}</TYPE.label>
<Circle size="20px" />
</RowBetween>
) : (
currencyA &&
currencyB && (
<AutoColumn gap="sm" style={{ width: '100%' }}>
<ToggleWrapper width="100%">
<ToggleElement
isActive={rateCurrencyBase === currencyA}
fontSize="12px"
onClick={() => setRateCurrencyBase(rateCurrencyBase === currencyB ? currencyA : currencyB)}
>
{currencyA.symbol} {t('rate')}
</ToggleElement>
<ToggleElement
fontSize="12px"
isActive={currencyB === rateCurrencyBase}
onClick={() => setRateCurrencyBase(rateCurrencyBase === currencyB ? currencyA : currencyB)}
>
{currencyB.symbol} Rate
</ToggleElement>
</ToggleWrapper>
<RowBetween padding="0 32px">
<TYPE.label>{lowerTick.rate}</TYPE.label>
<TYPE.main></TYPE.main>
<TYPE.label>{upperTick.rate}</TYPE.label>
</RowBetween>
</AutoColumn>
)
)}
</PreviewCard>
<PreviewCard disabled={!lowerTick || !upperTick}>
{!formattedAmounts[Field.CURRENCY_A] || !currencyA || !currencyB ? (
<RowBetween>
<TYPE.label>{t('inputTokenDynamic', { label: currencyA ? currencyA.symbol : 'Token' })}</TYPE.label>
<Circle size="20px" />
</RowBetween>
) : (
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyA} />
<TYPE.label ml="8px">{formattedAmounts[Field.CURRENCY_A]}</TYPE.label>
<TYPE.label ml="8px">{currencyA.symbol}</TYPE.label>
</RowFixed>
<TYPE.main>50%</TYPE.main>
</RowBetween>
)}
</PreviewCard>
<PreviewCard disabled={!lowerTick || !upperTick}>
{!formattedAmounts[Field.CURRENCY_B] || !currencyA || !currencyB ? (
<RowBetween>
<TYPE.label>{t('inputTokenDynamic', { label: currencyB ? currencyB.symbol : 'Token' })}</TYPE.label>
<Circle size="20px" />
</RowBetween>
) : (
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyB} />
<TYPE.label ml="8px">{formattedAmounts[Field.CURRENCY_B]}</TYPE.label>
<TYPE.label ml="8px">{currencyB.symbol}</TYPE.label>
</RowFixed>
<TYPE.main>50%</TYPE.main>
</RowBetween>
)}
</PreviewCard>
{addIsUnsupported ? ( {addIsUnsupported ? (
<ButtonPrimary disabled={true} borderRadius="12px" padding={'12px'}> <ButtonPrimary disabled={true} borderRadius="12px" padding={'12px'}>
<TYPE.main mb="4px">{t('unsupportedAsset')}</TYPE.main> <TYPE.main mb="4px">{t('unsupportedAsset')}</TYPE.main>
@ -655,20 +676,41 @@ export default function AddLiquidity({
)} )}
</RowBetween> </RowBetween>
)} )}
<ButtonError
onClick={() => {
onAdd()
}}
style={{ borderRadius: '12px' }}
padding={'12px'}
disabled={
!isValid ||
(approvalA !== ApprovalState.APPROVED && !depositADisabled) ||
(approvalB !== ApprovalState.APPROVED && !depositBDisabled)
}
error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]}
>
<Text fontWeight={500}>
{errorMessage ? errorMessage : noLiquidity ? 'Create Pool and Add' : 'Add'}
</Text>
</ButtonError>
</AutoColumn>
)}
</div>
) : (
<ButtonError <ButtonError
onClick={() => { onClick={() => {
expertMode ? onAdd() : setShowConfirm(true) expertMode ? onAdd() : setShowConfirm(true)
}} }}
style={{ borderRadius: '12px' }} style={{ borderRadius: '12px' }}
padding={'12px'} padding={'12px'}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED} disabled={!isValid}
error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]} error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]}
> >
<Text fontWeight={500}>{error ?? 'Next'}</Text> <Text fontWeight={500}>{errorMessage ?? 'Review'}</Text>
</ButtonError> </ButtonError>
</AutoColumn>
)} )}
</AutoColumn> </AutoColumn>
)}
</FixedPreview> </FixedPreview>
</ScrollablePage> </ScrollablePage>
) )

@ -2,32 +2,16 @@ import React from 'react'
import { Redirect, RouteComponentProps } from 'react-router-dom' import { Redirect, RouteComponentProps } from 'react-router-dom'
import AddLiquidity from './index' import AddLiquidity from './index'
export function RedirectToAddLiquidity() { export function RedirectDuplicateTokenIds(
return <Redirect to="/add/" /> props: RouteComponentProps<{ currencyIdA: string; currencyIdB: string; feeAmount?: string }>
} ) {
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 }>) {
const { const {
match: { match: {
params: { currencyIdA, currencyIdB }, params: { currencyIdA, currencyIdB },
}, },
} = props } = props
if (currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) {
if (currencyIdA && currencyIdB && currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) {
return <Redirect to={`/add/${currencyIdA}`} /> return <Redirect to={`/add/${currencyIdA}`} />
} }
return <AddLiquidity {...props} /> return <AddLiquidity {...props} />

@ -2,6 +2,7 @@ import styled from 'styled-components'
import { AutoColumn } from 'components/Column' import { AutoColumn } from 'components/Column'
import CurrencyInputPanel from 'components/CurrencyInputPanel' import CurrencyInputPanel from 'components/CurrencyInputPanel'
import { DarkGreyCard } from 'components/Card' import { DarkGreyCard } from 'components/Card'
import Input from 'components/NumericalInput'
export const ScrollablePage = styled.div` export const ScrollablePage = styled.div`
position: relative; position: relative;
@ -10,18 +11,21 @@ export const ScrollablePage = styled.div`
` `
export const ScrollableContent = styled.div` export const ScrollableContent = styled.div`
margin-right: 24px; margin-right: 16px;
` `
export const FixedPreview = styled.div` export const FixedPreview = styled.div`
position: relative; position: relative;
padding: 8px; padding: 16px;
width: 260px; width: 260px;
height: fit-content; height: fit-content;
margin-top: 42px;
background: ${({ theme }) => theme.bg0}; 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), 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); 0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 12px; border-radius: 12px;
position: sticky;
top: 120px;
` `
export const DynamicSection = styled(AutoColumn)<{ disabled?: boolean }>` export const DynamicSection = styled(AutoColumn)<{ disabled?: boolean }>`
@ -30,7 +34,7 @@ export const DynamicSection = styled(AutoColumn)<{ disabled?: boolean }>`
` `
export const CurrencyDropdown = styled(CurrencyInputPanel)` export const CurrencyDropdown = styled(CurrencyInputPanel)`
width: 49%; width: 48.5%;
` `
export const PreviewCard = styled(DarkGreyCard)<{ disabled?: boolean }>` export const PreviewCard = styled(DarkGreyCard)<{ disabled?: boolean }>`
@ -42,3 +46,10 @@ export const PreviewCard = styled(DarkGreyCard)<{ disabled?: boolean }>`
align-items: center; align-items: center;
justify-content: 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 { ApplicationModal } from '../state/application/actions'
import { useModalOpen, useToggleModal } from '../state/application/hooks' import { useModalOpen, useToggleModal } from '../state/application/hooks'
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader' import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
import AddLiquidity from './AddLiquidity' import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects'
import {
RedirectDuplicateTokenIds,
RedirectOldAddLiquidityPathStructure,
RedirectToAddLiquidity,
} from './AddLiquidity/redirects'
import Earn from './Earn' import Earn from './Earn'
import Manage from './Earn/Manage' import Manage from './Earn/Manage'
import MigrateV1 from './MigrateV1' import MigrateV1 from './MigrateV1'
@ -34,12 +29,15 @@ import Swap from './Swap'
import { OpenClaimAddressModalAndRedirectToSwap, RedirectPathToSwapOnly, RedirectToSwap } from './Swap/redirects' import { OpenClaimAddressModalAndRedirectToSwap, RedirectPathToSwapOnly, RedirectToSwap } from './Swap/redirects'
import Vote from './Vote' import Vote from './Vote'
import VotePage from './Vote/VotePage' import VotePage from './Vote/VotePage'
import { RedirectDuplicateTokenIdsV2 } from './AddLiquidityV2/redirects'
import AddLiquidity from './AddLiquidity'
import AddLiquidityV2 from './AddLiquidityV2'
const AppWrapper = styled.div` const AppWrapper = styled.div`
display: flex; display: flex;
flex-flow: column; flex-flow: column;
align-items: flex-start; align-items: flex-start;
overflow-x: hidden; /* overflow-x: hidden; */
` `
const BodyWrapper = styled.div` const BodyWrapper = styled.div`
@ -49,8 +47,8 @@ const BodyWrapper = styled.div`
padding-top: 160px; padding-top: 160px;
align-items: center; align-items: center;
flex: 1; flex: 1;
overflow-y: auto; /* overflow-y: auto; */
overflow-x: hidden; /* overflow-x: hidden; */
${({ theme }) => theme.mediaWidth.upToSmall` ${({ theme }) => theme.mediaWidth.upToSmall`
padding: 16px; padding: 16px;
@ -108,13 +106,20 @@ export default function App() {
<Route exact strict path="/find" component={PoolFinder} /> <Route exact strict path="/find" component={PoolFinder} />
<Route exact strict path="/pool/v2" component={PoolV2} /> <Route exact strict path="/pool/v2" component={PoolV2} />
<Route exact strict path="/pool" component={Pool} /> <Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/create" component={RedirectToAddLiquidity} />
<Route exact path="/add" component={AddLiquidity} /> <Route exact path="/add" component={AddLiquidity} />
<Route exact path="/add/:currencyIdA" component={RedirectOldAddLiquidityPathStructure} /> <Route exact path="/add/v2/" component={AddLiquidityV2} />
<Route exact path="/add/:currencyIdA/:currencyIdB" component={RedirectDuplicateTokenIds} /> <Route exact path="/add/v2/:currencyIdA" component={AddLiquidityV2} />
<Route exact path="/create" component={AddLiquidity} />
<Route exact path="/create/:currencyIdA" component={RedirectOldAddLiquidityPathStructure} /> <Route exact path="/add/:currencyIdA" component={AddLiquidity} />
<Route exact path="/create/:currencyIdA/:currencyIdB" component={RedirectDuplicateTokenIds} /> <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/v1/:address" component={RemoveV1Exchange} />
<Route exact strict path="/remove/v2/:tokens" component={RedirectOldRemoveLiquidityPathStructure} /> <Route exact strict path="/remove/v2/:tokens" component={RedirectOldRemoveLiquidityPathStructure} />
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} /> <Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />

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

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

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

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

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

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

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

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

@ -31,7 +31,7 @@ import { useTransactionAdder } from '../../state/transactions/hooks'
import { StyledInternalLink, TYPE } from '../../theme' import { StyledInternalLink, TYPE } from '../../theme'
import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils' import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
import { currencyId } from '../../utils/currencyId' import { currencyId } from '../../utils/currencyId'
import useDebouncedChangeHandler from '../../utils/useDebouncedChangeHandler' import useDebouncedChangeHandler from '../../hooks/useDebouncedChangeHandler'
import { wrappedCurrency } from '../../utils/wrappedCurrency' import { wrappedCurrency } from '../../utils/wrappedCurrency'
import AppBody from '../AppBody' import AppBody from '../AppBody'
import { ClickableText, MaxButton, Wrapper } from '../Pool/styleds' 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 { JSBI, Pair } from '@uniswap/v2-sdk'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { usePair } from '../../data/Reserves' import { usePair } from '../../data/V2'
import { useTotalSupply } from '../../data/TotalSupply' import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'

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

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

@ -1,27 +1,19 @@
import { Currency, CurrencyAmount, ETHER, Percent, Price, TokenAmount } from '@uniswap/sdk-core' import { BIG_INT_ZERO } from './../../constants/index'
import { JSBI, Pair } from '@uniswap/v2-sdk' 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 { useCallback, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { PairState, usePair } from '../../data/Reserves'
import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { wrappedCurrency, wrappedCurrencyAmount } from '../../utils/wrappedCurrency' import { wrappedCurrency, wrappedCurrencyAmount } from '../../utils/wrappedCurrency'
import { AppDispatch, AppState } from '../index' import { AppDispatch, AppState } from '../index'
import { tryParseAmount } from '../swap/hooks' import { tryParseAmount } from '../swap/hooks'
import { useCurrencyBalances } from '../wallet/hooks' import { useCurrencyBalances } from '../wallet/hooks'
import { import { Field, Bound, typeInput, typeLowerRangeInput, typeUpperRangeInput, typeStartPriceInput } from './actions'
Field,
Bound,
typeInput,
typeLowerRangeInput,
typeUpperRangeInput,
RangeType,
updateRangeType as updateRangeTypeAction,
} from './actions'
import { tryParseTick } from './utils' import { tryParseTick } from './utils'
import { usePool } from 'data/Pools'
const ZERO = JSBI.BigInt(0)
export function useMintState(): AppState['mint'] { export function useMintState(): AppState['mint'] {
return useSelector<AppState, AppState['mint']>((state) => state.mint) return useSelector<AppState, AppState['mint']>((state) => state.mint)
@ -34,7 +26,7 @@ export function useMintActionHandlers(
onFieldBInput: (typedValue: string) => void onFieldBInput: (typedValue: string) => void
onLowerRangeInput: (typedValue: string) => void onLowerRangeInput: (typedValue: string) => void
onUpperRangeInput: (typedValue: string) => void onUpperRangeInput: (typedValue: string) => void
updateRangeType: (rangetype: RangeType) => void onStartPriceInput: (typedValue: string) => void
} { } {
const dispatch = useDispatch<AppDispatch>() const dispatch = useDispatch<AppDispatch>()
@ -66,9 +58,9 @@ export function useMintActionHandlers(
[dispatch] [dispatch]
) )
const updateRangeType = useCallback( const onStartPriceInput = useCallback(
(rangeType: RangeType) => { (typedValue: string) => {
dispatch(updateRangeTypeAction({ rangeType })) dispatch(typeStartPriceInput({ typedValue }))
}, },
[dispatch] [dispatch]
) )
@ -78,39 +70,48 @@ export function useMintActionHandlers(
onFieldBInput, onFieldBInput,
onLowerRangeInput, onLowerRangeInput,
onUpperRangeInput, onUpperRangeInput,
updateRangeType, onStartPriceInput,
} }
} }
// dummy entity
export interface Tick {
rate: number
}
export function useDerivedMintInfo( export function useDerivedMintInfo(
currencyA: Currency | undefined, currencyA: Currency | undefined,
currencyB: Currency | undefined currencyB: Currency | undefined,
feeAmount: FeeAmount | undefined
): { ): {
dependentField: Field pool?: Pool | null
currencies: { [field in Field]?: Currency } poolState: PoolState
pair?: Pair | null ticks: { [bound in Bound]?: number | undefined }
pairState: PairState
currencyBalances: { [field in Field]?: CurrencyAmount }
parsedAmounts: { [field in Field]?: CurrencyAmount }
ticks: { [bound in Bound]?: Tick }
price?: Price 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 noLiquidity?: boolean
liquidityMinted?: TokenAmount errorMessage?: string
poolTokenPercentage?: Percent invalidPool: boolean
error?: string outOfRange: boolean
invalidRange: boolean
depositADisabled: boolean
depositBDisabled: boolean
} { } {
const { account, chainId } = useActiveWeb3React() 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 const dependentField = independentField === Field.CURRENCY_A ? Field.CURRENCY_B : Field.CURRENCY_A
// tokens // currencies
const currencies: { [field in Field]?: Currency } = useMemo( const currencies: { [field in Field]?: Currency } = useMemo(
() => ({ () => ({
[Field.CURRENCY_A]: currencyA ?? undefined, [Field.CURRENCY_A]: currencyA ?? undefined,
@ -119,48 +120,152 @@ export function useDerivedMintInfo(
[currencyA, currencyB] [currencyA, currencyB]
) )
// pair // formatted with tokens
const [pairState, pair] = usePair(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B]) const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
const totalSupply = useTotalSupply(pair?.liquidityToken) chainId,
currencyA,
const noLiquidity: boolean = currencyB,
pairState === PairState.NOT_EXISTS || Boolean(totalSupply && JSBI.equal(totalSupply.raw, ZERO)) ])
// balances // balances
const balances = useCurrencyBalances(account ?? undefined, [ const balances = useCurrencyBalances(account ?? undefined, [
currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_A],
currencies[Field.CURRENCY_B], currencies[Field.CURRENCY_B],
]) ])
const currencyBalances: { [field in Field]?: CurrencyAmount } = { const currencyBalances: { [field in Field]?: CurrencyAmount } = {
[Field.CURRENCY_A]: balances[0], [Field.CURRENCY_A]: balances[0],
[Field.CURRENCY_B]: balances[1], [Field.CURRENCY_B]: balances[1],
} }
// amounts // pool
const independentAmount: CurrencyAmount | undefined = tryParseAmount(typedValue, currencies[independentField]) const [poolState, pool] = usePool(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B], feeAmount)
const dependentAmount: CurrencyAmount | undefined = useMemo(() => { const noLiquidity = poolState === PoolState.NOT_EXISTS
const price = useMemo(() => {
// if no liquidity use typed value
if (noLiquidity) { if (noLiquidity) {
if (otherTypedValue && currencies[dependentField]) { const parsedAmount = tryParseAmount(startPriceTypedValue, tokenA)
return tryParseAmount(otherTypedValue, currencies[dependentField]) if (parsedAmount && tokenA && tokenB) {
} const amountOne = tryParseAmount('1', tokenA)
return undefined return amountOne ? new Price(tokenA, tokenB, amountOne.raw, parsedAmount.raw) : 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 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 { } else {
return undefined 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(() => { const parsedAmounts: { [field in Field]: CurrencyAmount | undefined } = useMemo(() => {
return { return {
@ -169,87 +274,101 @@ export function useDerivedMintInfo(
} }
}, [dependentAmount, independentAmount, independentField]) }, [dependentAmount, independentAmount, independentField])
const price = useMemo(() => { // single deposit only if price is out of range
if (noLiquidity) { const deposit1Disabled = Boolean(sortedTicks && poolForPosition && poolForPosition.tickCurrent <= sortedTicks[0])
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts const deposit0Disabled = Boolean(sortedTicks && poolForPosition && poolForPosition.tickCurrent >= sortedTicks[1])
if (currencyAAmount && currencyBAmount) {
return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw) // 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 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),
} }
// liquidity minted // mark as 0 if disbaled because out of range
const liquidityMinted = useMemo(() => { const amount0 = !deposit0Disabled
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts ? parsedAmounts?.[tokenA.equals(poolForPosition.token0) ? Field.CURRENCY_A : Field.CURRENCY_B]?.raw
const [tokenAmountA, tokenAmountB] = [ : BIG_INT_ZERO
wrappedCurrencyAmount(currencyAAmount, chainId), const amount1 = !deposit1Disabled
wrappedCurrencyAmount(currencyBAmount, chainId), ? parsedAmounts?.[tokenA.equals(poolForPosition.token0) ? Field.CURRENCY_B : Field.CURRENCY_A]?.raw
] : BIG_INT_ZERO
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 { } else {
return undefined return undefined
} }
}, [parsedAmounts, chainId, pair, totalSupply]) }, [parsedAmounts, poolForPosition, sortedTicks, tokenA, tokenB, deposit0Disabled, deposit1Disabled])
const poolTokenPercentage = useMemo(() => { // liquiidty range warning
if (liquidityMinted && totalSupply) { const outOfRange = Boolean(
return new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw) price &&
} else { lowerPrice &&
return undefined upperPrice &&
} !invalidRange &&
}, [liquidityMinted, totalSupply]) (lowerPrice.greaterThan(price) || price.greaterThan(upperPrice))
)
let error: string | undefined let errorMessage: string | undefined
if (!account) { if (!account) {
error = 'Connect Wallet' errorMessage = 'Connect Wallet'
} }
if (pairState === PairState.INVALID) { if (poolState === PoolState.INVALID) {
error = error ?? 'Invalid pair' errorMessage = errorMessage ?? 'Invalid pair'
} }
if (!parsedAmounts[Field.CURRENCY_A] || !parsedAmounts[Field.CURRENCY_B]) { if (
error = error ?? 'Enter an amount' (!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 const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
if (currencyAAmount && currencyBalances?.[Field.CURRENCY_A]?.lessThan(currencyAAmount)) { 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)) { 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 { return {
dependentField, dependentField,
currencies, currencies,
pair, pool,
pairState, poolState,
currencyBalances, currencyBalances,
parsedAmounts, parsedAmounts,
ticks, ticks,
price, price,
pricesAtTicks,
position,
noLiquidity, noLiquidity,
liquidityMinted, errorMessage,
poolTokenPercentage, invalidPool,
error, invalidRange,
outOfRange,
depositADisabled,
depositBDisabled,
} }
} }

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

@ -1,18 +1,27 @@
import { Tick } from './hooks' import { Pool, priceToClosestTick, nearestUsableTick } from '@uniswap/v3-sdk/dist/'
/** import { Price, Token } from '@uniswap/sdk-core'
* @todo import { tryParseAmount } from 'state/swap/hooks'
* udpate to actually parse input and calculate next tick
*/ export function tryParseTick(
export function tryParseTick(value?: string): Tick | undefined { baseToken: Token | undefined,
if (!value) { quoteToken: Token | undefined,
pool: Pool | undefined,
value?: string
): number | undefined {
if (!value || !baseToken || !quoteToken || !pool) {
return undefined return undefined
} }
try { const amount = tryParseAmount(value, quoteToken)
return { rate: parseFloat(value) * 0.999 }
} catch (error) {
console.debug(`Failed to parse range amount: "${value}"`, error)
}
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( const calls = useMemo(
() => () =>
contract && fragment && callInputs && callInputs.length > 0 contract && fragment && callInputs?.length > 0 && callInputs.every((inputs) => isValidMethodArgs(inputs))
? callInputs.map<Call>((inputs) => { ? callInputs.map<Call>((inputs) => {
return { return {
address: contract.address, address: contract.address,
@ -180,7 +180,7 @@ export function useSingleContractMultipleData(
} }
}) })
: [], : [],
[callInputs, contract, fragment, gasRequired] [contract, fragment, callInputs, gasRequired]
) )
const results = useCallsData(calls, options) const results = useCallsData(calls, options)

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

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

@ -1,11 +1,4 @@
import { BigNumberish } from '@ethersproject/bignumber' 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 { export interface Position {
feesEarned: Record<string, BigNumberish> feesEarned: Record<string, BigNumberish>

@ -12,7 +12,7 @@ export function hexToUint8Array(hex: string): Uint8Array {
return arr 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 * 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 { Contract } from '@ethersproject/contracts'
import { getAddress } from '@ethersproject/address' import { getAddress } from '@ethersproject/address'
import { AddressZero } from '@ethersproject/constants' import { AddressZero } from '@ethersproject/constants'
import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers' import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { abi as IUniswapV2Router02ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router02.json' import { abi as IUniswapV2Router02ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router02.json'
import { ROUTER_ADDRESS } from '../constants' import { ROUTER_ADDRESS } from '../constants'
import { ChainId, Percent, Token, CurrencyAmount, Currency, ETHER } from '@uniswap/sdk-core' import { ChainId, Percent, Token, CurrencyAmount, Currency, ETHER } from '@uniswap/sdk-core'
import { JSBI } from '@uniswap/v2-sdk' import { JSBI } from '@uniswap/v2-sdk'
@ -123,3 +125,7 @@ export function supportedChainId(chainId: number): ChainId | undefined {
} }
return 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' import { ChainId, Currency, CurrencyAmount, ETHER, Token, TokenAmount, WETH9 } from '@uniswap/sdk-core'
export function wrappedCurrency(currency: Currency | undefined, chainId: ChainId | undefined): Token | undefined { 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( export function wrappedCurrencyAmount(

5657
yarn.lock

File diff suppressed because it is too large Load Diff