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:
parent
c3f65e3abd
commit
0c0305a53d
1
.env
1
.env
@ -1,2 +1,3 @@
|
||||
REACT_APP_CHAIN_ID="1"
|
||||
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
|
||||
SKIP_PREFLIGHT_CHECK=true
|
||||
|
@ -1,18 +1,18 @@
|
||||
describe('Add Liquidity', () => {
|
||||
it('loads the two correct tokens', () => {
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab/500')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH')
|
||||
})
|
||||
|
||||
it('does not crash if ETH is duplicated', () => {
|
||||
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'ETH')
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH')
|
||||
})
|
||||
|
||||
it('token not in storage is loaded', () => {
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
@ -23,28 +23,4 @@ describe('Add Liquidity', () => {
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('redirects /add/token-token to add/token/token', () => {
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
|
||||
)
|
||||
})
|
||||
|
||||
it('redirects /add/WETH-token to /add/WETH-address/token', () => {
|
||||
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/add/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
|
||||
)
|
||||
})
|
||||
|
||||
it('redirects /add/token-WETH to /add/token/WETH-address', () => {
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
15
package.json
15
package.json
@ -37,19 +37,20 @@
|
||||
"@types/styled-components": "^5.1.0",
|
||||
"@types/testing-library__cypress": "^5.0.5",
|
||||
"@types/wcag-contrast": "^3.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.20.0",
|
||||
"@typescript-eslint/parser": "^4.20.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.1.0",
|
||||
"@typescript-eslint/parser": "^4.1.0",
|
||||
"@uniswap/default-token-list": "^2.0.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
"@uniswap/sdk-core": "^1.0.8",
|
||||
"@uniswap/sdk-core": "^1.0.9",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.19",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v2-sdk": "^1.0.6",
|
||||
"@uniswap/v3-core": "^1.0.0-rc.0",
|
||||
"@uniswap/v3-periphery": "^1.0.0-beta.12",
|
||||
"@uniswap/v3-periphery": "^1.0.0-beta.17",
|
||||
"@uniswap/v3-sdk": "^1.0.0-alpha.11",
|
||||
"@web3-react/core": "^6.0.9",
|
||||
"@web3-react/fortmatic-connector": "^6.0.9",
|
||||
"@web3-react/injected-connector": "^6.0.7",
|
||||
@ -75,8 +76,8 @@
|
||||
"lightweight-charts": "^3.3.0",
|
||||
"lodash.flatmap": "^4.5.0",
|
||||
"luxon": "^1.25.0",
|
||||
"multicodec": "^2.0.0",
|
||||
"multihashes": "^3.0.1",
|
||||
"multicodec": "^3.0.1",
|
||||
"multihashes": "^4.0.2",
|
||||
"node-vibrant": "^3.1.5",
|
||||
"polished": "^3.3.2",
|
||||
"prettier": "^2.2.1",
|
||||
@ -92,7 +93,7 @@
|
||||
"react-popper": "^2.2.3",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-scripts": "^4.0.2",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
|
@ -91,14 +91,14 @@
|
||||
"selectFee": "Select Fee",
|
||||
"selectLiquidityRange": "Select Liquidity Range",
|
||||
"selectPool": "Select Fee Tier",
|
||||
"inputTokens": "Input Tokens",
|
||||
"depositAmounts": "Deposit Amounts",
|
||||
"fee": "fee",
|
||||
"setLimits": "Set Limits",
|
||||
"percent": "Percent",
|
||||
"rate": "Rate",
|
||||
"currentRate": "Current {{label}} Rate:",
|
||||
"currentRate": "Current {{label}} Price:",
|
||||
"inactiveRangeWarning": "Your position will not be active or earn fees until the selected rates come into range.",
|
||||
"invalidRangeWarning": "Invalid Range",
|
||||
"invalidRangeWarning": "Invalid Range entered. Lower range must be lower than upper range.",
|
||||
"connectWallet": "Connect Wallet",
|
||||
"unsupportedAsset": "Unsupported Asset",
|
||||
"feePool": "Fee Pool",
|
||||
@ -109,6 +109,8 @@
|
||||
"poolType": "Select a fee tier based on your preferred liquidity provider fee.",
|
||||
"rangeWarning": "Your liquidity will only be active and earning fees when the rate of the pool is within this price range.",
|
||||
"chooseLiquidityAmount": "Choose an amount of tokens to open this liquidity position. If you don’t have enough tokens you can trade for them with a Swap.",
|
||||
"selectPriceLimits": "Select Price Limits",
|
||||
"inputTokenDynamic": "Input {{label}}"
|
||||
"selectRange": "Select Liquidity Range",
|
||||
"inputTokenDynamic": "Input {{label}}",
|
||||
"selectStartingPrice": "Select Starting Price",
|
||||
"newPoolPrice": "Select the market rate for the tokens being added."
|
||||
}
|
||||
|
165
src/abis/multicall2.json
Normal file
165
src/abis/multicall2.json
Normal file
@ -0,0 +1,165 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "target", "type": "address" },
|
||||
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "aggregate",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
|
||||
{ "internalType": "bytes[]", "name": "returnData", "type": "bytes[]" }
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "target", "type": "address" },
|
||||
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "blockAndAggregate",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
|
||||
{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "bool", "name": "success", "type": "bool" },
|
||||
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }],
|
||||
"name": "getBlockHash",
|
||||
"outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getBlockNumber",
|
||||
"outputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockCoinbase",
|
||||
"outputs": [{ "internalType": "address", "name": "coinbase", "type": "address" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockDifficulty",
|
||||
"outputs": [{ "internalType": "uint256", "name": "difficulty", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockGasLimit",
|
||||
"outputs": [{ "internalType": "uint256", "name": "gaslimit", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockTimestamp",
|
||||
"outputs": [{ "internalType": "uint256", "name": "timestamp", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "address", "name": "addr", "type": "address" }],
|
||||
"name": "getEthBalance",
|
||||
"outputs": [{ "internalType": "uint256", "name": "balance", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getLastBlockHash",
|
||||
"outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "bool", "name": "requireSuccess", "type": "bool" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "target", "type": "address" },
|
||||
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "tryAggregate",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "bool", "name": "success", "type": "bool" },
|
||||
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "bool", "name": "requireSuccess", "type": "bool" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "target", "type": "address" },
|
||||
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "tryBlockAndAggregate",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
|
||||
{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "bool", "name": "success", "type": "bool" },
|
||||
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
@ -231,7 +231,8 @@ export const ButtonText = styled(Base)`
|
||||
text-decoration: underline;
|
||||
}
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
// text-decoration: underline;
|
||||
opacity: 0.9;
|
||||
}
|
||||
&:active {
|
||||
text-decoration: underline;
|
||||
@ -377,6 +378,9 @@ const Circle = styled.div`
|
||||
const CheckboxWrapper = styled.div`
|
||||
width: 30px;
|
||||
padding: 0 10px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
`
|
||||
|
||||
export function ButtonRadioChecked({ active = false, children, ...rest }: { active?: boolean } & ButtonProps) {
|
||||
|
@ -48,5 +48,4 @@ export const BlueCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.primary5};
|
||||
color: ${({ theme }) => theme.blue2};
|
||||
border-radius: 12px;
|
||||
width: fit-content;
|
||||
`
|
||||
|
@ -15,6 +15,8 @@ import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import { Lock } from 'react-feather'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
|
||||
const InputPanel = styled.div<{ hideInput?: boolean }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
@ -25,6 +27,19 @@ const InputPanel = styled.div<{ hideInput?: boolean }>`
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
`
|
||||
|
||||
const FixedContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
border-radius: 20px;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
opacity: 0.95;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
`
|
||||
|
||||
const Container = styled.div<{ hideInput: boolean }>`
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '12px' : '20px')};
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
@ -134,6 +149,7 @@ interface CurrencyInputPanelProps {
|
||||
id: string
|
||||
showCommonBases?: boolean
|
||||
customBalanceText?: string
|
||||
locked?: boolean
|
||||
}
|
||||
|
||||
export default function CurrencyInputPanel({
|
||||
@ -144,14 +160,15 @@ export default function CurrencyInputPanel({
|
||||
label = 'Input',
|
||||
onCurrencySelect,
|
||||
currency,
|
||||
disableCurrencySelect = false,
|
||||
hideBalance = false,
|
||||
pair = null, // used for double token logo
|
||||
hideInput = false,
|
||||
otherCurrency,
|
||||
id,
|
||||
showCommonBases,
|
||||
customBalanceText,
|
||||
disableCurrencySelect = false,
|
||||
hideBalance = false,
|
||||
pair = null, // used for double token logo
|
||||
hideInput = false,
|
||||
locked = false,
|
||||
...rest
|
||||
}: CurrencyInputPanelProps) {
|
||||
const { t } = useTranslation()
|
||||
@ -167,6 +184,14 @@ export default function CurrencyInputPanel({
|
||||
|
||||
return (
|
||||
<InputPanel id={id} hideInput={hideInput} {...rest}>
|
||||
{locked && (
|
||||
<FixedContainer>
|
||||
<AutoColumn gap="sm" justify="center">
|
||||
<Lock />
|
||||
<TYPE.label fontSize="12px">Single-asset deposit only, price out of range.</TYPE.label>
|
||||
</AutoColumn>
|
||||
</FixedContainer>
|
||||
)}
|
||||
<Container hideInput={hideInput}>
|
||||
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableCurrencySelect}>
|
||||
{!hideInput && (
|
||||
|
@ -12,7 +12,7 @@ import { useTotalUniEarned } from '../../state/stake/hooks'
|
||||
import { useAggregateUniBalance, useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { ExternalLink, StyledInternalLink, TYPE, UniTokenAnimated } from '../../theme'
|
||||
import { computeUniCirculation } from '../../utils/computeUniCirculation'
|
||||
import useUSDCPrice from '../../utils/useUSDCPrice'
|
||||
import useUSDCPrice from '../../hooks/useUSDCPrice'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { RowBetween } from '../Row'
|
||||
import { Break, CardBGImage, CardNoise, CardSection, DataCard } from '../earn/styled'
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react'
|
||||
import { OutlineCard } from 'components/Card'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { ButtonGray } from 'components/Button'
|
||||
import { TYPE } from 'theme'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
import styled, { keyframes, css } from 'styled-components'
|
||||
import { TYPE } from 'theme'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
|
||||
const pulse = (color: string) => keyframes`
|
||||
0% {
|
||||
@ -22,7 +22,7 @@ const pulse = (color: string) => keyframes`
|
||||
|
||||
const FocusedOutlineCard = styled(OutlineCard)<{ active?: boolean; pulsing?: boolean }>`
|
||||
border-color: ${({ active, theme }) => active && theme.blue1};
|
||||
padding: 8px 12px;
|
||||
padding: 12px;
|
||||
|
||||
${({ pulsing, theme }) =>
|
||||
pulsing &&
|
||||
@ -33,25 +33,22 @@ const FocusedOutlineCard = styled(OutlineCard)<{ active?: boolean; pulsing?: boo
|
||||
|
||||
const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>`
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
text-align: ${({ usePercent }) => (usePercent ? 'right' : 'center')};
|
||||
text-align: left;
|
||||
margin-right: 2px;
|
||||
`
|
||||
|
||||
const ContentWrapper = styled(RowBetween)`
|
||||
padding: 0 8px;
|
||||
width: 70%;
|
||||
width: 92%;
|
||||
`
|
||||
|
||||
interface StepCounterProps {
|
||||
value: string
|
||||
onUserInput: (value: string) => void
|
||||
onIncrement?: () => void
|
||||
onDecrement?: () => void
|
||||
usePercent?: boolean
|
||||
prependSymbol?: string | undefined
|
||||
label?: string
|
||||
width?: string
|
||||
}
|
||||
|
||||
const StepCounter = ({ value, onUserInput, usePercent = false, prependSymbol }: StepCounterProps) => {
|
||||
const StepCounter = ({ value, onUserInput, label, width }: StepCounterProps) => {
|
||||
// for focus state, styled components doesnt let you select input parent container
|
||||
const [active, setActive] = useState(false)
|
||||
|
||||
@ -85,20 +82,9 @@ const StepCounter = ({ value, onUserInput, usePercent = false, prependSymbol }:
|
||||
}
|
||||
}, [localValue, useLocalValue, value])
|
||||
|
||||
const handleDeccrement = useCallback(() => {
|
||||
localValue && setLocalValue((parseFloat(localValue) * 0.997).toString())
|
||||
}, [localValue])
|
||||
|
||||
const handleIncrement = useCallback(() => {
|
||||
localValue && setLocalValue((parseFloat(localValue) * 1.003).toString())
|
||||
}, [localValue])
|
||||
|
||||
return (
|
||||
<FocusedOutlineCard pulsing={pulsing} active={active} onFocus={handleOnFocus} onBlur={handleOnBlur}>
|
||||
<RowBetween>
|
||||
<ButtonGray padding="2px 0px" borderRadius="8px" onClick={handleDeccrement} width="50px">
|
||||
<TYPE.label>-</TYPE.label>
|
||||
</ButtonGray>
|
||||
<FocusedOutlineCard pulsing={pulsing} active={active} onFocus={handleOnFocus} onBlur={handleOnBlur} width={width}>
|
||||
<AutoColumn gap="md">
|
||||
<ContentWrapper>
|
||||
<StyledInput
|
||||
className="rate-input-0"
|
||||
@ -107,15 +93,10 @@ const StepCounter = ({ value, onUserInput, usePercent = false, prependSymbol }:
|
||||
onUserInput={(val) => {
|
||||
setLocalValue(val)
|
||||
}}
|
||||
prependSymbol={prependSymbol}
|
||||
usePercent={usePercent}
|
||||
/>
|
||||
{usePercent && <TYPE.main>%</TYPE.main>}
|
||||
</ContentWrapper>
|
||||
<ButtonGray padding="2px 0px" borderRadius="8px" onClick={handleIncrement} width="50px">
|
||||
<TYPE.label>+</TYPE.label>
|
||||
</ButtonGray>
|
||||
</RowBetween>
|
||||
{label && <TYPE.label fontSize="12px">{label}</TYPE.label>}
|
||||
</AutoColumn>
|
||||
</FocusedOutlineCard>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { ChainId, Currency, currencyEquals, ETHER, Token } from '@uniswap/sdk-core'
|
||||
import { ChainId, Currency, currencyEquals, Token, ETHER } from '@uniswap/sdk-core'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { SUGGESTED_BASES } from '../../constants'
|
||||
|
@ -79,7 +79,9 @@ export function CurrencySearch({
|
||||
|
||||
// if they input an address, use it
|
||||
const isAddressSearch = isAddress(debouncedQuery)
|
||||
|
||||
const searchToken = useToken(debouncedQuery)
|
||||
|
||||
const searchTokenIsAdded = useIsUserAddedToken(searchToken)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -18,8 +18,8 @@ import useAddTokenToMetamask from 'hooks/useAddTokenToMetamask'
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
`
|
||||
const Section = styled(AutoColumn)`
|
||||
padding: 24px;
|
||||
const Section = styled(AutoColumn)<{ inline?: boolean }>`
|
||||
padding: ${({ inline }) => (inline ? '0' : '24px')};
|
||||
`
|
||||
|
||||
const BottomSection = styled(Section)`
|
||||
@ -28,8 +28,8 @@ const BottomSection = styled(Section)`
|
||||
border-bottom-right-radius: 20px;
|
||||
`
|
||||
|
||||
const ConfirmedIcon = styled(ColumnCenter)`
|
||||
padding: 60px 0;
|
||||
const ConfirmedIcon = styled(ColumnCenter)<{ inline?: boolean }>`
|
||||
padding: ${({ inline }) => (inline ? '20px 0' : '60px 0;')};
|
||||
`
|
||||
|
||||
const StyledLogo = styled.img`
|
||||
@ -38,19 +38,29 @@ const StyledLogo = styled.img`
|
||||
margin-left: 6px;
|
||||
`
|
||||
|
||||
function ConfirmationPendingContent({ onDismiss, pendingText }: { onDismiss: () => void; pendingText: string }) {
|
||||
export function ConfirmationPendingContent({
|
||||
onDismiss,
|
||||
pendingText,
|
||||
inline,
|
||||
}: {
|
||||
onDismiss: () => void
|
||||
pendingText: string
|
||||
inline?: boolean // not in modal
|
||||
}) {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Section>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
|
||||
<AutoColumn gap="md">
|
||||
{!inline && (
|
||||
<RowBetween>
|
||||
<div />
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
</RowBetween>
|
||||
)}
|
||||
<ConfirmedIcon inline={inline}>
|
||||
<CustomLightSpinner src={Circle} alt="loader" size={inline ? '40px' : '90px'} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
<Text fontWeight={500} fontSize={20} textAlign="center">
|
||||
Waiting For Confirmation
|
||||
</Text>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
@ -62,21 +72,23 @@ function ConfirmationPendingContent({ onDismiss, pendingText }: { onDismiss: ()
|
||||
Confirm this transaction in your wallet
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
</Section>
|
||||
</AutoColumn>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
function TransactionSubmittedContent({
|
||||
export function TransactionSubmittedContent({
|
||||
onDismiss,
|
||||
chainId,
|
||||
hash,
|
||||
currencyToAdd,
|
||||
inline,
|
||||
}: {
|
||||
onDismiss: () => void
|
||||
hash: string | undefined
|
||||
chainId: ChainId
|
||||
currencyToAdd?: Currency | undefined
|
||||
inline?: boolean // not in modal
|
||||
}) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
@ -86,16 +98,18 @@ function TransactionSubmittedContent({
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Section>
|
||||
<RowBetween>
|
||||
<div />
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
|
||||
<Section inline={inline}>
|
||||
{!inline && (
|
||||
<RowBetween>
|
||||
<div />
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
</RowBetween>
|
||||
)}
|
||||
<ConfirmedIcon inline={inline}>
|
||||
<ArrowUpCircle strokeWidth={0.5} size={inline ? '40px' : '90px'} color={theme.primary1} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="12px" justify={'center'}>
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
<Text fontWeight={500} fontSize={20} textAlign="center">
|
||||
Transaction Submitted
|
||||
</Text>
|
||||
{chainId && hash && (
|
||||
@ -121,7 +135,7 @@ function TransactionSubmittedContent({
|
||||
)}
|
||||
<ButtonPrimary onClick={onDismiss} style={{ margin: '20px 0 0 0' }}>
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
Close
|
||||
{inline ? 'Return' : 'Close'}
|
||||
</Text>
|
||||
</ButtonPrimary>
|
||||
</AutoColumn>
|
||||
@ -145,7 +159,7 @@ export function ConfirmationModalContent({
|
||||
<Wrapper>
|
||||
<Section>
|
||||
<RowBetween>
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{title}
|
||||
</Text>
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
|
@ -13,8 +13,8 @@ import { currencyId } from '../../utils/currencyId'
|
||||
import { Break, CardNoise, CardBGImage } from './styled'
|
||||
import { unwrappedToken } from '../../utils/wrappedCurrency'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { usePair } from '../../data/Reserves'
|
||||
import useUSDCPrice from '../../utils/useUSDCPrice'
|
||||
import { usePair } from '../../data/V2'
|
||||
import useUSDCPrice from '../../hooks/useUSDCPrice'
|
||||
import { BIG_INT_SECONDS_IN_WEEK } from '../../constants'
|
||||
|
||||
const StatContainer = styled.div`
|
||||
|
@ -4,6 +4,14 @@ import JSBI from 'jsbi'
|
||||
|
||||
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
|
||||
|
||||
export const MULTICALL_ADDRESSES: { [chainId in ChainId]: string } = {
|
||||
[ChainId.MAINNET]: '',
|
||||
[ChainId.ROPSTEN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
|
||||
[ChainId.KOVAN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
|
||||
[ChainId.RINKEBY]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
|
||||
[ChainId.GÖRLI]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
|
||||
}
|
||||
|
||||
export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'
|
||||
|
||||
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||
@ -12,7 +20,7 @@ export { PRELOADED_PROPOSALS } from './proposals'
|
||||
|
||||
// a list of tokens by chain
|
||||
type ChainTokenList = {
|
||||
readonly [chainId in ChainId]: Token[]
|
||||
readonly [chainId in ChainId | 1337]: Token[]
|
||||
}
|
||||
|
||||
export const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin')
|
||||
@ -57,6 +65,7 @@ const WETH_ONLY: ChainTokenList = {
|
||||
[ChainId.RINKEBY]: [WETH9[ChainId.RINKEBY]],
|
||||
[ChainId.GÖRLI]: [WETH9[ChainId.GÖRLI]],
|
||||
[ChainId.KOVAN]: [WETH9[ChainId.KOVAN]],
|
||||
[1337]: [WETH9[ChainId.KOVAN]],
|
||||
}
|
||||
|
||||
// used to construct intermediary pairs for trading
|
||||
|
@ -102,6 +102,23 @@
|
||||
"internalType": "bytes32",
|
||||
"name": "blockHash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "returnData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
|
@ -1,12 +0,0 @@
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import MULTICALL_ABI from './abi.json'
|
||||
|
||||
const MULTICALL_NETWORKS: { [chainId in ChainId]: string } = {
|
||||
[ChainId.MAINNET]: '0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441',
|
||||
[ChainId.ROPSTEN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
|
||||
[ChainId.KOVAN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
|
||||
[ChainId.RINKEBY]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
|
||||
[ChainId.GÖRLI]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
|
||||
}
|
||||
|
||||
export { MULTICALL_ABI, MULTICALL_NETWORKS }
|
@ -17,6 +17,14 @@
|
||||
"decimals": 6,
|
||||
"chainId": 1,
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4922a015c4407F87432B179bb209e125432E4a2A/logo.png"
|
||||
},
|
||||
{
|
||||
"name": "Grump Cat",
|
||||
"address": "0x93B2FfF814FCaEFFB01406e80B4Ecd89Ca6A021b",
|
||||
"symbol": "GRUMPY",
|
||||
"decimals": 9,
|
||||
"chainId": 1,
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4922a015c4407F87432B179bb209e125432E4a2A/logo.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -2,40 +2,40 @@ import { ChainId } from '@uniswap/sdk-core'
|
||||
|
||||
export const FACTORY_ADDRESSES: { [chainId in ChainId]: string } = {
|
||||
[ChainId.MAINNET]: '',
|
||||
[ChainId.ROPSTEN]: '',
|
||||
[ChainId.RINKEBY]: '',
|
||||
[ChainId.ROPSTEN]: '0xb31b9A7b331eA8993bdfC67c650eDbfc9256eC62',
|
||||
[ChainId.RINKEBY]: '0xD8A6adFB40Ba3B3CdA9F985BF1fdbDc0c1d7591e',
|
||||
[ChainId.GÖRLI]: '0xb31b9A7b331eA8993bdfC67c650eDbfc9256eC62',
|
||||
[ChainId.KOVAN]: '',
|
||||
}
|
||||
|
||||
export const TICK_LENS_ADDRESSES: { [chainId in ChainId]: string } = {
|
||||
[ChainId.MAINNET]: '',
|
||||
[ChainId.ROPSTEN]: '',
|
||||
[ChainId.RINKEBY]: '',
|
||||
[ChainId.ROPSTEN]: '0x8E984b597F19E8D0FDd0b5bAfDb1d0ae4386455f',
|
||||
[ChainId.RINKEBY]: '0xB1c59e8Ae4B72f63a5a9CB9c25A9253096A4b126',
|
||||
[ChainId.GÖRLI]: '0x8E984b597F19E8D0FDd0b5bAfDb1d0ae4386455f',
|
||||
[ChainId.KOVAN]: '',
|
||||
}
|
||||
|
||||
export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: { [chainId in ChainId]: string } = {
|
||||
[ChainId.MAINNET]: '',
|
||||
[ChainId.ROPSTEN]: '',
|
||||
[ChainId.RINKEBY]: '',
|
||||
[ChainId.ROPSTEN]: '0x29e4bF3bFD649b807B4C752c01023E535094F6Bc',
|
||||
[ChainId.RINKEBY]: '0xee9e30637f84Bbf929042A9118c6E20023dab833',
|
||||
[ChainId.GÖRLI]: '0x29e4bF3bFD649b807B4C752c01023E535094F6Bc',
|
||||
[ChainId.KOVAN]: '',
|
||||
}
|
||||
|
||||
export const NONFUNGIBLE_TOKEN_POSITION_DESCRIPTOR_ADDRESSES: { [chainId in ChainId]: string } = {
|
||||
[ChainId.MAINNET]: '',
|
||||
[ChainId.ROPSTEN]: '',
|
||||
[ChainId.RINKEBY]: '',
|
||||
[ChainId.ROPSTEN]: '0xa0588c89Fe967e66533aB1A0504C30989f90156f',
|
||||
[ChainId.RINKEBY]: '0x3431b9Ed12e3204bC6f7039e1c576417B70fdD67',
|
||||
[ChainId.GÖRLI]: '0xa0588c89Fe967e66533aB1A0504C30989f90156f',
|
||||
[ChainId.KOVAN]: '',
|
||||
}
|
||||
|
||||
export const SWAP_ROUTER_ADDRESSES: { [chainId in ChainId]: string } = {
|
||||
[ChainId.MAINNET]: '',
|
||||
[ChainId.ROPSTEN]: '',
|
||||
[ChainId.RINKEBY]: '',
|
||||
[ChainId.ROPSTEN]: '0x71bB3d0e63f2Fa2A5d04d54267211f4Caef7062e',
|
||||
[ChainId.RINKEBY]: '0xa0588c89Fe967e66533aB1A0504C30989f90156f',
|
||||
[ChainId.GÖRLI]: '0x71bB3d0e63f2Fa2A5d04d54267211f4Caef7062e',
|
||||
[ChainId.KOVAN]: '',
|
||||
}
|
||||
@ -43,7 +43,7 @@ export const SWAP_ROUTER_ADDRESSES: { [chainId in ChainId]: string } = {
|
||||
export const V2_MIGRATOR_ADDRESSES: { [chainId in ChainId]: string } = {
|
||||
[ChainId.MAINNET]: '',
|
||||
[ChainId.ROPSTEN]: '',
|
||||
[ChainId.RINKEBY]: '',
|
||||
[ChainId.RINKEBY]: '0xb31b9A7b331eA8993bdfC67c650eDbfc9256eC62',
|
||||
[ChainId.GÖRLI]: '0xee9e30637f84Bbf929042A9118c6E20023dab833',
|
||||
[ChainId.KOVAN]: '',
|
||||
}
|
||||
|
98
src/data/Pools.ts
Normal file
98
src/data/Pools.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { Tick } from '@uniswap/v3-sdk'
|
||||
import { ZERO_ADDRESS } from './../constants/index'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { useMemo } from 'react'
|
||||
import { useActiveWeb3React } from '../hooks'
|
||||
import { useSingleCallResult } from '../state/multicall/hooks'
|
||||
import { wrappedCurrency } from '../utils/wrappedCurrency'
|
||||
import { Pool, FeeAmount, computePoolAddress } from '@uniswap/v3-sdk'
|
||||
import { useV3Factory, useV3Pool } from 'hooks/useContract'
|
||||
import { FACTORY_ADDRESSES } from 'constants/v3'
|
||||
import { useAllV3Ticks } from 'hooks/useAllV3Ticks'
|
||||
|
||||
export enum PoolState {
|
||||
LOADING,
|
||||
NOT_EXISTS,
|
||||
EXISTS,
|
||||
INVALID,
|
||||
}
|
||||
|
||||
export function usePool(currencyA?: Currency, currencyB?: Currency, feeAmount?: FeeAmount): [PoolState, Pool | null] {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const factoryContract = useV3Factory()
|
||||
|
||||
const tokenA = wrappedCurrency(currencyA, chainId)
|
||||
const tokenB = wrappedCurrency(currencyB, chainId)
|
||||
|
||||
// sorted version
|
||||
const [token0, token1] = useMemo(
|
||||
() =>
|
||||
tokenA && tokenB && !tokenA.equals(tokenB)
|
||||
? tokenA.sortsBefore(tokenB)
|
||||
? [tokenA, tokenB]
|
||||
: [tokenB, tokenA]
|
||||
: [undefined, undefined],
|
||||
[tokenA, tokenB]
|
||||
)
|
||||
|
||||
// fetch all generated addresses for pools
|
||||
const poolAddress = useMemo(() => {
|
||||
try {
|
||||
return chainId && tokenA && tokenB && feeAmount && !tokenA.equals(tokenB)
|
||||
? computePoolAddress({
|
||||
factoryAddress: FACTORY_ADDRESSES[chainId],
|
||||
tokenA,
|
||||
tokenB,
|
||||
fee: feeAmount,
|
||||
})
|
||||
: undefined
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}, [chainId, feeAmount, tokenA, tokenB])
|
||||
|
||||
const poolContract = useV3Pool(poolAddress)
|
||||
|
||||
// check factory if pools exists
|
||||
const addressParams = token0 && token1 && feeAmount ? [token0.address, token1.address, feeAmount] : undefined
|
||||
const addressFromFactory = useSingleCallResult(addressParams ? factoryContract : undefined, 'getPool', addressParams)
|
||||
|
||||
// attempt to fetch pool metadata
|
||||
const slot0Datas = useSingleCallResult(poolContract, 'slot0')
|
||||
|
||||
// fetch additional data to instantiate pools
|
||||
const liquidityDatas = useSingleCallResult(poolContract, 'liquidity')
|
||||
|
||||
const { result: slot0, loading: slot0Loading } = slot0Datas
|
||||
const { result: liquidityResult, loading: liquidityLoading } = liquidityDatas
|
||||
const { result: addressesResult, loading: addressesLoading } = addressFromFactory
|
||||
|
||||
const liquidity = liquidityResult?.[0]
|
||||
const poolAddressFromFactory = addressesResult?.[0]
|
||||
|
||||
// fetch tick data for pool
|
||||
const { tickData, loading: tickLoading } = useAllV3Ticks(token0, token1, feeAmount)
|
||||
|
||||
// still loading data
|
||||
if (slot0Loading || addressesLoading || liquidityLoading || tickLoading) return [PoolState.LOADING, null]
|
||||
|
||||
// invalid pool setup
|
||||
if (!tokenA || !tokenB || !feeAmount || tokenA.equals(tokenB)) return [PoolState.INVALID, null]
|
||||
|
||||
// pool has not been created or not initialized yet
|
||||
if (poolAddressFromFactory === ZERO_ADDRESS || !slot0 || !liquidity || slot0.sqrtPriceX96 === 0) {
|
||||
return [PoolState.NOT_EXISTS, null]
|
||||
}
|
||||
|
||||
const tickList: Tick[] = tickData
|
||||
.map((tick) => {
|
||||
return new Tick({
|
||||
index: tick.tick,
|
||||
liquidityGross: tick.liquidityGross,
|
||||
liquidityNet: tick.liquidityNet,
|
||||
})
|
||||
})
|
||||
.sort((tickA, tickB) => (tickA.index > tickB.index ? 1 : -1))
|
||||
|
||||
return [PoolState.EXISTS, new Pool(tokenA, tokenB, feeAmount, slot0.sqrtPriceX96, liquidity, slot0.tick, tickList)]
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { TokenAmount, Currency } from '@uniswap/sdk-core'
|
||||
import { useMemo } from 'react'
|
||||
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
|
||||
import { Interface } from '@ethersproject/abi'
|
||||
@ -7,6 +6,7 @@ import { useActiveWeb3React } from '../hooks'
|
||||
|
||||
import { useMultipleContractSingleData } from '../state/multicall/hooks'
|
||||
import { wrappedCurrency } from '../utils/wrappedCurrency'
|
||||
import { Currency, TokenAmount } from '@uniswap/sdk-core'
|
||||
|
||||
const PAIR_INTERFACE = new Interface(IUniswapV2PairABI)
|
||||
|
@ -5,7 +5,7 @@ import flatMap from 'lodash.flatmap'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { BASES_TO_CHECK_TRADES_AGAINST, CUSTOM_BASES, BETTER_TRADE_LESS_HOPS_THRESHOLD } from '../constants'
|
||||
import { PairState, usePairs } from '../data/Reserves'
|
||||
import { PairState, usePairs } from '../data/V2'
|
||||
import { wrappedCurrency } from '../utils/wrappedCurrency'
|
||||
|
||||
import { useActiveWeb3React } from './index'
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { FeeAmount, TICK_SPACINGS } from '@uniswap/v3-sdk'
|
||||
import { ZERO_ADDRESS } from '../constants'
|
||||
import { useMemo } from 'react'
|
||||
import { Result, useSingleContractMultipleData } from 'state/multicall/hooks'
|
||||
import { useTickLens } from './useContract'
|
||||
import { Result, useSingleCallResult, useSingleContractMultipleData } from 'state/multicall/hooks'
|
||||
import { useTickLens, useV3Factory } from './useContract'
|
||||
|
||||
// the following should probably all be from the sdk, just mocking it for now
|
||||
function MIN_TICK(tickSpacing: number) {
|
||||
@ -25,8 +28,9 @@ interface TickData {
|
||||
}
|
||||
|
||||
export function useAllV3Ticks(
|
||||
poolAddress: string,
|
||||
tickSpacing: number
|
||||
token0: Token | undefined,
|
||||
token1: Token | undefined,
|
||||
feeAmount: FeeAmount | undefined
|
||||
): {
|
||||
loading: boolean
|
||||
syncing: boolean
|
||||
@ -34,22 +38,35 @@ export function useAllV3Ticks(
|
||||
valid: boolean
|
||||
tickData: TickData[]
|
||||
} {
|
||||
const tickLens = useTickLens()
|
||||
const tickSpacing = useMemo(() => (feeAmount ? TICK_SPACINGS[feeAmount] : undefined), [feeAmount])
|
||||
|
||||
const minIndex = useMemo(() => bitmapIndex(MIN_TICK(tickSpacing), tickSpacing), [tickSpacing])
|
||||
const maxIndex = useMemo(() => bitmapIndex(MAX_TICK(tickSpacing), tickSpacing), [tickSpacing])
|
||||
const minIndex = useMemo(() => (tickSpacing ? bitmapIndex(MIN_TICK(tickSpacing), tickSpacing) : undefined), [
|
||||
tickSpacing,
|
||||
])
|
||||
const maxIndex = useMemo(() => (tickSpacing ? bitmapIndex(MAX_TICK(tickSpacing), tickSpacing) : undefined), [
|
||||
tickSpacing,
|
||||
])
|
||||
|
||||
const tickLensArgs = useMemo(
|
||||
// fetch the pool address
|
||||
const factoryContract = useV3Factory()
|
||||
const addressParams = token0 && token1 && feeAmount ? [token0.address, token1.address, feeAmount] : undefined
|
||||
const poolAddress = useSingleCallResult(addressParams ? factoryContract : undefined, 'getPool', addressParams)
|
||||
.result?.[0]
|
||||
|
||||
const tickLensArgs: [string, number][] = useMemo(
|
||||
() =>
|
||||
new Array(maxIndex - minIndex + 1)
|
||||
.fill(0)
|
||||
.map((_, i) => i + minIndex)
|
||||
.map((wordIndex) => [poolAddress, wordIndex]),
|
||||
maxIndex && minIndex && poolAddress && poolAddress !== ZERO_ADDRESS
|
||||
? new Array(maxIndex - minIndex + 1)
|
||||
.fill(0)
|
||||
.map((_, i) => i + minIndex)
|
||||
.map((wordIndex) => [poolAddress, wordIndex])
|
||||
: [],
|
||||
[minIndex, maxIndex, poolAddress]
|
||||
)
|
||||
|
||||
const tickLens = useTickLens()
|
||||
const callStates = useSingleContractMultipleData(
|
||||
tickLens,
|
||||
tickLensArgs.length > 0 ? tickLens : undefined,
|
||||
'getPopulatedTicksInWord',
|
||||
tickLensArgs,
|
||||
REFRESH_FREQUENCY,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { UniswapV3Pool } from './../types/v3/UniswapV3Pool.d'
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import { abi as GOVERNANCE_ABI } from '@uniswap/governance/build/GovernorAlpha.json'
|
||||
import { abi as UNI_ABI } from '@uniswap/governance/build/Uni.json'
|
||||
@ -8,6 +9,7 @@ import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.
|
||||
import { abi as NFTPositionManagerABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
|
||||
import { abi as V3FactoryABI } from '@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json'
|
||||
import { abi as TickLensABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/TickLens.sol/TickLens.json'
|
||||
import { abi as V3PoolABI } from '@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json'
|
||||
|
||||
import ARGENT_WALLET_DETECTOR_ABI from 'abis/argent-wallet-detector.json'
|
||||
import ENS_PUBLIC_RESOLVER_ABI from 'abis/ens-public-resolver.json'
|
||||
@ -24,8 +26,9 @@ import {
|
||||
MERKLE_DISTRIBUTOR_ADDRESS,
|
||||
V1_MIGRATOR_ADDRESS,
|
||||
UNI,
|
||||
MULTICALL_ADDRESSES,
|
||||
} from 'constants/index'
|
||||
import { MULTICALL_ABI, MULTICALL_NETWORKS } from 'constants/multicall'
|
||||
import MULTICALL_ABI from 'abis/multicall2.json'
|
||||
import { V1_EXCHANGE_ABI, V1_FACTORY_ABI, V1_FACTORY_ADDRESSES } from 'constants/v1'
|
||||
import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, FACTORY_ADDRESSES, TICK_LENS_ADDRESSES } from 'constants/v3'
|
||||
import { useMemo } from 'react'
|
||||
@ -35,7 +38,7 @@ import { getContract } from 'utils'
|
||||
import { useActiveWeb3React } from './index'
|
||||
|
||||
// returns null on errors
|
||||
function useContract(address: string | undefined, ABI: any, withSignerIfPossible = true): Contract | null {
|
||||
export function useContract(address: string | undefined, ABI: any, withSignerIfPossible = true): Contract | null {
|
||||
const { library, account } = useActiveWeb3React()
|
||||
|
||||
return useMemo(() => {
|
||||
@ -65,10 +68,10 @@ export function useV1ExchangeContract(address?: string, withSignerIfPossible?: b
|
||||
export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: boolean): Contract | null {
|
||||
return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
export function useWETHContract(withSignerIfPossible?: boolean): Contract | null {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
return useContract(chainId ? WETH9[chainId].address : undefined, WETH_ABI, withSignerIfPossible)
|
||||
const address = chainId && chainId in WETH9 ? WETH9[chainId].address : undefined
|
||||
return useContract(address, WETH_ABI, withSignerIfPossible)
|
||||
}
|
||||
|
||||
export function useArgentWalletDetectorContract(): Contract | null {
|
||||
@ -110,7 +113,7 @@ export function usePairContract(pairAddress?: string, withSignerIfPossible?: boo
|
||||
|
||||
export function useMulticallContract(): Contract | null {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
return useContract(chainId && MULTICALL_NETWORKS[chainId], MULTICALL_ABI, false)
|
||||
return useContract(chainId && MULTICALL_ADDRESSES[chainId], MULTICALL_ABI, false)
|
||||
}
|
||||
|
||||
export function useMerkleDistributorContract(): Contract | null {
|
||||
@ -152,6 +155,10 @@ export function useV3Factory(): UniswapV3Factory | null {
|
||||
return useContract(address, V3FactoryABI) as UniswapV3Factory | null
|
||||
}
|
||||
|
||||
export function useV3Pool(address: string | undefined): UniswapV3Pool | null {
|
||||
return useContract(address, V3PoolABI) as UniswapV3Pool | null
|
||||
}
|
||||
|
||||
export function useTickLens(): TickLens | null {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const address = chainId ? TICK_LENS_ADDRESSES[chainId] : undefined
|
||||
|
13
src/hooks/useTickToPrice.ts
Normal file
13
src/hooks/useTickToPrice.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Token, Price } from '@uniswap/sdk-core'
|
||||
import { tickToPrice } from '@uniswap/v3-sdk'
|
||||
|
||||
export function getTickToPrice(
|
||||
baseToken: Token | undefined,
|
||||
quoteToken: Token | undefined,
|
||||
tick: number | undefined
|
||||
): Price | undefined {
|
||||
if (!baseToken || !quoteToken || !tick) {
|
||||
return undefined
|
||||
}
|
||||
return tickToPrice(baseToken, quoteToken, tick)
|
||||
}
|
@ -2,9 +2,9 @@ import { ChainId, Currency, currencyEquals, Price, WETH9 } from '@uniswap/sdk-co
|
||||
import { JSBI } from '@uniswap/v2-sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { USDC } from '../constants'
|
||||
import { PairState, usePairs } from '../data/Reserves'
|
||||
import { PairState, usePairs } from '../data/V2'
|
||||
import { useActiveWeb3React } from '../hooks'
|
||||
import { wrappedCurrency } from './wrappedCurrency'
|
||||
import { wrappedCurrency } from '../utils/wrappedCurrency'
|
||||
|
||||
/**
|
||||
* Returns the price in USDC of the input currency
|
@ -12,7 +12,7 @@ import useTheme from 'hooks/useTheme'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import { ToggleWrapper, ToggleElement } from 'components/Toggle/MultiToggle'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Price, Percent, Currency, CurrencyAmount } from '@uniswap/sdk-core'
|
||||
import { Price, Currency, CurrencyAmount } from '@uniswap/sdk-core'
|
||||
|
||||
const Wrapper = styled(AutoColumn)`
|
||||
padding: 1rem 0;
|
||||
@ -27,7 +27,6 @@ export function ConfirmContent({
|
||||
price?: Price
|
||||
currencies: { [field in Field]?: Currency }
|
||||
parsedAmounts: { [field in Field]?: CurrencyAmount }
|
||||
poolTokenPercentage?: Percent
|
||||
onAdd: () => void
|
||||
}) {
|
||||
const currencyA: Currency | undefined = currencies[Field.CURRENCY_A]
|
||||
|
140
src/pages/AddLiquidity/Review.tsx
Normal file
140
src/pages/AddLiquidity/Review.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { RowBetween, RowFixed } from '../../components/Row'
|
||||
import CurrencyLogo from '../../components/CurrencyLogo'
|
||||
import { Field } from '../../state/mint/actions'
|
||||
import { TYPE } from '../../theme'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import Card, { DarkGreyCard } from 'components/Card'
|
||||
import styled from 'styled-components'
|
||||
import { Break } from 'components/earn/styled'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Currency, CurrencyAmount, Price } from '@uniswap/sdk-core'
|
||||
import { Position } from '@uniswap/v3-sdk'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import { wrappedCurrency } from 'utils/wrappedCurrency'
|
||||
import { useActiveWeb3React } from 'hooks'
|
||||
// import QuestionHelper from 'components/QuestionHelper'
|
||||
// import { WarningBadge } from 'components/Badge/Badge.stories'
|
||||
// import { AlertCircle } from 'react-feather'
|
||||
// import useTheme from 'hooks/useTheme'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
padding: 20px;
|
||||
min-width: 460px;
|
||||
`
|
||||
|
||||
const Badge = styled(Card)<{ inRange?: boolean }>`
|
||||
width: fit-content;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-radius: 8px;
|
||||
padding: 4px 6px;
|
||||
background-color: ${({ inRange, theme }) => (inRange ? theme.green1 : theme.yellow2)};
|
||||
`
|
||||
|
||||
export function Review({
|
||||
position,
|
||||
currencies,
|
||||
parsedAmounts,
|
||||
priceLower,
|
||||
priceUpper,
|
||||
outOfRange,
|
||||
}: {
|
||||
position?: Position
|
||||
currencies: { [field in Field]?: Currency }
|
||||
parsedAmounts: { [field in Field]?: CurrencyAmount }
|
||||
priceLower?: Price
|
||||
priceUpper?: Price
|
||||
outOfRange: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { chainId } = useActiveWeb3React()
|
||||
// const theme = useTheme()
|
||||
|
||||
const currencyA: Currency | undefined = currencies[Field.CURRENCY_A]
|
||||
const currencyB: Currency | undefined = currencies[Field.CURRENCY_B]
|
||||
|
||||
// formatted with tokens
|
||||
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
|
||||
chainId,
|
||||
currencyA,
|
||||
currencyB,
|
||||
])
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<AutoColumn gap="lg">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} size={24} margin={true} />
|
||||
<TYPE.label ml="10px" fontSize="24px">
|
||||
{currencyA?.symbol} / {currencyB?.symbol}
|
||||
</TYPE.label>
|
||||
</RowFixed>
|
||||
<Badge inRange={!outOfRange}>{outOfRange ? 'Out of range' : 'In Range'}</Badge>
|
||||
</RowBetween>
|
||||
{position && tokenA && tokenB && (
|
||||
<DarkGreyCard>
|
||||
<AutoColumn gap="md">
|
||||
<TYPE.label>Deposit Amounts</TYPE.label>
|
||||
{parsedAmounts[Field.CURRENCY_A] && (
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<CurrencyLogo currency={currencyA} />
|
||||
<TYPE.label ml="8px">{currencyA?.symbol}</TYPE.label>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
<TYPE.label mr="8px">{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</TYPE.label>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
)}
|
||||
{parsedAmounts[Field.CURRENCY_B] && (
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<CurrencyLogo currency={currencyB} />
|
||||
<TYPE.label ml="8px">{currencyB?.symbol}</TYPE.label>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
<TYPE.label mr="8px">{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</TYPE.label>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
)}
|
||||
<Break />
|
||||
<RowBetween>
|
||||
<TYPE.label>{t('feePool')}</TYPE.label>
|
||||
<TYPE.label>{position?.pool?.fee / 10000}%</TYPE.label>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<TYPE.label>Current Price</TYPE.label>
|
||||
<TYPE.label>{`1 ${currencyA?.symbol} = ${position?.pool
|
||||
?.priceOf(position.pool?.token0.equals(tokenA) ? position.pool?.token0 : position.pool?.token1)
|
||||
.toSignificant(6)} ${position.pool.token1?.symbol}`}</TYPE.label>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<TYPE.label>Active Range</TYPE.label>
|
||||
<TYPE.label>{`1 ${
|
||||
position.pool?.token0.equals(tokenA) ? currencyA?.symbol : currencyB?.symbol
|
||||
} = ${priceLower?.toSignificant(4)} ⟷ ${priceUpper?.toSignificant(6)} ${
|
||||
position.pool?.token0.equals(tokenA) ? currencyB?.symbol : currencyA?.symbol
|
||||
}`}</TYPE.label>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
</DarkGreyCard>
|
||||
)}
|
||||
{/* <YellowCard>
|
||||
<AutoColumn gap="md">
|
||||
<RowBetween>
|
||||
<TYPE.label color={theme.text2}>Efficiency Comparison</TYPE.label>
|
||||
<AlertCircle stroke={theme.text2} />
|
||||
</RowBetween>
|
||||
<TYPE.label fontWeight={400} color={theme.text2}>
|
||||
This liquidity position has an increased capital efficiency relative to an unbounded price limit.
|
||||
</TYPE.label>
|
||||
</AutoColumn>
|
||||
</YellowCard> */}
|
||||
</AutoColumn>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default Review
|
File diff suppressed because it is too large
Load Diff
@ -2,32 +2,16 @@ import React from 'react'
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom'
|
||||
import AddLiquidity from './index'
|
||||
|
||||
export function RedirectToAddLiquidity() {
|
||||
return <Redirect to="/add/" />
|
||||
}
|
||||
|
||||
const OLD_PATH_STRUCTURE = /^(0x[a-fA-F0-9]{40})-(0x[a-fA-F0-9]{40})$/
|
||||
export function RedirectOldAddLiquidityPathStructure(props: RouteComponentProps<{ currencyIdA: string }>) {
|
||||
const {
|
||||
match: {
|
||||
params: { currencyIdA },
|
||||
},
|
||||
} = props
|
||||
const match = currencyIdA.match(OLD_PATH_STRUCTURE)
|
||||
if (match?.length) {
|
||||
return <Redirect to={`/add/${match[1]}/${match[2]}`} />
|
||||
}
|
||||
|
||||
return <AddLiquidity {...props} />
|
||||
}
|
||||
|
||||
export function RedirectDuplicateTokenIds(props: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
|
||||
export function RedirectDuplicateTokenIds(
|
||||
props: RouteComponentProps<{ currencyIdA: string; currencyIdB: string; feeAmount?: string }>
|
||||
) {
|
||||
const {
|
||||
match: {
|
||||
params: { currencyIdA, currencyIdB },
|
||||
},
|
||||
} = props
|
||||
if (currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) {
|
||||
|
||||
if (currencyIdA && currencyIdB && currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) {
|
||||
return <Redirect to={`/add/${currencyIdA}`} />
|
||||
}
|
||||
return <AddLiquidity {...props} />
|
||||
|
@ -2,6 +2,7 @@ import styled from 'styled-components'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyInputPanel from 'components/CurrencyInputPanel'
|
||||
import { DarkGreyCard } from 'components/Card'
|
||||
import Input from 'components/NumericalInput'
|
||||
|
||||
export const ScrollablePage = styled.div`
|
||||
position: relative;
|
||||
@ -10,18 +11,21 @@ export const ScrollablePage = styled.div`
|
||||
`
|
||||
|
||||
export const ScrollableContent = styled.div`
|
||||
margin-right: 24px;
|
||||
margin-right: 16px;
|
||||
`
|
||||
|
||||
export const FixedPreview = styled.div`
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
padding: 16px;
|
||||
width: 260px;
|
||||
height: fit-content;
|
||||
margin-top: 42px;
|
||||
background: ${({ theme }) => theme.bg0};
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
0px 24px 32px rgba(0, 0, 0, 0.01);
|
||||
border-radius: 12px;
|
||||
position: sticky;
|
||||
top: 120px;
|
||||
`
|
||||
|
||||
export const DynamicSection = styled(AutoColumn)<{ disabled?: boolean }>`
|
||||
@ -30,7 +34,7 @@ export const DynamicSection = styled(AutoColumn)<{ disabled?: boolean }>`
|
||||
`
|
||||
|
||||
export const CurrencyDropdown = styled(CurrencyInputPanel)`
|
||||
width: 49%;
|
||||
width: 48.5%;
|
||||
`
|
||||
|
||||
export const PreviewCard = styled(DarkGreyCard)<{ disabled?: boolean }>`
|
||||
@ -42,3 +46,10 @@ export const PreviewCard = styled(DarkGreyCard)<{ disabled?: boolean }>`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
export const StyledInput = styled(Input)`
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
text-align: left;
|
||||
font-size: 18px;
|
||||
width: 100%;
|
||||
`
|
||||
|
67
src/pages/AddLiquidityV2/ConfirmAddModalBottom.tsx
Normal file
67
src/pages/AddLiquidityV2/ConfirmAddModalBottom.tsx
Normal file
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
52
src/pages/AddLiquidityV2/PoolPriceBar.tsx
Normal file
52
src/pages/AddLiquidityV2/PoolPriceBar.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
485
src/pages/AddLiquidityV2/index.tsx
Normal file
485
src/pages/AddLiquidityV2/index.tsx
Normal file
@ -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]}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
20
src/pages/AddLiquidityV2/redirects.tsx
Normal file
20
src/pages/AddLiquidityV2/redirects.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom'
|
||||
import AddLiquidityV2 from './index'
|
||||
|
||||
export function RedirectToAddLiquidityV2() {
|
||||
return <Redirect to="/add/v2/ETH" />
|
||||
}
|
||||
|
||||
export function RedirectDuplicateTokenIdsV2(props: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
|
||||
const {
|
||||
match: {
|
||||
params: { currencyIdA, currencyIdB },
|
||||
},
|
||||
} = props
|
||||
|
||||
if (currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) {
|
||||
return <Redirect to={`/add/V2/${currencyIdA}`} />
|
||||
}
|
||||
return <AddLiquidityV2 {...props} />
|
||||
}
|
@ -11,12 +11,7 @@ import Web3ReactManager from '../components/Web3ReactManager'
|
||||
import { ApplicationModal } from '../state/application/actions'
|
||||
import { useModalOpen, useToggleModal } from '../state/application/hooks'
|
||||
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
|
||||
import AddLiquidity from './AddLiquidity'
|
||||
import {
|
||||
RedirectDuplicateTokenIds,
|
||||
RedirectOldAddLiquidityPathStructure,
|
||||
RedirectToAddLiquidity,
|
||||
} from './AddLiquidity/redirects'
|
||||
import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects'
|
||||
import Earn from './Earn'
|
||||
import Manage from './Earn/Manage'
|
||||
import MigrateV1 from './MigrateV1'
|
||||
@ -34,12 +29,15 @@ import Swap from './Swap'
|
||||
import { OpenClaimAddressModalAndRedirectToSwap, RedirectPathToSwapOnly, RedirectToSwap } from './Swap/redirects'
|
||||
import Vote from './Vote'
|
||||
import VotePage from './Vote/VotePage'
|
||||
import { RedirectDuplicateTokenIdsV2 } from './AddLiquidityV2/redirects'
|
||||
import AddLiquidity from './AddLiquidity'
|
||||
import AddLiquidityV2 from './AddLiquidityV2'
|
||||
|
||||
const AppWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: flex-start;
|
||||
overflow-x: hidden;
|
||||
/* overflow-x: hidden; */
|
||||
`
|
||||
|
||||
const BodyWrapper = styled.div`
|
||||
@ -49,8 +47,8 @@ const BodyWrapper = styled.div`
|
||||
padding-top: 160px;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
/* overflow-y: auto; */
|
||||
/* overflow-x: hidden; */
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
padding: 16px;
|
||||
@ -108,13 +106,20 @@ export default function App() {
|
||||
<Route exact strict path="/find" component={PoolFinder} />
|
||||
<Route exact strict path="/pool/v2" component={PoolV2} />
|
||||
<Route exact strict path="/pool" component={Pool} />
|
||||
<Route exact strict path="/create" component={RedirectToAddLiquidity} />
|
||||
|
||||
<Route exact path="/add" component={AddLiquidity} />
|
||||
<Route exact path="/add/:currencyIdA" component={RedirectOldAddLiquidityPathStructure} />
|
||||
<Route exact path="/add/:currencyIdA/:currencyIdB" component={RedirectDuplicateTokenIds} />
|
||||
<Route exact path="/create" component={AddLiquidity} />
|
||||
<Route exact path="/create/:currencyIdA" component={RedirectOldAddLiquidityPathStructure} />
|
||||
<Route exact path="/create/:currencyIdA/:currencyIdB" component={RedirectDuplicateTokenIds} />
|
||||
<Route exact path="/add/v2/" component={AddLiquidityV2} />
|
||||
<Route exact path="/add/v2/:currencyIdA" component={AddLiquidityV2} />
|
||||
|
||||
<Route exact path="/add/:currencyIdA" component={AddLiquidity} />
|
||||
<Route exact strict path="/add/v2/:currencyIdA?/:currencyIdB?" component={RedirectDuplicateTokenIdsV2} />
|
||||
<Route
|
||||
exact
|
||||
strict
|
||||
path="/add/:currencyIdA?/:currencyIdB?/:feeAmount?"
|
||||
component={RedirectDuplicateTokenIds}
|
||||
/>
|
||||
|
||||
<Route exact strict path="/remove/v1/:address" component={RemoveV1Exchange} />
|
||||
<Route exact strict path="/remove/v2/:tokens" component={RedirectOldRemoveLiquidityPathStructure} />
|
||||
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const BodyWrapper = styled.div`
|
||||
export const BodyWrapper = styled.div<{ margin?: string }>`
|
||||
position: relative;
|
||||
margin-top: ${({ margin }) => margin ?? '0px'};
|
||||
max-width: 460px;
|
||||
width: 100%;
|
||||
background: ${({ theme }) => theme.bg0};
|
||||
@ -14,6 +15,6 @@ export const BodyWrapper = styled.div`
|
||||
/**
|
||||
* The styled container element that wraps the content of most pages and the tabs.
|
||||
*/
|
||||
export default function AppBody({ children }: { children: React.ReactNode }) {
|
||||
return <BodyWrapper>{children}</BodyWrapper>
|
||||
export default function AppBody({ children, ...rest }: { children: React.ReactNode }) {
|
||||
return <BodyWrapper {...rest}>{children}</BodyWrapper>
|
||||
}
|
||||
|
@ -26,9 +26,9 @@ import { CountUp } from 'use-count-up'
|
||||
import { wrappedCurrency } from '../../utils/wrappedCurrency'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { usePair } from '../../data/Reserves'
|
||||
import { usePair } from '../../data/V2'
|
||||
import usePrevious from '../../hooks/usePrevious'
|
||||
import useUSDCPrice from '../../utils/useUSDCPrice'
|
||||
import useUSDCPrice from '../../hooks/useUSDCPrice'
|
||||
import { BIG_INT_ZERO, BIG_INT_SECONDS_IN_WEEK } from '../../constants'
|
||||
|
||||
const PageWrapper = styled(AutoColumn)`
|
||||
|
@ -14,13 +14,12 @@ import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount'
|
||||
import QuestionHelper from '../../components/QuestionHelper'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
|
||||
import { Dots } from '../../components/swap/styleds'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE, V1_MIGRATOR_ADDRESS } from 'constants/index'
|
||||
import { PairState, usePair } from '../../data/Reserves'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE, V1_MIGRATOR_ADDRESS } from '../../constants'
|
||||
import { PairState, usePair } from '../../data/V2'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useToken } from '../../hooks/Tokens'
|
||||
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
|
||||
import { useV1ExchangeContract, useV1MigratorContract } from '../../hooks/useContract'
|
||||
import { NEVER_RELOAD, useSingleCallResult } from '../../state/multicall/hooks'
|
||||
import { useIsTransactionPending, useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { useETHBalances, useTokenBalance } from '../../state/wallet/hooks'
|
||||
@ -28,6 +27,7 @@ import { BackArrow, ExternalLink, TYPE } from '../../theme'
|
||||
import { getEtherscanLink, isAddress } from '../../utils'
|
||||
import { BodyWrapper } from '../AppBody'
|
||||
import { EmptyState } from './EmptyState'
|
||||
import { useV1ExchangeContract, useV1MigratorContract } from 'hooks/useContract'
|
||||
|
||||
const WEI_DENOM = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))
|
||||
const ZERO = JSBI.BigInt(0)
|
||||
|
@ -1,43 +1,32 @@
|
||||
import { TransactionResponse } from '@ethersproject/abstract-provider'
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
import { Currency, CurrencyAmount, Fraction, Percent, Price, Token, TokenAmount, WETH9 } from '@uniswap/sdk-core'
|
||||
import React, { useMemo } from 'react'
|
||||
import { Currency, CurrencyAmount, Price, Token, TokenAmount, WETH9 } from '@uniswap/sdk-core'
|
||||
import { JSBI } from '@uniswap/v2-sdk'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Redirect, RouteComponentProps } from 'react-router'
|
||||
import { Text } from 'rebass'
|
||||
import { ButtonConfirmed } from '../../components/Button'
|
||||
import { LightCard, PinkCard, YellowCard } from '../../components/Card'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import CurrencyLogo from '../../components/CurrencyLogo'
|
||||
import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount'
|
||||
import QuestionHelper from '../../components/QuestionHelper'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
|
||||
import { Dots } from '../../components/swap/styleds'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE, V1_MIGRATOR_ADDRESS } from 'constants/index'
|
||||
import { PairState, usePair } from '../../data/Reserves'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useToken } from '../../hooks/Tokens'
|
||||
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
|
||||
import { usePairContract, useV1MigratorContract } from '../../hooks/useContract'
|
||||
import { usePairContract } from '../../hooks/useContract'
|
||||
import { NEVER_RELOAD, useSingleCallResult } from '../../state/multicall/hooks'
|
||||
import { useIsTransactionPending, useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { useETHBalances, useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { BackArrow, ExternalLink, TYPE } from '../../theme'
|
||||
import { getEtherscanLink, isAddress } from '../../utils'
|
||||
import { BodyWrapper } from '../AppBody'
|
||||
import { EmptyState } from '../MigrateV1/EmptyState'
|
||||
import { toV2LiquidityToken } from 'state/user/hooks'
|
||||
import { V2_MIGRATOR_ADDRESSES } from 'constants/v3'
|
||||
|
||||
// TODO the whole file
|
||||
|
||||
const WEI_DENOM = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))
|
||||
const ZERO = JSBI.BigInt(0)
|
||||
const ONE = JSBI.BigInt(1)
|
||||
const ZERO_FRACTION = new Fraction(ZERO, ONE)
|
||||
const ALLOWED_OUTPUT_MIN_PERCENT = new Percent(JSBI.BigInt(10000 - INITIAL_ALLOWED_SLIPPAGE), JSBI.BigInt(10000))
|
||||
// const WEI_DENOM = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))
|
||||
// const ZERO = JSBI.BigInt(0)
|
||||
// const ONE = JSBI.BigInt(1)
|
||||
// const ZERO_FRACTION = new Fraction(ZERO, ONE)
|
||||
// const ALLOWED_OUTPUT_MIN_PERCENT = new Percent(JSBI.BigInt(10000 - INITIAL_ALLOWED_SLIPPAGE), JSBI.BigInt(10000))
|
||||
|
||||
export function V2LiquidityInfo({
|
||||
token,
|
||||
@ -105,7 +94,7 @@ function V2PairMigration({
|
||||
token0: Token
|
||||
token1: Token
|
||||
}) {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
// this is just getLiquidityValue with the fee off, but for the passed pair
|
||||
const token0Value = useMemo(
|
||||
@ -119,6 +108,8 @@ function V2PairMigration({
|
||||
|
||||
const v2SpotPrice = new Price(token0, token1, reserve0.raw, reserve1.raw)
|
||||
|
||||
console.log(token0Value, token1Value, v2SpotPrice)
|
||||
|
||||
// const isFirstLiquidityProvider: boolean = false // check for v3 pair existence
|
||||
|
||||
// const [confirmingMigration, setConfirmingMigration] = useState<boolean>(false)
|
||||
|
@ -13,8 +13,8 @@ import { EmptyState } from '../MigrateV1/EmptyState'
|
||||
import QuestionHelper from '../../components/QuestionHelper'
|
||||
import { Dots } from '../../components/swap/styleds'
|
||||
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
|
||||
import { usePairs } from 'data/Reserves'
|
||||
import MigrateV2PositionCard from 'components/PositionCard/V2'
|
||||
import { usePairs } from 'data/V2'
|
||||
|
||||
// TODO there's a bug in loading where "No V2 Liquidity found" flashes
|
||||
// TODO add support for more pairs
|
||||
|
@ -15,7 +15,7 @@ import { ButtonPrimary, ButtonSecondary } from '../../components/Button'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { usePairs } from '../../data/Reserves'
|
||||
import { usePairs } from '../../data/V2'
|
||||
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
|
||||
import { Dots } from '../../components/swap/styleds'
|
||||
import { CardSection, DataCard, CardNoise, CardBGImage } from '../../components/earn/styled'
|
||||
|
@ -11,7 +11,7 @@ import { FindPoolTabs } from '../../components/NavigationTabs'
|
||||
import { MinimalPositionCard } from '../../components/PositionCard'
|
||||
import Row from '../../components/Row'
|
||||
import CurrencySearchModal from '../../components/SearchModal/CurrencySearchModal'
|
||||
import { PairState, usePair } from '../../data/Reserves'
|
||||
import { PairState, usePair } from '../../data/V2'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { usePairAdder } from '../../state/user/hooks'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
|
@ -8,5 +8,6 @@ export default function RemoveLiquidityV3({
|
||||
params: { currencyIdA, currencyIdB, fee },
|
||||
},
|
||||
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string; fee: string }>) {
|
||||
console.log(currencyIdA, currencyIdB, fee)
|
||||
return <AppBody>TODO</AppBody>
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { StyledInternalLink, TYPE } from '../../theme'
|
||||
import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import useDebouncedChangeHandler from '../../utils/useDebouncedChangeHandler'
|
||||
import useDebouncedChangeHandler from '../../hooks/useDebouncedChangeHandler'
|
||||
import { wrappedCurrency } from '../../utils/wrappedCurrency'
|
||||
import AppBody from '../AppBody'
|
||||
import { ClickableText, MaxButton, Wrapper } from '../Pool/styleds'
|
||||
|
6
src/pages/styled.tsx
Normal file
6
src/pages/styled.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const StandardPageWrapper = styled.div`
|
||||
padding-top: 160px;
|
||||
width: 100%;
|
||||
`
|
@ -2,7 +2,7 @@ import { Currency, CurrencyAmount, Percent, TokenAmount } from '@uniswap/sdk-cor
|
||||
import { JSBI, Pair } from '@uniswap/v2-sdk'
|
||||
import { useCallback } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { usePair } from '../../data/Reserves'
|
||||
import { usePair } from '../../data/V2'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
|
@ -42,7 +42,6 @@ const EMPTY_LIST: TokenAddressMap = {
|
||||
[ChainId.ROPSTEN]: {},
|
||||
[ChainId.GÖRLI]: {},
|
||||
[ChainId.MAINNET]: {},
|
||||
[1337]: {},
|
||||
}
|
||||
|
||||
const listCache: WeakMap<TokenList, TokenAddressMap> | null =
|
||||
@ -99,7 +98,6 @@ function combineMaps(map1: TokenAddressMap, map2: TokenAddressMap): TokenAddress
|
||||
4: { ...map1[4], ...map2[4] },
|
||||
5: { ...map1[5], ...map2[5] },
|
||||
42: { ...map1[42], ...map2[42] },
|
||||
1337: { ...map1[1337], ...map2[1337] },
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,11 @@ export enum Field {
|
||||
}
|
||||
|
||||
export enum Bound {
|
||||
CURRENT = 'CURRENT',
|
||||
LOWER = 'LOWER',
|
||||
UPPER = 'UPPER',
|
||||
}
|
||||
|
||||
// save for % inputs
|
||||
export enum RangeType {
|
||||
PERCENT = 'PERCENT',
|
||||
RATE = 'RATE',
|
||||
@ -19,5 +19,5 @@ export enum RangeType {
|
||||
export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('mint/typeInputMint')
|
||||
export const typeLowerRangeInput = createAction<{ typedValue: string }>('mint/typeLowerRangeInput')
|
||||
export const typeUpperRangeInput = createAction<{ typedValue: string }>('mint/typeUpperRangeInput')
|
||||
export const updateRangeType = createAction<{ rangeType: RangeType }>('mint/updateRangeType')
|
||||
export const typeStartPriceInput = createAction<{ typedValue: string }>('mint/typeStartPriceInput')
|
||||
export const resetMintState = createAction<void>('mint/resetMintState')
|
||||
|
@ -1,27 +1,19 @@
|
||||
import { Currency, CurrencyAmount, ETHER, Percent, Price, TokenAmount } from '@uniswap/sdk-core'
|
||||
import { JSBI, Pair } from '@uniswap/v2-sdk'
|
||||
import { BIG_INT_ZERO } from './../../constants/index'
|
||||
import { getTickToPrice } from 'utils/getTickToPrice'
|
||||
import JSBI from 'jsbi'
|
||||
import { PoolState } from './../../data/Pools'
|
||||
import { Pool, FeeAmount, Position, priceToClosestTick, TickMath } from '@uniswap/v3-sdk/dist/'
|
||||
import { Currency, CurrencyAmount, ETHER, Price } from '@uniswap/sdk-core'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { PairState, usePair } from '../../data/Reserves'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { wrappedCurrency, wrappedCurrencyAmount } from '../../utils/wrappedCurrency'
|
||||
import { AppDispatch, AppState } from '../index'
|
||||
import { tryParseAmount } from '../swap/hooks'
|
||||
import { useCurrencyBalances } from '../wallet/hooks'
|
||||
import {
|
||||
Field,
|
||||
Bound,
|
||||
typeInput,
|
||||
typeLowerRangeInput,
|
||||
typeUpperRangeInput,
|
||||
RangeType,
|
||||
updateRangeType as updateRangeTypeAction,
|
||||
} from './actions'
|
||||
import { Field, Bound, typeInput, typeLowerRangeInput, typeUpperRangeInput, typeStartPriceInput } from './actions'
|
||||
import { tryParseTick } from './utils'
|
||||
|
||||
const ZERO = JSBI.BigInt(0)
|
||||
import { usePool } from 'data/Pools'
|
||||
|
||||
export function useMintState(): AppState['mint'] {
|
||||
return useSelector<AppState, AppState['mint']>((state) => state.mint)
|
||||
@ -34,7 +26,7 @@ export function useMintActionHandlers(
|
||||
onFieldBInput: (typedValue: string) => void
|
||||
onLowerRangeInput: (typedValue: string) => void
|
||||
onUpperRangeInput: (typedValue: string) => void
|
||||
updateRangeType: (rangetype: RangeType) => void
|
||||
onStartPriceInput: (typedValue: string) => void
|
||||
} {
|
||||
const dispatch = useDispatch<AppDispatch>()
|
||||
|
||||
@ -66,9 +58,9 @@ export function useMintActionHandlers(
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const updateRangeType = useCallback(
|
||||
(rangeType: RangeType) => {
|
||||
dispatch(updateRangeTypeAction({ rangeType }))
|
||||
const onStartPriceInput = useCallback(
|
||||
(typedValue: string) => {
|
||||
dispatch(typeStartPriceInput({ typedValue }))
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
@ -78,39 +70,48 @@ export function useMintActionHandlers(
|
||||
onFieldBInput,
|
||||
onLowerRangeInput,
|
||||
onUpperRangeInput,
|
||||
updateRangeType,
|
||||
onStartPriceInput,
|
||||
}
|
||||
}
|
||||
|
||||
// dummy entity
|
||||
export interface Tick {
|
||||
rate: number
|
||||
}
|
||||
|
||||
export function useDerivedMintInfo(
|
||||
currencyA: Currency | undefined,
|
||||
currencyB: Currency | undefined
|
||||
currencyB: Currency | undefined,
|
||||
feeAmount: FeeAmount | undefined
|
||||
): {
|
||||
dependentField: Field
|
||||
currencies: { [field in Field]?: Currency }
|
||||
pair?: Pair | null
|
||||
pairState: PairState
|
||||
currencyBalances: { [field in Field]?: CurrencyAmount }
|
||||
parsedAmounts: { [field in Field]?: CurrencyAmount }
|
||||
ticks: { [bound in Bound]?: Tick }
|
||||
pool?: Pool | null
|
||||
poolState: PoolState
|
||||
ticks: { [bound in Bound]?: number | undefined }
|
||||
price?: Price
|
||||
pricesAtTicks: {
|
||||
[bound in Bound]?: Price | undefined
|
||||
}
|
||||
currencies: { [field in Field]?: Currency }
|
||||
currencyBalances: { [field in Field]?: CurrencyAmount }
|
||||
dependentField: Field
|
||||
parsedAmounts: { [field in Field]?: CurrencyAmount }
|
||||
position: Position | undefined
|
||||
noLiquidity?: boolean
|
||||
liquidityMinted?: TokenAmount
|
||||
poolTokenPercentage?: Percent
|
||||
error?: string
|
||||
errorMessage?: string
|
||||
invalidPool: boolean
|
||||
outOfRange: boolean
|
||||
invalidRange: boolean
|
||||
depositADisabled: boolean
|
||||
depositBDisabled: boolean
|
||||
} {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
const { independentField, typedValue, otherTypedValue, lowerRangeTypedValue, upperRangeTypedValue } = useMintState()
|
||||
const {
|
||||
independentField,
|
||||
typedValue,
|
||||
lowerRangeTypedValue,
|
||||
upperRangeTypedValue,
|
||||
startPriceTypedValue,
|
||||
} = useMintState()
|
||||
|
||||
const dependentField = independentField === Field.CURRENCY_A ? Field.CURRENCY_B : Field.CURRENCY_A
|
||||
|
||||
// tokens
|
||||
// currencies
|
||||
const currencies: { [field in Field]?: Currency } = useMemo(
|
||||
() => ({
|
||||
[Field.CURRENCY_A]: currencyA ?? undefined,
|
||||
@ -119,48 +120,152 @@ export function useDerivedMintInfo(
|
||||
[currencyA, currencyB]
|
||||
)
|
||||
|
||||
// pair
|
||||
const [pairState, pair] = usePair(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B])
|
||||
const totalSupply = useTotalSupply(pair?.liquidityToken)
|
||||
|
||||
const noLiquidity: boolean =
|
||||
pairState === PairState.NOT_EXISTS || Boolean(totalSupply && JSBI.equal(totalSupply.raw, ZERO))
|
||||
// formatted with tokens
|
||||
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
|
||||
chainId,
|
||||
currencyA,
|
||||
currencyB,
|
||||
])
|
||||
|
||||
// balances
|
||||
const balances = useCurrencyBalances(account ?? undefined, [
|
||||
currencies[Field.CURRENCY_A],
|
||||
currencies[Field.CURRENCY_B],
|
||||
])
|
||||
|
||||
const currencyBalances: { [field in Field]?: CurrencyAmount } = {
|
||||
[Field.CURRENCY_A]: balances[0],
|
||||
[Field.CURRENCY_B]: balances[1],
|
||||
}
|
||||
|
||||
// amounts
|
||||
const independentAmount: CurrencyAmount | undefined = tryParseAmount(typedValue, currencies[independentField])
|
||||
const dependentAmount: CurrencyAmount | undefined = useMemo(() => {
|
||||
// pool
|
||||
const [poolState, pool] = usePool(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B], feeAmount)
|
||||
const noLiquidity = poolState === PoolState.NOT_EXISTS
|
||||
|
||||
const price = useMemo(() => {
|
||||
// if no liquidity use typed value
|
||||
if (noLiquidity) {
|
||||
if (otherTypedValue && currencies[dependentField]) {
|
||||
return tryParseAmount(otherTypedValue, currencies[dependentField])
|
||||
}
|
||||
return undefined
|
||||
} else if (independentAmount) {
|
||||
// we wrap the currencies just to get the price in terms of the other token
|
||||
const wrappedIndependentAmount = wrappedCurrencyAmount(independentAmount, chainId)
|
||||
const [tokenA, tokenB] = [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)]
|
||||
if (tokenA && tokenB && wrappedIndependentAmount && pair) {
|
||||
const dependentCurrency = dependentField === Field.CURRENCY_B ? currencyB : currencyA
|
||||
const dependentTokenAmount =
|
||||
dependentField === Field.CURRENCY_B
|
||||
? pair.priceOf(tokenA).quote(wrappedIndependentAmount)
|
||||
: pair.priceOf(tokenB).quote(wrappedIndependentAmount)
|
||||
return dependentCurrency === ETHER ? CurrencyAmount.ether(dependentTokenAmount.raw) : dependentTokenAmount
|
||||
const parsedAmount = tryParseAmount(startPriceTypedValue, tokenA)
|
||||
if (parsedAmount && tokenA && tokenB) {
|
||||
const amountOne = tryParseAmount('1', tokenA)
|
||||
return amountOne ? new Price(tokenA, tokenB, amountOne.raw, parsedAmount.raw) : undefined
|
||||
}
|
||||
return undefined
|
||||
} else {
|
||||
// get the amount of quote currency
|
||||
return pool && tokenA ? pool.priceOf(tokenA) : undefined
|
||||
}
|
||||
}, [noLiquidity, startPriceTypedValue, tokenA, tokenB, pool])
|
||||
|
||||
// used for ratio calculation when pool not initialized
|
||||
const mockPool = useMemo(() => {
|
||||
if (tokenA && tokenB && feeAmount && price) {
|
||||
const currentTick = priceToClosestTick(price)
|
||||
const currentSqrt = TickMath.getSqrtRatioAtTick(currentTick)
|
||||
return new Pool(tokenA, tokenB, feeAmount, currentSqrt, JSBI.BigInt(0), currentTick, [])
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}, [noLiquidity, otherTypedValue, currencies, dependentField, independentAmount, currencyA, chainId, currencyB, pair])
|
||||
}, [feeAmount, price, tokenA, tokenB])
|
||||
|
||||
// if pool exists use it, if not use the mock pool
|
||||
const poolForPosition: Pool | undefined = pool ?? mockPool
|
||||
|
||||
// parse typed range values and determine closest ticks
|
||||
const ticks: {
|
||||
[key: string]: number | undefined
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
[Bound.LOWER]: poolForPosition ? tryParseTick(tokenA, tokenB, poolForPosition, lowerRangeTypedValue) : undefined,
|
||||
[Bound.UPPER]: poolForPosition ? tryParseTick(tokenA, tokenB, poolForPosition, upperRangeTypedValue) : undefined,
|
||||
}
|
||||
}, [lowerRangeTypedValue, poolForPosition, tokenA, tokenB, upperRangeTypedValue])
|
||||
|
||||
const { [Bound.LOWER]: tickLower, [Bound.UPPER]: tickUpper } = ticks || {}
|
||||
const sortedTicks = useMemo(
|
||||
() =>
|
||||
tickLower !== undefined && tickUpper !== undefined
|
||||
? tickLower < tickUpper
|
||||
? [tickLower, tickUpper]
|
||||
: [tickUpper, tickLower]
|
||||
: undefined,
|
||||
[tickLower, tickUpper]
|
||||
)
|
||||
|
||||
const pricesAtTicks = useMemo(() => {
|
||||
return {
|
||||
[Bound.LOWER]: poolForPosition ? getTickToPrice(tokenA, tokenB, ticks[Bound.LOWER]) : undefined,
|
||||
[Bound.UPPER]: poolForPosition ? getTickToPrice(tokenA, tokenB, ticks[Bound.UPPER]) : undefined,
|
||||
}
|
||||
}, [poolForPosition, ticks, tokenA, tokenB])
|
||||
const { [Bound.LOWER]: lowerPrice, [Bound.UPPER]: upperPrice } = pricesAtTicks
|
||||
|
||||
// mark invalid range
|
||||
const invalidRange = Boolean(lowerPrice && upperPrice && lowerPrice.greaterThan(upperPrice))
|
||||
|
||||
// amounts
|
||||
const independentAmount: CurrencyAmount | undefined = tryParseAmount(typedValue, currencies[independentField])
|
||||
|
||||
const dependentAmount: CurrencyAmount | undefined = useMemo(() => {
|
||||
// we wrap the currencies just to get the price in terms of the other token
|
||||
const wrappedIndependentAmount = wrappedCurrencyAmount(independentAmount, chainId)
|
||||
|
||||
const dependentCurrency = dependentField === Field.CURRENCY_B ? currencyB : currencyA
|
||||
|
||||
if (
|
||||
feeAmount &&
|
||||
independentAmount &&
|
||||
tokenA &&
|
||||
tokenB &&
|
||||
wrappedIndependentAmount &&
|
||||
price &&
|
||||
lowerPrice &&
|
||||
upperPrice &&
|
||||
sortedTicks &&
|
||||
poolForPosition
|
||||
) {
|
||||
// if price is out of range or invalid range - retun 0 (single deposit with be independent)
|
||||
if (price.lessThan(lowerPrice) || price.greaterThan(upperPrice) || invalidRange) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const position: Position | undefined = wrappedIndependentAmount.token.equals(poolForPosition.token0)
|
||||
? Position.fromAmount0({
|
||||
pool: poolForPosition,
|
||||
tickLower: sortedTicks[0],
|
||||
tickUpper: sortedTicks[1],
|
||||
amount0: independentAmount.raw,
|
||||
})
|
||||
: Position.fromAmount1({
|
||||
pool: poolForPosition,
|
||||
tickLower: sortedTicks[0],
|
||||
tickUpper: sortedTicks[1],
|
||||
amount1: independentAmount.raw,
|
||||
})
|
||||
|
||||
const dependentTokenAmount = wrappedIndependentAmount.token.equals(poolForPosition.token0)
|
||||
? position.amount1
|
||||
: position.amount0
|
||||
return dependentCurrency === ETHER ? CurrencyAmount.ether(dependentTokenAmount.raw) : dependentTokenAmount
|
||||
}
|
||||
|
||||
return undefined
|
||||
}, [
|
||||
independentAmount,
|
||||
chainId,
|
||||
dependentField,
|
||||
currencyB,
|
||||
currencyA,
|
||||
feeAmount,
|
||||
tokenA,
|
||||
tokenB,
|
||||
price,
|
||||
sortedTicks,
|
||||
lowerPrice,
|
||||
upperPrice,
|
||||
poolForPosition,
|
||||
invalidRange,
|
||||
])
|
||||
|
||||
const parsedAmounts: { [field in Field]: CurrencyAmount | undefined } = useMemo(() => {
|
||||
return {
|
||||
@ -169,87 +274,101 @@ export function useDerivedMintInfo(
|
||||
}
|
||||
}, [dependentAmount, independentAmount, independentField])
|
||||
|
||||
const price = useMemo(() => {
|
||||
if (noLiquidity) {
|
||||
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
|
||||
if (currencyAAmount && currencyBAmount) {
|
||||
return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw)
|
||||
}
|
||||
// single deposit only if price is out of range
|
||||
const deposit1Disabled = Boolean(sortedTicks && poolForPosition && poolForPosition.tickCurrent <= sortedTicks[0])
|
||||
const deposit0Disabled = Boolean(sortedTicks && poolForPosition && poolForPosition.tickCurrent >= sortedTicks[1])
|
||||
|
||||
// sorted for token order
|
||||
const depositADisabled = Boolean(
|
||||
(deposit0Disabled && poolForPosition && tokenA && poolForPosition.token0.equals(tokenA)) ||
|
||||
(deposit1Disabled && poolForPosition && tokenA && poolForPosition.token1.equals(tokenA))
|
||||
)
|
||||
const depositBDisabled = Boolean(
|
||||
(deposit0Disabled && poolForPosition && tokenB && poolForPosition.token0.equals(tokenB)) ||
|
||||
(deposit1Disabled && poolForPosition && tokenB && poolForPosition.token1.equals(tokenB))
|
||||
)
|
||||
|
||||
// create position entity based on users selection
|
||||
const position: Position | undefined = useMemo(() => {
|
||||
if (!poolForPosition || !tokenA || !tokenB || !sortedTicks) {
|
||||
return undefined
|
||||
} else {
|
||||
const wrappedCurrencyA = wrappedCurrency(currencyA, chainId)
|
||||
return pair && wrappedCurrencyA ? pair.priceOf(wrappedCurrencyA) : undefined
|
||||
}
|
||||
}, [chainId, currencyA, noLiquidity, pair, parsedAmounts])
|
||||
|
||||
// parse typed range values and determine closest ticks, dummy rn
|
||||
const ticks = {
|
||||
[Bound.CURRENT]: {
|
||||
rate: parseFloat(
|
||||
independentField === Field.CURRENCY_A ? price?.toFixed(6) ?? '0' : price?.invert().toFixed(6) ?? '0'
|
||||
),
|
||||
},
|
||||
[Bound.LOWER]: tryParseTick(lowerRangeTypedValue),
|
||||
[Bound.UPPER]: tryParseTick(upperRangeTypedValue),
|
||||
}
|
||||
// mark as 0 if disbaled because out of range
|
||||
const amount0 = !deposit0Disabled
|
||||
? parsedAmounts?.[tokenA.equals(poolForPosition.token0) ? Field.CURRENCY_A : Field.CURRENCY_B]?.raw
|
||||
: BIG_INT_ZERO
|
||||
const amount1 = !deposit1Disabled
|
||||
? parsedAmounts?.[tokenA.equals(poolForPosition.token0) ? Field.CURRENCY_B : Field.CURRENCY_A]?.raw
|
||||
: BIG_INT_ZERO
|
||||
|
||||
// liquidity minted
|
||||
const liquidityMinted = useMemo(() => {
|
||||
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
|
||||
const [tokenAmountA, tokenAmountB] = [
|
||||
wrappedCurrencyAmount(currencyAAmount, chainId),
|
||||
wrappedCurrencyAmount(currencyBAmount, chainId),
|
||||
]
|
||||
if (pair && totalSupply && tokenAmountA && tokenAmountB) {
|
||||
return pair.getLiquidityMinted(totalSupply, tokenAmountA, tokenAmountB)
|
||||
if (amount0 !== undefined && amount1 !== undefined) {
|
||||
return Position.fromAmounts({
|
||||
pool: poolForPosition,
|
||||
tickLower: sortedTicks[0],
|
||||
tickUpper: sortedTicks[1],
|
||||
amount0: amount0,
|
||||
amount1: amount1,
|
||||
})
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}, [parsedAmounts, chainId, pair, totalSupply])
|
||||
}, [parsedAmounts, poolForPosition, sortedTicks, tokenA, tokenB, deposit0Disabled, deposit1Disabled])
|
||||
|
||||
const poolTokenPercentage = useMemo(() => {
|
||||
if (liquidityMinted && totalSupply) {
|
||||
return new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}, [liquidityMinted, totalSupply])
|
||||
// liquiidty range warning
|
||||
const outOfRange = Boolean(
|
||||
price &&
|
||||
lowerPrice &&
|
||||
upperPrice &&
|
||||
!invalidRange &&
|
||||
(lowerPrice.greaterThan(price) || price.greaterThan(upperPrice))
|
||||
)
|
||||
|
||||
let error: string | undefined
|
||||
let errorMessage: string | undefined
|
||||
if (!account) {
|
||||
error = 'Connect Wallet'
|
||||
errorMessage = 'Connect Wallet'
|
||||
}
|
||||
|
||||
if (pairState === PairState.INVALID) {
|
||||
error = error ?? 'Invalid pair'
|
||||
if (poolState === PoolState.INVALID) {
|
||||
errorMessage = errorMessage ?? 'Invalid pair'
|
||||
}
|
||||
|
||||
if (!parsedAmounts[Field.CURRENCY_A] || !parsedAmounts[Field.CURRENCY_B]) {
|
||||
error = error ?? 'Enter an amount'
|
||||
if (
|
||||
(!parsedAmounts[Field.CURRENCY_A] && !depositADisabled) ||
|
||||
(!parsedAmounts[Field.CURRENCY_B] && !depositBDisabled)
|
||||
) {
|
||||
errorMessage = errorMessage ?? 'Enter an amount'
|
||||
}
|
||||
|
||||
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
|
||||
|
||||
if (currencyAAmount && currencyBalances?.[Field.CURRENCY_A]?.lessThan(currencyAAmount)) {
|
||||
error = 'Insufficient ' + currencies[Field.CURRENCY_A]?.symbol + ' balance'
|
||||
errorMessage = 'Insufficient ' + currencies[Field.CURRENCY_A]?.symbol + ' balance'
|
||||
}
|
||||
|
||||
if (currencyBAmount && currencyBalances?.[Field.CURRENCY_B]?.lessThan(currencyBAmount)) {
|
||||
error = 'Insufficient ' + currencies[Field.CURRENCY_B]?.symbol + ' balance'
|
||||
errorMessage = 'Insufficient ' + currencies[Field.CURRENCY_B]?.symbol + ' balance'
|
||||
}
|
||||
|
||||
const invalidPool = poolState === PoolState.INVALID
|
||||
|
||||
return {
|
||||
dependentField,
|
||||
currencies,
|
||||
pair,
|
||||
pairState,
|
||||
pool,
|
||||
poolState,
|
||||
currencyBalances,
|
||||
parsedAmounts,
|
||||
ticks,
|
||||
price,
|
||||
pricesAtTicks,
|
||||
position,
|
||||
noLiquidity,
|
||||
liquidityMinted,
|
||||
poolTokenPercentage,
|
||||
error,
|
||||
errorMessage,
|
||||
invalidPool,
|
||||
invalidRange,
|
||||
outOfRange,
|
||||
depositADisabled,
|
||||
depositBDisabled,
|
||||
}
|
||||
}
|
||||
|
@ -5,37 +5,30 @@ import {
|
||||
typeInput,
|
||||
typeLowerRangeInput,
|
||||
typeUpperRangeInput,
|
||||
RangeType,
|
||||
updateRangeType,
|
||||
typeStartPriceInput,
|
||||
} from './actions'
|
||||
|
||||
export interface MintState {
|
||||
readonly independentField: Field
|
||||
readonly typedValue: string
|
||||
readonly otherTypedValue: string // for the case when there's no liquidity
|
||||
readonly startPriceTypedValue: string // for the case when there's no liquidity
|
||||
readonly lowerRangeTypedValue: string
|
||||
readonly upperRangeTypedValue: string
|
||||
readonly rangeType: RangeType
|
||||
}
|
||||
|
||||
export const initialState: MintState = {
|
||||
independentField: Field.CURRENCY_A,
|
||||
typedValue: '',
|
||||
otherTypedValue: '',
|
||||
startPriceTypedValue: '',
|
||||
lowerRangeTypedValue: '',
|
||||
upperRangeTypedValue: '',
|
||||
rangeType: RangeType.RATE,
|
||||
}
|
||||
|
||||
export default createReducer<MintState>(initialState, (builder) =>
|
||||
builder
|
||||
.addCase(resetMintState, () => initialState)
|
||||
.addCase(updateRangeType, (state, { payload: { rangeType } }) => {
|
||||
return {
|
||||
...state,
|
||||
rangeType,
|
||||
}
|
||||
})
|
||||
.addCase(typeLowerRangeInput, (state, { payload: { typedValue } }) => {
|
||||
return {
|
||||
...state,
|
||||
@ -48,6 +41,12 @@ export default createReducer<MintState>(initialState, (builder) =>
|
||||
upperRangeTypedValue: typedValue,
|
||||
}
|
||||
})
|
||||
.addCase(typeStartPriceInput, (state, { payload: { typedValue } }) => {
|
||||
return {
|
||||
...state,
|
||||
startPriceTypedValue: typedValue,
|
||||
}
|
||||
})
|
||||
.addCase(typeInput, (state, { payload: { field, typedValue, noLiquidity } }) => {
|
||||
if (noLiquidity) {
|
||||
// they're typing into the field they've last typed in
|
||||
|
@ -1,18 +1,27 @@
|
||||
import { Tick } from './hooks'
|
||||
/**
|
||||
* @todo
|
||||
* udpate to actually parse input and calculate next tick
|
||||
*/
|
||||
export function tryParseTick(value?: string): Tick | undefined {
|
||||
if (!value) {
|
||||
import { Pool, priceToClosestTick, nearestUsableTick } from '@uniswap/v3-sdk/dist/'
|
||||
import { Price, Token } from '@uniswap/sdk-core'
|
||||
import { tryParseAmount } from 'state/swap/hooks'
|
||||
|
||||
export function tryParseTick(
|
||||
baseToken: Token | undefined,
|
||||
quoteToken: Token | undefined,
|
||||
pool: Pool | undefined,
|
||||
value?: string
|
||||
): number | undefined {
|
||||
if (!value || !baseToken || !quoteToken || !pool) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
return { rate: parseFloat(value) * 0.999 }
|
||||
} catch (error) {
|
||||
console.debug(`Failed to parse range amount: "${value}"`, error)
|
||||
}
|
||||
const amount = tryParseAmount(value, quoteToken)
|
||||
|
||||
return undefined
|
||||
const amountOne = tryParseAmount('1', baseToken)
|
||||
|
||||
if (!amount || !amountOne) return undefined
|
||||
|
||||
// parse the typed value into a price, token0 should always be base currency based on url
|
||||
const price = new Price(baseToken, quoteToken, amountOne.raw, amount.raw)
|
||||
|
||||
const tick = priceToClosestTick(price)
|
||||
|
||||
return nearestUsableTick(tick, pool.tickSpacing)
|
||||
}
|
||||
|
169
src/state/mint/v2.ts
Normal file
169
src/state/mint/v2.ts
Normal file
@ -0,0 +1,169 @@
|
||||
import { useMemo } from 'react'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { Currency, CurrencyAmount, ETHER, Percent, Price, TokenAmount } from '@uniswap/sdk-core'
|
||||
import JSBI from 'jsbi'
|
||||
import { PairState, usePair } from '../../data/V2'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { wrappedCurrency, wrappedCurrencyAmount } from '../../utils/wrappedCurrency'
|
||||
import { tryParseAmount } from '../swap/hooks'
|
||||
import { useCurrencyBalances } from '../wallet/hooks'
|
||||
import { Field } from './actions'
|
||||
import { useMintState } from './hooks'
|
||||
|
||||
const ZERO = JSBI.BigInt(0)
|
||||
|
||||
export function useV2DerivedMintInfo(
|
||||
currencyA: Currency | undefined,
|
||||
currencyB: Currency | undefined
|
||||
): {
|
||||
dependentField: Field
|
||||
currencies: { [field in Field]?: Currency }
|
||||
pair?: Pair | null
|
||||
pairState: PairState
|
||||
currencyBalances: { [field in Field]?: CurrencyAmount }
|
||||
parsedAmounts: { [field in Field]?: CurrencyAmount }
|
||||
price?: Price
|
||||
noLiquidity?: boolean
|
||||
liquidityMinted?: TokenAmount
|
||||
poolTokenPercentage?: Percent
|
||||
error?: string
|
||||
} {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
const { independentField, typedValue, otherTypedValue } = useMintState()
|
||||
|
||||
const dependentField = independentField === Field.CURRENCY_A ? Field.CURRENCY_B : Field.CURRENCY_A
|
||||
|
||||
// tokens
|
||||
const currencies: { [field in Field]?: Currency } = useMemo(
|
||||
() => ({
|
||||
[Field.CURRENCY_A]: currencyA ?? undefined,
|
||||
[Field.CURRENCY_B]: currencyB ?? undefined,
|
||||
}),
|
||||
[currencyA, currencyB]
|
||||
)
|
||||
|
||||
// pair
|
||||
const [pairState, pair] = usePair(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B])
|
||||
const totalSupply = useTotalSupply(pair?.liquidityToken)
|
||||
|
||||
const noLiquidity: boolean =
|
||||
pairState === PairState.NOT_EXISTS || Boolean(totalSupply && JSBI.equal(totalSupply.raw, ZERO))
|
||||
|
||||
// balances
|
||||
const balances = useCurrencyBalances(account ?? undefined, [
|
||||
currencies[Field.CURRENCY_A],
|
||||
currencies[Field.CURRENCY_B],
|
||||
])
|
||||
const currencyBalances: { [field in Field]?: CurrencyAmount } = {
|
||||
[Field.CURRENCY_A]: balances[0],
|
||||
[Field.CURRENCY_B]: balances[1],
|
||||
}
|
||||
|
||||
// amounts
|
||||
const independentAmount: CurrencyAmount | undefined = tryParseAmount(typedValue, currencies[independentField])
|
||||
const dependentAmount: CurrencyAmount | undefined = useMemo(() => {
|
||||
if (noLiquidity) {
|
||||
if (otherTypedValue && currencies[dependentField]) {
|
||||
return tryParseAmount(otherTypedValue, currencies[dependentField])
|
||||
}
|
||||
return undefined
|
||||
} else if (independentAmount) {
|
||||
// we wrap the currencies just to get the price in terms of the other token
|
||||
const wrappedIndependentAmount = wrappedCurrencyAmount(independentAmount, chainId)
|
||||
const [tokenA, tokenB] = [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)]
|
||||
if (tokenA && tokenB && wrappedIndependentAmount && pair) {
|
||||
const dependentCurrency = dependentField === Field.CURRENCY_B ? currencyB : currencyA
|
||||
const dependentTokenAmount =
|
||||
dependentField === Field.CURRENCY_B
|
||||
? pair.priceOf(tokenA).quote(wrappedIndependentAmount)
|
||||
: pair.priceOf(tokenB).quote(wrappedIndependentAmount)
|
||||
return dependentCurrency === ETHER ? CurrencyAmount.ether(dependentTokenAmount.raw) : dependentTokenAmount
|
||||
}
|
||||
return undefined
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}, [noLiquidity, otherTypedValue, currencies, dependentField, independentAmount, currencyA, chainId, currencyB, pair])
|
||||
|
||||
const parsedAmounts: { [field in Field]: CurrencyAmount | undefined } = useMemo(() => {
|
||||
return {
|
||||
[Field.CURRENCY_A]: independentField === Field.CURRENCY_A ? independentAmount : dependentAmount,
|
||||
[Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount,
|
||||
}
|
||||
}, [dependentAmount, independentAmount, independentField])
|
||||
|
||||
const price = useMemo(() => {
|
||||
if (noLiquidity) {
|
||||
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
|
||||
if (currencyAAmount && currencyBAmount) {
|
||||
return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw)
|
||||
}
|
||||
return undefined
|
||||
} else {
|
||||
const wrappedCurrencyA = wrappedCurrency(currencyA, chainId)
|
||||
return pair && wrappedCurrencyA ? pair.priceOf(wrappedCurrencyA) : undefined
|
||||
}
|
||||
}, [chainId, currencyA, noLiquidity, pair, parsedAmounts])
|
||||
|
||||
// liquidity minted
|
||||
const liquidityMinted = useMemo(() => {
|
||||
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
|
||||
const [tokenAmountA, tokenAmountB] = [
|
||||
wrappedCurrencyAmount(currencyAAmount, chainId),
|
||||
wrappedCurrencyAmount(currencyBAmount, chainId),
|
||||
]
|
||||
if (pair && totalSupply && tokenAmountA && tokenAmountB) {
|
||||
return pair.getLiquidityMinted(totalSupply, tokenAmountA, tokenAmountB)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}, [parsedAmounts, chainId, pair, totalSupply])
|
||||
|
||||
const poolTokenPercentage = useMemo(() => {
|
||||
if (liquidityMinted && totalSupply) {
|
||||
return new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}, [liquidityMinted, totalSupply])
|
||||
|
||||
let error: string | undefined
|
||||
if (!account) {
|
||||
error = 'Connect Wallet'
|
||||
}
|
||||
|
||||
if (pairState === PairState.INVALID) {
|
||||
error = error ?? 'Invalid pair'
|
||||
}
|
||||
|
||||
if (!parsedAmounts[Field.CURRENCY_A] || !parsedAmounts[Field.CURRENCY_B]) {
|
||||
error = error ?? 'Enter an amount'
|
||||
}
|
||||
|
||||
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
|
||||
|
||||
if (currencyAAmount && currencyBalances?.[Field.CURRENCY_A]?.lessThan(currencyAAmount)) {
|
||||
error = 'Insufficient ' + currencies[Field.CURRENCY_A]?.symbol + ' balance'
|
||||
}
|
||||
|
||||
if (currencyBAmount && currencyBalances?.[Field.CURRENCY_B]?.lessThan(currencyBAmount)) {
|
||||
error = 'Insufficient ' + currencies[Field.CURRENCY_B]?.symbol + ' balance'
|
||||
}
|
||||
|
||||
return {
|
||||
dependentField,
|
||||
currencies,
|
||||
pair,
|
||||
pairState,
|
||||
currencyBalances,
|
||||
parsedAmounts,
|
||||
price,
|
||||
noLiquidity,
|
||||
liquidityMinted,
|
||||
poolTokenPercentage,
|
||||
error,
|
||||
}
|
||||
}
|
@ -171,7 +171,7 @@ export function useSingleContractMultipleData(
|
||||
|
||||
const calls = useMemo(
|
||||
() =>
|
||||
contract && fragment && callInputs && callInputs.length > 0
|
||||
contract && fragment && callInputs?.length > 0 && callInputs.every((inputs) => isValidMethodArgs(inputs))
|
||||
? callInputs.map<Call>((inputs) => {
|
||||
return {
|
||||
address: contract.address,
|
||||
@ -180,7 +180,7 @@ export function useSingleContractMultipleData(
|
||||
}
|
||||
})
|
||||
: [],
|
||||
[callInputs, contract, fragment, gasRequired]
|
||||
[contract, fragment, callInputs, gasRequired]
|
||||
)
|
||||
|
||||
const results = useCallsData(calls, options)
|
||||
|
@ -53,6 +53,7 @@ export function colors(darkMode: boolean): Colors {
|
||||
bg3: darkMode ? '#40444F' : '#EDEEF2',
|
||||
bg4: darkMode ? '#565A69' : '#CED0D9',
|
||||
bg5: darkMode ? '#6C7284' : '#888D9B',
|
||||
bg6: darkMode ? '#1A2028' : '#888D9B',
|
||||
|
||||
//specialty colors
|
||||
modalBG: darkMode ? 'rgba(0,0,0,.425)' : 'rgba(0,0,0,0.3)',
|
||||
|
1
src/theme/styled.d.ts
vendored
1
src/theme/styled.d.ts
vendored
@ -20,6 +20,7 @@ export interface Colors {
|
||||
bg3: Color
|
||||
bg4: Color
|
||||
bg5: Color
|
||||
bg6: Color
|
||||
|
||||
modalBG: Color
|
||||
advancedBG: Color
|
||||
|
7
src/types/position.d.ts
vendored
7
src/types/position.d.ts
vendored
@ -1,11 +1,4 @@
|
||||
import { BigNumberish } from '@ethersproject/bignumber'
|
||||
import { basisPointsToPercent } from 'utils'
|
||||
|
||||
const FEE_BIPS = {
|
||||
FIVE: basisPointsToPercent(5),
|
||||
THIRTY: basisPointsToPercent(30),
|
||||
ONE_HUNDRED: basisPointsToPercent(100),
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
feesEarned: Record<string, BigNumberish>
|
||||
|
@ -12,7 +12,7 @@ export function hexToUint8Array(hex: string): Uint8Array {
|
||||
return arr
|
||||
}
|
||||
|
||||
const UTF_8_DECODER = new TextDecoder()
|
||||
const UTF_8_DECODER = new TextDecoder('utf-8')
|
||||
|
||||
/**
|
||||
* Returns the URI representation of the content hash for supported codecs
|
||||
|
13
src/utils/getTickToPrice.ts
Normal file
13
src/utils/getTickToPrice.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Token, Price } from '@uniswap/sdk-core'
|
||||
import { tickToPrice } from '@uniswap/v3-sdk'
|
||||
|
||||
export function getTickToPrice(
|
||||
baseToken: Token | undefined,
|
||||
quoteToken: Token | undefined,
|
||||
tick: number | undefined
|
||||
): Price | undefined {
|
||||
if (!baseToken || !quoteToken || tick === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return tickToPrice(baseToken, quoteToken, tick)
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import { getAddress } from '@ethersproject/address'
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { abi as IUniswapV2Router02ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router02.json'
|
||||
|
||||
import { ROUTER_ADDRESS } from '../constants'
|
||||
import { ChainId, Percent, Token, CurrencyAmount, Currency, ETHER } from '@uniswap/sdk-core'
|
||||
import { JSBI } from '@uniswap/v2-sdk'
|
||||
@ -123,3 +125,7 @@ export function supportedChainId(chainId: number): ChainId | undefined {
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const formattedFeeAmount = (feeAmount: FeeAmount): number => {
|
||||
return feeAmount / 10000
|
||||
}
|
||||
|
@ -2,7 +2,13 @@ import { supportedChainId } from 'utils'
|
||||
import { ChainId, Currency, CurrencyAmount, ETHER, Token, TokenAmount, WETH9 } from '@uniswap/sdk-core'
|
||||
|
||||
export function wrappedCurrency(currency: Currency | undefined, chainId: ChainId | undefined): Token | undefined {
|
||||
return chainId && currency === ETHER ? WETH9[chainId] : currency instanceof Token ? currency : undefined
|
||||
return chainId && currency === ETHER
|
||||
? chainId === 1337
|
||||
? new Token(1337, '0xbBca0fFBFE60F60071630A8c80bb6253dC9D6023', 18, 'WETH', 'WETH9')
|
||||
: WETH9[chainId]
|
||||
: currency instanceof Token
|
||||
? currency
|
||||
: undefined
|
||||
}
|
||||
|
||||
export function wrappedCurrencyAmount(
|
||||
|
Loading…
Reference in New Issue
Block a user