Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c8b45c8e5 | ||
|
|
2eb5ff3c5c | ||
|
|
a8864614c1 | ||
|
|
19d25de1d9 | ||
|
|
a9e7c6f560 | ||
|
|
333c3a289a | ||
|
|
d06451cb98 | ||
|
|
c1297b2aa3 | ||
|
|
6ab6f4daa5 | ||
|
|
ae559d164a | ||
|
|
a90318cbe9 | ||
|
|
93cf4b358e | ||
|
|
ad8aff0b90 | ||
|
|
8757e413bb | ||
|
|
78ac7650ee | ||
|
|
f3e9b513ba | ||
|
|
412a10e0d9 | ||
|
|
411b690ae4 | ||
|
|
b721824c7f | ||
|
|
2451a5c9ec | ||
|
|
d62dd585ea | ||
|
|
6ba3c6cf93 | ||
|
|
e069ba1153 | ||
|
|
4ebc467c58 | ||
|
|
7b7e4e61d2 | ||
|
|
e8936f500b | ||
|
|
3d8a5ed6fe | ||
|
|
c3d2c43861 | ||
|
|
de5816e573 | ||
|
|
5a2f81c4bf | ||
|
|
c34742c23b | ||
|
|
6225462645 | ||
|
|
c21b9379fd | ||
|
|
1610356a18 | ||
|
|
1b8cee7e87 | ||
|
|
a7c6ce499d | ||
|
|
e9fc55550d | ||
|
|
3eeb467266 | ||
|
|
585d67c44a |
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -9,3 +9,5 @@ updates:
|
||||
- dependency-name: '@uniswap/default-token-list'
|
||||
- dependency-name: '@uniswap/token-lists'
|
||||
- dependency-name: '@uniswap/widgets'
|
||||
reviewers:
|
||||
- 'Uniswap/dependabot-reviewers'
|
||||
|
||||
25
.github/workflows/test.yml
vendored
25
.github/workflows/test.yml
vendored
@@ -36,13 +36,12 @@ jobs:
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
|
||||
cypress-build:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn prepare
|
||||
|
||||
- run: yarn build
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@@ -50,6 +49,24 @@ jobs:
|
||||
path: build
|
||||
if-no-files-found: error
|
||||
|
||||
size-tests:
|
||||
needs: [build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
- run: yarn test:size
|
||||
|
||||
|
||||
cypress-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: actions/cache@v3
|
||||
id: cypress-cache
|
||||
with:
|
||||
@@ -59,7 +76,7 @@ jobs:
|
||||
run: yarn cypress install
|
||||
|
||||
cypress-test-matrix:
|
||||
needs: cypress-build
|
||||
needs: [build, cypress-build]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -96,7 +113,7 @@ jobs:
|
||||
|
||||
# Included as a single job to check against for cypress test success, as cypress runs in a matrix.
|
||||
cypress-tests:
|
||||
needs: cypress-test-matrix
|
||||
needs: [cypress-test-matrix]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo 'Finished cypress tests https\://dashboard.cypress.io/projects/yp82ef'
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@
|
||||
/src/locales/**/pseudo.po
|
||||
|
||||
# generated graphql types
|
||||
/src/graphql/**/__generated__
|
||||
schema.graphql
|
||||
|
||||
# dependencies
|
||||
|
||||
@@ -64,6 +64,14 @@ To run _all_ cypress integration tests _from the command line_:
|
||||
yarn cypress:run
|
||||
```
|
||||
|
||||
## Adding a new dependency
|
||||
|
||||
Adding many new dependencies would cause bloat, so we have a test to guard against this: `scripts/test-size.js`. This will run as part of CI with every PR.
|
||||
|
||||
If you *need* to add a new dependency, and it causes the generated build to exceed its size quota, you'll need to increase the quota. Do so in `scripts/test-size.js`.
|
||||
|
||||
You can also run the test on your last build using `yarn build && yarn test:size`. If you exceed the size quota, it will let you know what to do :).
|
||||
|
||||
## Engineering standards
|
||||
|
||||
Code merged into the `main` branch of this repository should adhere to high standards of correctness and maintainability.
|
||||
|
||||
@@ -5,9 +5,7 @@ const BONSAI_COLLECTION_ADDRESS = '0xec9c519d49856fd2f8133a0741b4dbe002ce211b'
|
||||
|
||||
describe('Testing nfts', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/').then(() => {
|
||||
cy.get(getTestSelector('FiatOnrampAnnouncement-close')).first().click()
|
||||
})
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
it('should load nft leaderboard', () => {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Pool', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/pool').then(() => {
|
||||
@@ -8,18 +6,12 @@ describe('Pool', () => {
|
||||
})
|
||||
|
||||
it('add liquidity links to /add/ETH', () => {
|
||||
cy.get('body')
|
||||
.then((body) => {
|
||||
if (body.find(getTestSelector('FiatOnrampAnnouncement-close')).length > 0) {
|
||||
cy.get(getTestSelector('FiatOnrampAnnouncement-close')).click()
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
cy.get('#join-pool-button')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.url().should('contain', '/add/ETH')
|
||||
})
|
||||
})
|
||||
cy.get('body').then(() => {
|
||||
cy.get('#join-pool-button')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.url().should('contain', '/add/ETH')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -49,10 +49,6 @@ describe('Token details', () => {
|
||||
// Shiba predator token, low trading volume and also has warning modal
|
||||
cy.visit('/tokens/ethereum/0xa71d0588EAf47f12B13cF8eC750430d21DF04974')
|
||||
|
||||
// Should have missing price chart when price unavailable (expected for this token)
|
||||
if (cy.get('[data-cy="chart-header"]').contains('Price Unavailable')) {
|
||||
cy.get('[data-cy="missing-chart"]').should('exist')
|
||||
}
|
||||
// Stats should have: TVL, 24H Volume, 52W low, 52W high
|
||||
cy.get(getTestSelector('token-details-stats')).should('exist')
|
||||
cy.get(getTestSelector('token-details-stats')).within(() => {
|
||||
|
||||
35
package.json
35
package.json
@@ -8,11 +8,11 @@
|
||||
"contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/abis/types \"./src/abis/**/*.json\"",
|
||||
"contracts:compile:v3": "typechain --target ethers-v5 --out-dir src/types/v3 \"./node_modules/@uniswap/**/artifacts/contracts/**/*[!dbg].json\"",
|
||||
"contracts:compile": "yarn contracts:compile:abi && yarn contracts:compile:v3",
|
||||
"graphql:fetch": "node fetch-schema.js",
|
||||
"graphql:fetch": "node scripts/fetch-schema.js",
|
||||
"graphql:generate:data": "graphql-codegen --config apollo-codegen.ts",
|
||||
"graphql:generate:thegraph": "graphql-codegen --config apollo-codegen_thegraph.ts",
|
||||
"graphql:generate": "yarn graphql:generate:data && yarn graphql:generate:thegraph",
|
||||
"prei18n:extract": "node prei18n-extract.js",
|
||||
"prei18n:extract": "node scripts/prei18n-extract.js",
|
||||
"i18n:extract": "lingui extract --locale en-US",
|
||||
"i18n:compile": "yarn i18n:extract && lingui compile",
|
||||
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
|
||||
@@ -23,6 +23,7 @@
|
||||
"deduplicate": "yarn-deduplicate --strategy=highest",
|
||||
"lint": "yarn eslint .",
|
||||
"test": "craco test --coverage",
|
||||
"test:size": "node scripts/test-size.js",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
"cypress:run": "cypress run --browser chrome --e2e",
|
||||
"postinstall": "patch-package"
|
||||
@@ -125,7 +126,7 @@
|
||||
"@lingui/react": "^3.14.0",
|
||||
"@looksrare/sdk": "^0.10.2",
|
||||
"@metamask/jazzicon": "^2.0.0",
|
||||
"@opensea/seaport-js": "^1.0.2",
|
||||
"@opensea/seaport-js": "^1.0.10",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@reach/dialog": "^0.10.3",
|
||||
"@reach/portal": "^0.10.3",
|
||||
@@ -134,7 +135,7 @@
|
||||
"@sentry/react": "^7.29.0",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "^1.3.1",
|
||||
"@uniswap/analytics-events": "^2.4.0",
|
||||
"@uniswap/analytics-events": "^2.6.0",
|
||||
"@uniswap/conedison": "^1.4.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
@@ -143,7 +144,7 @@
|
||||
"@uniswap/redux-multicall": "^1.1.8",
|
||||
"@uniswap/router-sdk": "^1.3.0",
|
||||
"@uniswap/sdk-core": "^3.2.0",
|
||||
"@uniswap/smart-order-router": "^3.6.0",
|
||||
"@uniswap/smart-order-router": "^3.6.1",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.30",
|
||||
"@uniswap/universal-router-sdk": "^1.3.6",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
@@ -152,7 +153,7 @@
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@uniswap/widgets": "^2.44.5",
|
||||
"@uniswap/widgets": "^2.47.3",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
@@ -165,16 +166,16 @@
|
||||
"@visx/responsive": "^2.10.0",
|
||||
"@visx/shape": "^2.11.1",
|
||||
"@walletconnect/ethereum-provider": "^1.8.0",
|
||||
"@web3-react/coinbase-wallet": "8.1.1-beta.0",
|
||||
"@web3-react/core": "8.1.1-beta.0",
|
||||
"@web3-react/eip1193": "8.1.1-beta.0",
|
||||
"@web3-react/empty": "8.1.1-beta.0",
|
||||
"@web3-react/gnosis-safe": "8.1.1-beta.0",
|
||||
"@web3-react/metamask": "8.1.1-beta.0",
|
||||
"@web3-react/network": "8.1.1-beta.0",
|
||||
"@web3-react/types": "8.1.1-beta.0",
|
||||
"@web3-react/url": "8.1.1-beta.0",
|
||||
"@web3-react/walletconnect": "8.1.1-beta.0",
|
||||
"@web3-react/coinbase-wallet": "8.1.2-beta.0",
|
||||
"@web3-react/core": "8.1.2-beta.0",
|
||||
"@web3-react/eip1193": "8.1.2-beta.0",
|
||||
"@web3-react/empty": "8.1.2-beta.0",
|
||||
"@web3-react/gnosis-safe": "8.1.2-beta.0",
|
||||
"@web3-react/metamask": "8.1.2-beta.0",
|
||||
"@web3-react/network": "8.1.2-beta.0",
|
||||
"@web3-react/types": "8.1.2-beta.0",
|
||||
"@web3-react/url": "8.1.2-beta.0",
|
||||
"@web3-react/walletconnect": "8.1.2-beta.0",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"array.prototype.flatmap": "^1.2.4",
|
||||
"cids": "^1.0.0",
|
||||
@@ -236,7 +237,7 @@
|
||||
"workbox-navigation-preload": "^6.1.0",
|
||||
"workbox-precaching": "^6.1.0",
|
||||
"workbox-routing": "^6.1.0",
|
||||
"zustand": "^4.0.0-rc.1"
|
||||
"zustand": "^4.3.6"
|
||||
},
|
||||
"engines": {
|
||||
"npm": "please-use-yarn",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
require('dotenv').config({ path: '.env.production' })
|
||||
|
||||
const { exec } = require('child_process')
|
||||
const dataConfig = require('./graphql.config')
|
||||
const thegraphConfig = require('./graphql_thegraph.config')
|
||||
const dataConfig = require('../graphql.config')
|
||||
const thegraphConfig = require('../graphql_thegraph.config')
|
||||
|
||||
function fetchSchema(url, outputFile) {
|
||||
exec(
|
||||
56
scripts/test-size.js
Normal file
56
scripts/test-size.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/* eslint-disable no-undef */
|
||||
const assert = require('assert')
|
||||
const chalk = require('chalk')
|
||||
const fs = require('fs')
|
||||
const gzipSize = require('gzip-size').sync
|
||||
const path = require('path')
|
||||
|
||||
const buildDir = path.join(__dirname, '../build')
|
||||
|
||||
let entrypoints
|
||||
try {
|
||||
entrypoints = require(path.join(buildDir, 'asset-manifest.json')).entrypoints
|
||||
} catch (e) {
|
||||
console.log(chalk.yellow('You must build first: `yarn build`'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// The last recorded size for these assets, as reported by `yarn build`.
|
||||
const LAST_SIZE_MAIN_KB = 374
|
||||
|
||||
// This is the async-loaded js, called <number>.<hash>.js, with a matching css file.
|
||||
const LAST_SIZE_ENTRY_KB = 1417
|
||||
|
||||
const SIZE_TOLERANCE_KB = 5
|
||||
|
||||
const jsEntrypoints = entrypoints.filter((entrypoint) => entrypoint.endsWith('js'))
|
||||
assert(jsEntrypoints.length === 3)
|
||||
|
||||
let fail = false
|
||||
console.log('File sizes after gzip:\n')
|
||||
jsEntrypoints.forEach((entrypoint) => {
|
||||
const name = entrypoint.match(/\/([\w\d-]*)\./)[1]
|
||||
const size = gzipSize(fs.readFileSync(path.join(buildDir, entrypoint))) / 1024
|
||||
|
||||
let maxSize = LAST_SIZE_ENTRY_KB + SIZE_TOLERANCE_KB
|
||||
if (name === 'runtime-main') {
|
||||
return
|
||||
} else if (name === 'main') {
|
||||
maxSize = LAST_SIZE_MAIN_KB + SIZE_TOLERANCE_KB
|
||||
}
|
||||
|
||||
const report = `\t${size.toFixed(2).padEnd(8)}kB\t${chalk.dim(
|
||||
`max: ${maxSize.toFixed().padEnd(4)} kB`
|
||||
)}\t${entrypoint}`
|
||||
if (maxSize > size) {
|
||||
console.log(chalk.green(report))
|
||||
} else {
|
||||
console.log(chalk.red(report), '\tdid you import an unnecessary dependency?')
|
||||
fail = true
|
||||
}
|
||||
})
|
||||
if (fail) {
|
||||
console.log(chalk.yellow('\nOne or more of your files has grown too large.'))
|
||||
console.log(chalk.yellow('Reduce the file size or update the size limit (in scripts/test-size.js)'))
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -1 +1,11 @@
|
||||
<svg id="Celo_Rings" data-name="Celo Rings" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 950 950"><defs><style>.cls-1{fill:#fbcc5c;}.cls-2{fill:#35d07f;}.cls-3{fill:#5ea33b;}</style></defs><title>Artboard 1</title><path id="Bottom_Ring" data-name="Bottom Ring" class="cls-1" d="M375,850c151.88,0,275-123.12,275-275S526.88,300,375,300,100,423.12,100,575,223.12,850,375,850Zm0,100C167.9,950,0,782.1,0,575S167.9,200,375,200,750,367.9,750,575,582.1,950,375,950Z"/><path id="Top_Ring" data-name="Top Ring" class="cls-2" d="M575,650c151.88,0,275-123.12,275-275S726.88,100,575,100,300,223.12,300,375,423.12,650,575,650Zm0,100c-207.1,0-375-167.9-375-375S367.9,0,575,0,950,167.9,950,375,782.1,750,575,750Z"/><path id="Rings_Overlap" data-name="Rings Overlap" class="cls-3" d="M587.39,750a274.38,274.38,0,0,0,54.55-108.06A274.36,274.36,0,0,0,750,587.4a373.63,373.63,0,0,1-29.16,133.45A373.62,373.62,0,0,1,587.39,750ZM308.06,308.06A274.36,274.36,0,0,0,200,362.6a373.63,373.63,0,0,1,29.16-133.45A373.62,373.62,0,0,1,362.61,200,274.38,274.38,0,0,0,308.06,308.06Z"/></svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 250 250" style="enable-background:new 0 0 250 250;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FCFF52;}
|
||||
</style>
|
||||
<circle class="st0" cx="125" cy="125" r="125"/>
|
||||
<path d="M188.9,60.7H60.7v128.2h128.2v-44.8h-21.3c-7.3,16.3-23.8,27.7-42.7,27.7c-26,0-47.1-21.3-47.1-47.1c0-25.9,21.1-47,47.1-47
|
||||
c19.3,0,35.8,11.7,43.1,28.4h20.9V60.7z"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 630 B |
@@ -205,26 +205,6 @@ export const ButtonOutlined = styled(BaseButton)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonYellow = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
color: ${({ theme }) => theme.accentWarning};
|
||||
&:focus {
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
}
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.stateOverlayHover};
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
&:active {
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
opacity: 60%;
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonEmpty = styled(BaseButton)`
|
||||
background-color: transparent;
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
@@ -422,7 +402,7 @@ function pickThemeButtonBackgroundColor({ theme, emphasis }: { theme: DefaultThe
|
||||
case ButtonEmphasis.high:
|
||||
return theme.accentAction
|
||||
case ButtonEmphasis.promotional:
|
||||
return theme.accentTextLightPrimary
|
||||
return theme.accentActionSoft
|
||||
case ButtonEmphasis.highSoft:
|
||||
return theme.accentActionSoft
|
||||
case ButtonEmphasis.low:
|
||||
@@ -476,7 +456,7 @@ function pickThemeButtonTextColor({ theme, emphasis }: { theme: DefaultTheme; em
|
||||
switch (emphasis) {
|
||||
case ButtonEmphasis.high:
|
||||
case ButtonEmphasis.promotional:
|
||||
return theme.accentTextLightPrimary
|
||||
return theme.accentAction
|
||||
case ButtonEmphasis.highSoft:
|
||||
return theme.accentAction
|
||||
case ButtonEmphasis.low:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t } from '@lingui/macro'
|
||||
import { formatCurrencyAmount, formatPriceImpact, NumberType } from '@uniswap/conedison/format'
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import { formatNumber, formatPriceImpact, NumberType } from '@uniswap/conedison/format'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
@@ -22,7 +22,7 @@ export function FiatValue({
|
||||
priceImpact,
|
||||
isLoading = false,
|
||||
}: {
|
||||
fiatValue: CurrencyAmount<Currency> | null | undefined
|
||||
fiatValue: number | null | undefined
|
||||
priceImpact?: Percent
|
||||
isLoading?: boolean
|
||||
}) {
|
||||
@@ -56,7 +56,7 @@ export function FiatValue({
|
||||
<FiatLoadingBubble />
|
||||
) : (
|
||||
<div>
|
||||
{fiatValue && <>{formatCurrencyAmount(fiatValue, NumberType.FiatTokenPrice)}</>}
|
||||
{fiatValue ? formatNumber(fiatValue, NumberType.FiatTokenPrice) : undefined}
|
||||
{priceImpact && (
|
||||
<span style={{ color: priceImpactColor }}>
|
||||
{' '}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
@@ -195,7 +195,7 @@ interface SwapCurrencyInputPanelProps {
|
||||
pair?: Pair | null
|
||||
hideInput?: boolean
|
||||
otherCurrency?: Currency | null
|
||||
fiatValue?: CurrencyAmount<Token> | null
|
||||
fiatValue?: number | null
|
||||
priceImpact?: Percent
|
||||
id: string
|
||||
showCommonBases?: boolean
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
@@ -182,7 +182,7 @@ interface CurrencyInputPanelProps {
|
||||
pair?: Pair | null
|
||||
hideInput?: boolean
|
||||
otherCurrency?: Currency | null
|
||||
fiatValue?: CurrencyAmount<Token> | null
|
||||
fiatValue?: number | null
|
||||
priceImpact?: Percent
|
||||
id: string
|
||||
showCommonBases?: boolean
|
||||
|
||||
@@ -2,8 +2,8 @@ import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'fe
|
||||
import { GqlRoutingVariant, useGqlRoutingFlag } from 'featureFlags/flags/gqlRouting'
|
||||
import { NftGraphqlVariant, useNftGraphqlFlag } from 'featureFlags/flags/nftlGraphql'
|
||||
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
|
||||
import { Permit2Variant, usePermit2Flag } from 'featureFlags/flags/permit2'
|
||||
import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget'
|
||||
import { TaxServiceVariant, useTaxServiceBannerFlag } from 'featureFlags/flags/taxServiceBanner'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
|
||||
@@ -206,12 +206,6 @@ export default function FeatureFlagModal() {
|
||||
<X size={24} />
|
||||
</CloseButton>
|
||||
</Header>
|
||||
<FeatureFlagOption
|
||||
variant={Permit2Variant}
|
||||
value={usePermit2Flag()}
|
||||
featureFlag={FeatureFlag.permit2}
|
||||
label="Permit 2 / Universal Router"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={PayWithAnyTokenVariant}
|
||||
value={usePayWithAnyTokenFlag()}
|
||||
@@ -236,6 +230,12 @@ export default function FeatureFlagModal() {
|
||||
featureFlag={FeatureFlag.nftGraphql}
|
||||
label="Migrate NFT read endpoints to GQL"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={TaxServiceVariant}
|
||||
value={useTaxServiceBannerFlag()}
|
||||
featureFlag={FeatureFlag.taxService}
|
||||
label="Tax Service Banner"
|
||||
/>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
variant={TraceJsonRpcVariant}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { InterfaceEventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import fiatMaskUrl from 'assets/svg/fiat_mask.svg'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useToggleWalletDropdown } from 'state/application/hooks'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import { useFiatOnrampAck } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
const Arrow = styled.div`
|
||||
top: -4px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
width: 16px;
|
||||
|
||||
::before {
|
||||
background: hsl(315.75, 93%, 83%);
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
transform: rotate(45deg);
|
||||
width: 16px;
|
||||
}
|
||||
`
|
||||
const ArrowWrapper = styled.div`
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 90%;
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
min-height: 92px;
|
||||
|
||||
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
|
||||
right: 36px;
|
||||
}
|
||||
`
|
||||
|
||||
const CloseIcon = styled(X)`
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 1;
|
||||
`
|
||||
const Wrapper = styled.button`
|
||||
background: radial-gradient(105% 250% at 100% 5%, hsla(318, 95%, 85%) 1%, hsla(331, 80%, 75%, 0.1) 84%),
|
||||
linear-gradient(180deg, hsla(296, 92%, 67%, 0.5) 0%, hsla(313, 96%, 60%, 0.5) 130%);
|
||||
background-color: hsla(297, 93%, 68%, 1);
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-align: start;
|
||||
max-width: 320px;
|
||||
min-height: 92px;
|
||||
width: 100%;
|
||||
|
||||
:before {
|
||||
background-image: url(${fiatMaskUrl});
|
||||
background-repeat: no-repeat;
|
||||
content: '';
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: -154px; // roughly width of fiat mask image
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
|
||||
const Header = styled(ThemedText.SubHeader)`
|
||||
color: white;
|
||||
margin: 0;
|
||||
padding: 12px 12px 4px;
|
||||
position: relative;
|
||||
`
|
||||
const Body = styled(ThemedText.BodySmall)`
|
||||
color: white;
|
||||
margin: 0 12px 12px 12px !important;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const ANNOUNCEMENT_RENDERED = 'FiatOnrampAnnouncement-rendered'
|
||||
const ANNOUNCEMENT_DISMISSED = 'FiatOnrampAnnouncement-dismissed'
|
||||
|
||||
const MAX_RENDER_COUNT = 3
|
||||
export function FiatOnrampAnnouncement() {
|
||||
const { account } = useWeb3React()
|
||||
const [acks, acknowledge] = useFiatOnrampAck()
|
||||
const [localClose, setLocalClose] = useState(false)
|
||||
useEffect(() => {
|
||||
if (!sessionStorage.getItem(ANNOUNCEMENT_RENDERED)) {
|
||||
acknowledge({ renderCount: acks?.renderCount + 1 })
|
||||
sessionStorage.setItem(ANNOUNCEMENT_RENDERED, 'true')
|
||||
}
|
||||
}, [acknowledge, acks])
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setLocalClose(true)
|
||||
localStorage.setItem(ANNOUNCEMENT_DISMISSED, 'true')
|
||||
}, [])
|
||||
|
||||
const toggleWalletDropdown = useToggleWalletDropdown()
|
||||
const handleClick = useCallback(() => {
|
||||
sendAnalyticsEvent(InterfaceEventName.FIAT_ONRAMP_BANNER_CLICKED)
|
||||
toggleWalletDropdown()
|
||||
acknowledge({ user: true })
|
||||
}, [acknowledge, toggleWalletDropdown])
|
||||
|
||||
const openModal = useAppSelector((state) => state.application.openModal)
|
||||
|
||||
if (
|
||||
!account ||
|
||||
acks?.user ||
|
||||
localStorage.getItem(ANNOUNCEMENT_DISMISSED) ||
|
||||
acks?.renderCount >= MAX_RENDER_COUNT ||
|
||||
isMobile ||
|
||||
openModal !== null ||
|
||||
localClose
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<ArrowWrapper>
|
||||
<Arrow />
|
||||
<CloseIcon onClick={handleClose} data-testid="FiatOnrampAnnouncement-close" />
|
||||
<Wrapper onClick={handleClick}>
|
||||
<Header>
|
||||
<Trans>Buy crypto</Trans>
|
||||
</Header>
|
||||
<Body>
|
||||
<Trans>Get tokens at the best prices in web3 on Uniswap, powered by Moonpay.</Trans>
|
||||
</Body>
|
||||
</Wrapper>
|
||||
</ArrowWrapper>
|
||||
)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { BagIcon, HundredsOverflowIcon, TagIcon } from 'nft/components/icons'
|
||||
import { useBag, useSellAsset } from 'nft/hooks'
|
||||
import { useCallback } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import shallow from 'zustand/shallow'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
|
||||
const CounterDot = styled.div`
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
|
||||
@@ -4,6 +4,8 @@ import { sendAnalyticsEvent, Trace, TraceEvent, useTrace } from '@uniswap/analyt
|
||||
import { BrowserEvent, InterfaceElementName, InterfaceEventName, InterfaceSectionName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import clsx from 'clsx'
|
||||
import { useNftGraphqlEnabled } from 'featureFlags/flags/nftlGraphql'
|
||||
import { useCollectionSearch } from 'graphql/data/nft/CollectionSearch'
|
||||
import { useSearchTokens } from 'graphql/data/SearchTokens'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
@@ -49,12 +51,13 @@ export const SearchBar = () => {
|
||||
const { pathname } = useLocation()
|
||||
const isMobile = useIsMobile()
|
||||
const isTablet = useIsTablet()
|
||||
const isNftGraphqlEnabled = useNftGraphqlEnabled()
|
||||
|
||||
useOnClickOutside(searchRef, () => {
|
||||
isOpen && toggleOpen()
|
||||
})
|
||||
|
||||
const { data: collections, isLoading: collectionsAreLoading } = useQuery(
|
||||
const { data: queryCollections, isLoading: queryCollectionsAreLoading } = useQuery(
|
||||
['searchCollections', debouncedSearchValue],
|
||||
() => fetchSearchCollections(debouncedSearchValue),
|
||||
{
|
||||
@@ -65,12 +68,26 @@ export const SearchBar = () => {
|
||||
}
|
||||
)
|
||||
|
||||
const { data: gqlCollections, loading: gqlCollectionsAreLoading } = useCollectionSearch(debouncedSearchValue)
|
||||
|
||||
const { gatedCollections, gatedCollectionsAreLoading } = useMemo(() => {
|
||||
return isNftGraphqlEnabled
|
||||
? {
|
||||
gatedCollections: gqlCollections,
|
||||
gatedCollectionsAreLoading: gqlCollectionsAreLoading,
|
||||
}
|
||||
: {
|
||||
gatedCollections: queryCollections,
|
||||
gatedCollectionsAreLoading: queryCollectionsAreLoading,
|
||||
}
|
||||
}, [gqlCollections, gqlCollectionsAreLoading, isNftGraphqlEnabled, queryCollections, queryCollectionsAreLoading])
|
||||
|
||||
const { chainId } = useWeb3React()
|
||||
const { data: tokens, loading: tokensAreLoading } = useSearchTokens(debouncedSearchValue, chainId ?? 1)
|
||||
|
||||
const isNFTPage = useIsNftPage()
|
||||
|
||||
const [reducedTokens, reducedCollections] = organizeSearchResults(isNFTPage, tokens ?? [], collections ?? [])
|
||||
const [reducedTokens, reducedCollections] = organizeSearchResults(isNFTPage, tokens ?? [], gatedCollections ?? [])
|
||||
|
||||
// close dropdown on escape
|
||||
useEffect(() => {
|
||||
@@ -86,7 +103,7 @@ export const SearchBar = () => {
|
||||
return () => {
|
||||
document.removeEventListener('keydown', escapeKeyDownHandler)
|
||||
}
|
||||
}, [isOpen, toggleOpen, collections])
|
||||
}, [isOpen, toggleOpen, gatedCollections])
|
||||
|
||||
// clear searchbar when changing pages
|
||||
useEffect(() => {
|
||||
@@ -208,7 +225,7 @@ export const SearchBar = () => {
|
||||
collections={reducedCollections}
|
||||
queryText={debouncedSearchValue}
|
||||
hasInput={debouncedSearchValue.length > 0}
|
||||
isLoading={tokensAreLoading || collectionsAreLoading}
|
||||
isLoading={tokensAreLoading || gatedCollectionsAreLoading}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -156,7 +156,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
|
||||
}
|
||||
}, [toggleOpen, isHovered, token, navigate, handleClick, tokenDetailsPath])
|
||||
|
||||
const arrow = getDeltaArrow(token.market?.pricePercentChange?.value, 18)
|
||||
const arrow = getDeltaArrow(token.project?.markets?.[0]?.pricePercentChange?.value, 18)
|
||||
|
||||
return (
|
||||
<Link
|
||||
@@ -186,16 +186,16 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
|
||||
</Row>
|
||||
|
||||
<Column className={styles.suggestionSecondaryContainer}>
|
||||
{!!token.market?.price?.value && (
|
||||
{!!token.project?.markets?.[0]?.price?.value && (
|
||||
<>
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{formatUSDPrice(token.market.price.value)}</Box>
|
||||
<Box className={styles.primaryText}>{formatUSDPrice(token.project.markets[0].price.value)}</Box>
|
||||
</Row>
|
||||
<PriceChangeContainer>
|
||||
<ArrowCell>{arrow}</ArrowCell>
|
||||
<ThemedText.BodySmall>
|
||||
<DeltaText delta={token.market?.pricePercentChange?.value}>
|
||||
{Math.abs(token.market?.pricePercentChange?.value ?? 0).toFixed(2)}%
|
||||
<DeltaText delta={token.project.markets[0].pricePercentChange?.value}>
|
||||
{Math.abs(token.project.markets[0].pricePercentChange?.value ?? 0).toFixed(2)}%
|
||||
</DeltaText>
|
||||
</ThemedText.BodySmall>
|
||||
</PriceChangeContainer>
|
||||
|
||||
@@ -150,7 +150,10 @@ export function NetworkAlert() {
|
||||
return null
|
||||
}
|
||||
|
||||
const { label, logoUrl, bridge } = getChainInfo(chainId)
|
||||
const chainInfo = getChainInfo(chainId)
|
||||
if (!chainInfo) return null
|
||||
|
||||
const { label, logoUrl, bridge } = chainInfo
|
||||
const textColor = TEXT_COLORS[chainId]
|
||||
|
||||
return bridge ? (
|
||||
|
||||
BIN
src/components/TaxServiceModal/CointrackerFullLogo.png
Normal file
BIN
src/components/TaxServiceModal/CointrackerFullLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
src/components/TaxServiceModal/CointrackerLogo.png
Normal file
BIN
src/components/TaxServiceModal/CointrackerLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
160
src/components/TaxServiceModal/TaxServiceBanner.tsx
Normal file
160
src/components/TaxServiceModal/TaxServiceBanner.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
|
||||
import { bodySmall, subhead } from 'nft/css/common.css'
|
||||
import { useState } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useIsDarkMode } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { opacify } from 'theme/utils'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
import TaxServiceModal from '.'
|
||||
import CointrackerLogo from './CointrackerLogo.png'
|
||||
import TokenTaxLogo from './TokenTaxLogo.png'
|
||||
|
||||
const PopupContainer = styled.div<{ show: boolean; isDarkMode: boolean }>`
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 13px;
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
display: ${({ show }) => (show ? 'flex' : 'none')};
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
right: clamp(0px, 1vw, 16px);
|
||||
z-index: ${Z_INDEX.sticky};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.slow} opacity ${timing.in}`};
|
||||
width: 320px;
|
||||
height: 156px;
|
||||
bottom: 50px;
|
||||
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
|
||||
background-image: url(${CointrackerLogo}), url(${TokenTaxLogo});
|
||||
background-size: 15%, 20%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: top right 75px, bottom 5px right 7px;
|
||||
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
|
||||
background-size: 48px, 64px;
|
||||
background-position: top right 75px, bottom 20px right 7px;
|
||||
}
|
||||
|
||||
opacity: ${({ isDarkMode }) => (isDarkMode ? '0.9' : '0.25')};
|
||||
}
|
||||
`
|
||||
|
||||
const InnerContainer = styled.div<{ isDarkMode: boolean }>`
|
||||
border-radius: 12px;
|
||||
cursor: auto;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
background-color: ${({ isDarkMode, theme }) =>
|
||||
isDarkMode ? opacify(10, theme.accentAction) : opacify(4, theme.accentAction)};
|
||||
`
|
||||
|
||||
const Button = styled(ThemeButton)`
|
||||
margin-top: auto;
|
||||
margin-right: auto;
|
||||
padding: 8px 24px;
|
||||
gap: 8px;
|
||||
border-radius: 12px;
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
user-select: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 70%;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
export const StyledXButton = styled(X)`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
}
|
||||
&:active {
|
||||
opacity: ${({ theme }) => theme.opacity.click};
|
||||
}
|
||||
`
|
||||
|
||||
const TAX_SERVICE_DISMISSED = 'TaxServiceToast-dismissed'
|
||||
|
||||
export default function TaxServiceBanner() {
|
||||
const isDarkMode = useIsDarkMode()
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const sessionStorageTaxServiceDismissed = sessionStorage.getItem(TAX_SERVICE_DISMISSED)
|
||||
|
||||
if (!sessionStorageTaxServiceDismissed) {
|
||||
sessionStorage.setItem(TAX_SERVICE_DISMISSED, 'false')
|
||||
}
|
||||
const [bannerOpen, setBannerOpen] = useState(sessionStorageTaxServiceDismissed !== 'true')
|
||||
const onDismiss = useCallback(() => {
|
||||
setModalOpen(false)
|
||||
}, [])
|
||||
|
||||
const openTaxModal = useCallback(() => {
|
||||
setModalOpen(true)
|
||||
}, [])
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
sessionStorage.setItem(TAX_SERVICE_DISMISSED, 'true')
|
||||
setBannerOpen(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<PopupContainer show={bannerOpen} isDarkMode={isDarkMode}>
|
||||
<InnerContainer isDarkMode={isDarkMode}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<TextContainer data-testid="tax-service-description">
|
||||
<div className={subhead} style={{ paddingBottom: '12px' }}>
|
||||
<Trans>Save on your crypto taxes</Trans>
|
||||
</div>
|
||||
<div className={bodySmall} style={{ paddingBottom: '12px' }}>
|
||||
<Trans>Get up to a 20% discount on CoinTracker or TokenTax.</Trans>{' '}
|
||||
</div>
|
||||
</TextContainer>
|
||||
<StyledXButton size={20} onClick={handleClose} />
|
||||
</div>
|
||||
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.TAX_SERVICE_BANNER_CTA_BUTTON}
|
||||
>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
emphasis={ButtonEmphasis.promotional}
|
||||
onClick={openTaxModal}
|
||||
data-testid="learn-more-button"
|
||||
>
|
||||
<Trans>Learn more</Trans>
|
||||
</Button>
|
||||
</TraceEvent>
|
||||
</InnerContainer>
|
||||
<TaxServiceModal isOpen={modalOpen} onDismiss={onDismiss} />
|
||||
</PopupContainer>
|
||||
)
|
||||
}
|
||||
BIN
src/components/TaxServiceModal/TokenTaxFullLogo.png
Normal file
BIN
src/components/TaxServiceModal/TokenTaxFullLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
src/components/TaxServiceModal/TokenTaxLogo.png
Normal file
BIN
src/components/TaxServiceModal/TokenTaxLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
17
src/components/TaxServiceModal/index.test.tsx
Normal file
17
src/components/TaxServiceModal/index.test.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { render, screen } from '../../test-utils'
|
||||
import TaxServiceModal from './'
|
||||
import TaxServiceBanner from './TaxServiceBanner'
|
||||
|
||||
it('renders Tax Service Modal content', async () => {
|
||||
render(<TaxServiceModal isOpen={true} onDismiss={() => null} />)
|
||||
expect(screen.getByText('Save 10% on all plans')).toBeInTheDocument()
|
||||
expect(screen.getByText('New and existing users save up to 20%')).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('tax-service-option-button')).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('renders Tax Service Banner', async () => {
|
||||
render(<TaxServiceBanner />)
|
||||
expect(screen.getByText('Save on your crypto taxes')).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('learn-more-button')).toHaveLength(1)
|
||||
expect(screen.getByText('Get up to a 20% discount on CoinTracker or TokenTax.')).toBeInTheDocument()
|
||||
})
|
||||
127
src/components/TaxServiceModal/index.tsx
Normal file
127
src/components/TaxServiceModal/index.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { ButtonEmphasis } from 'components/Button'
|
||||
import { ButtonSize, ThemeButton } from 'components/Button'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { bodySmall, subhead } from 'nft/css/common.css'
|
||||
import { memo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import Modal from '../Modal'
|
||||
import CointrackerFullLogo from './CointrackerFullLogo.png'
|
||||
import { StyledXButton } from './TaxServiceBanner'
|
||||
import TokenTaxFullLogo from './TokenTaxFullLogo.png'
|
||||
|
||||
interface TaxServiceModalProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
}
|
||||
|
||||
interface TaxServiceOptionProps {
|
||||
logo: any
|
||||
description: string
|
||||
url: string
|
||||
}
|
||||
|
||||
const InnerContainer = styled.div`
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
width: 420px;
|
||||
height: 268px;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
gap: 20px;
|
||||
padding: 16px;
|
||||
`
|
||||
|
||||
const TaxOptionContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const TaxOptionDescription = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
text-align: center;
|
||||
`
|
||||
|
||||
const TaxOption = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
border-radius: 12px;
|
||||
cursor: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const StyledImageContainer = styled(Box)`
|
||||
width: 75%;
|
||||
height: 80%;
|
||||
cursor: auto;
|
||||
object-fit: contain;
|
||||
`
|
||||
|
||||
const Button = styled(ThemeButton)`
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
`
|
||||
|
||||
const TOKEN_TAX_URL = 'https://tokentax.co/uniswap?via=uniswap'
|
||||
const COINTRACKER_URL = 'https://www.cointracker.io/partner/uniswap?utm_source=uniswap'
|
||||
|
||||
const TOKEN_TAX_DESCRIPTION = 'Save 10% on all plans'
|
||||
const COINTRACKER_DESCRIPTION = 'New and existing users save up to 20%'
|
||||
|
||||
function TaxServiceOption({ description, logo, url }: TaxServiceOptionProps) {
|
||||
return (
|
||||
<TaxOption tabIndex={0}>
|
||||
<StyledImageContainer as="img" src={logo} draggable={false} />
|
||||
<TaxOptionDescription className={bodySmall}>{description}</TaxOptionDescription>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={
|
||||
url.includes('tokentax')
|
||||
? InterfaceElementName.TAX_SERVICE_TOKENTAX_BUTTON
|
||||
: InterfaceElementName.TAX_SERVICE_COINTRACKER_BUTTON
|
||||
}
|
||||
>
|
||||
<a href={url} target="_blank" rel="noreferrer" style={{ textDecoration: 'none' }}>
|
||||
<Button size={ButtonSize.medium} emphasis={ButtonEmphasis.medium} data-testid="tax-service-option-button">
|
||||
Get started
|
||||
</Button>
|
||||
</a>
|
||||
</TraceEvent>
|
||||
</TaxOption>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(function TaxServiceModal({ isOpen, onDismiss }: TaxServiceModalProps) {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90} minHeight={false}>
|
||||
<InnerContainer>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', userSelect: 'none' }}>
|
||||
<div className={subhead}>
|
||||
<Trans>Save on your crypto taxes</Trans>
|
||||
</div>
|
||||
<StyledXButton size={20} onClick={onDismiss} />
|
||||
</div>
|
||||
<TaxOptionContainer>
|
||||
<TaxServiceOption description={COINTRACKER_DESCRIPTION} logo={CointrackerFullLogo} url={COINTRACKER_URL} />
|
||||
<TaxServiceOption description={TOKEN_TAX_DESCRIPTION} logo={TokenTaxFullLogo} url={TOKEN_TAX_URL} />
|
||||
</TaxOptionContainer>
|
||||
</InnerContainer>
|
||||
</Modal>
|
||||
)
|
||||
})
|
||||
@@ -4,7 +4,7 @@ import { ButtonPrimary } from 'components/Button'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import TokenSafetyLabel from 'components/TokenSafety/TokenSafetyLabel'
|
||||
import { checkWarning, getWarningCopy, TOKEN_SAFETY_ARTICLE, Warning } from 'constants/tokenSafety'
|
||||
import { checkWarning, getWarningCopy, TOKEN_SAFETY_ARTICLE, Warning, WARNING_LEVEL } from 'constants/tokenSafety'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { ExternalLink as LinkIconFeather } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
@@ -77,7 +77,7 @@ const Buttons = ({
|
||||
showCancel,
|
||||
}: {
|
||||
warning: Warning
|
||||
onContinue: () => void
|
||||
onContinue?: () => void
|
||||
onCancel: () => void
|
||||
onBlocked?: () => void
|
||||
showCancel?: boolean
|
||||
@@ -251,32 +251,49 @@ export default function TokenSafety({
|
||||
<Trans>Learn more</Trans>
|
||||
</StyledExternalLink>
|
||||
)
|
||||
const tokenNotFoundWarning = {
|
||||
level: WARNING_LEVEL.UNKNOWN,
|
||||
message: <Trans>Token not found</Trans>,
|
||||
canProceed: false,
|
||||
}
|
||||
|
||||
return (
|
||||
displayWarning && (
|
||||
<Wrapper>
|
||||
<Container>
|
||||
<AutoColumn>
|
||||
<LogoContainer>{logos}</LogoContainer>
|
||||
</AutoColumn>
|
||||
<ShortColumn>
|
||||
<SafetyLabel warning={displayWarning} />
|
||||
</ShortColumn>
|
||||
<ShortColumn>
|
||||
<InfoText>
|
||||
{heading} {description} {learnMoreUrl}
|
||||
</InfoText>
|
||||
</ShortColumn>
|
||||
<LinkColumn>{urls}</LinkColumn>
|
||||
<Buttons
|
||||
warning={displayWarning}
|
||||
onContinue={acknowledge}
|
||||
onCancel={onCancel}
|
||||
onBlocked={onBlocked}
|
||||
showCancel={showCancel}
|
||||
/>
|
||||
</Container>
|
||||
</Wrapper>
|
||||
)
|
||||
return displayWarning ? (
|
||||
<Wrapper>
|
||||
<Container>
|
||||
<AutoColumn>
|
||||
<LogoContainer>{logos}</LogoContainer>
|
||||
</AutoColumn>
|
||||
<ShortColumn>
|
||||
<SafetyLabel warning={displayWarning} />
|
||||
</ShortColumn>
|
||||
<ShortColumn>
|
||||
<InfoText>
|
||||
{heading} {description} {learnMoreUrl}
|
||||
</InfoText>
|
||||
</ShortColumn>
|
||||
<LinkColumn>{urls}</LinkColumn>
|
||||
<Buttons
|
||||
warning={displayWarning}
|
||||
onContinue={acknowledge}
|
||||
onCancel={onCancel}
|
||||
onBlocked={onBlocked}
|
||||
showCancel={showCancel}
|
||||
/>
|
||||
</Container>
|
||||
</Wrapper>
|
||||
) : (
|
||||
<Wrapper>
|
||||
<Container>
|
||||
<ShortColumn>
|
||||
<SafetyLabel warning={tokenNotFoundWarning} />
|
||||
</ShortColumn>
|
||||
<ShortColumn>
|
||||
<InfoText>
|
||||
{heading} {description} {learnMoreUrl}
|
||||
</InfoText>
|
||||
</ShortColumn>
|
||||
<Buttons warning={tokenNotFoundWarning} onCancel={onCancel} showCancel={true} />
|
||||
</Container>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import TimePeriodSelector from './TimeSelector'
|
||||
function usePriceHistory(tokenPriceData: TokenPriceQuery): PricePoint[] | undefined {
|
||||
// Appends the current price to the end of the priceHistory array
|
||||
const priceHistory = useMemo(() => {
|
||||
const market = tokenPriceData.token?.market
|
||||
const market = tokenPriceData.token?.project?.markets?.[0]
|
||||
const priceHistory = market?.priceHistory?.filter(isPricePoint)
|
||||
const currentPrice = market?.price?.value
|
||||
if (Array.isArray(priceHistory) && currentPrice !== undefined) {
|
||||
|
||||
@@ -138,15 +138,6 @@ export function PriceChart({ width, height, prices: originalPrices, timePeriod }
|
||||
)
|
||||
|
||||
const chartAvailable = !!prices && prices.length > 0
|
||||
const missingPricesMessage = !chartAvailable ? (
|
||||
prices?.length === 0 ? (
|
||||
<>
|
||||
<Trans>Missing price data due to recently low trading volume on Uniswap v3</Trans>
|
||||
</>
|
||||
) : (
|
||||
<Trans>Missing chart data</Trans>
|
||||
)
|
||||
) : null
|
||||
|
||||
// first price point on the x-axis of the current time period's chart
|
||||
const startingPrice = originalPrices?.[0] ?? DATA_EMPTY
|
||||
@@ -287,12 +278,18 @@ export function PriceChart({ width, height, prices: originalPrices, timePeriod }
|
||||
) : (
|
||||
<>
|
||||
<MissingPrice>Price Unavailable</MissingPrice>
|
||||
<ThemedText.Caption style={{ color: theme.textTertiary }}>{missingPricesMessage}</ThemedText.Caption>
|
||||
<ThemedText.Caption style={{ color: theme.textTertiary }}>
|
||||
<Trans>Missing price data</Trans>
|
||||
</ThemedText.Caption>
|
||||
</>
|
||||
)}
|
||||
</ChartHeader>
|
||||
{!chartAvailable ? (
|
||||
<MissingPriceChart width={width} height={graphHeight} message={!!displayPrice.value && missingPricesMessage} />
|
||||
<MissingPriceChart
|
||||
width={width}
|
||||
height={graphHeight}
|
||||
message={!!displayPrice.value && <Trans>Price history unavailable</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<svg data-cy="price-chart" width={width} height={graphHeight} style={{ minWidth: '100%' }}>
|
||||
<AnimatedInLineChart
|
||||
|
||||
@@ -200,8 +200,8 @@ export default function TokenDetails({
|
||||
<StatsSection
|
||||
TVL={tokenQueryData?.market?.totalValueLocked?.value}
|
||||
volume24H={tokenQueryData?.market?.volume24H?.value}
|
||||
priceHigh52W={tokenQueryData?.market?.priceHigh52W?.value}
|
||||
priceLow52W={tokenQueryData?.market?.priceLow52W?.value}
|
||||
priceHigh52W={tokenQueryData?.project?.markets?.[0]?.priceHigh52W?.value}
|
||||
priceLow52W={tokenQueryData?.project?.markets?.[0]?.priceLow52W?.value}
|
||||
/>
|
||||
<Hr />
|
||||
<AboutSection
|
||||
|
||||
@@ -438,7 +438,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
const filterNetwork = lowercaseChainName.toUpperCase()
|
||||
const chainId = CHAIN_NAME_TO_CHAIN_ID[filterNetwork]
|
||||
const timePeriod = useAtomValue(filterTimeAtom)
|
||||
const delta = token.market?.pricePercentChange?.value
|
||||
const delta = token.project?.markets?.[0]?.pricePercentChange?.value
|
||||
const arrow = getDeltaArrow(delta)
|
||||
const smallArrow = getDeltaArrow(delta, 14)
|
||||
const formattedDelta = formatDelta(delta)
|
||||
@@ -478,7 +478,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
price={
|
||||
<ClickableContent>
|
||||
<PriceInfoCell>
|
||||
{formatUSDPrice(token.market?.price?.value)}
|
||||
{formatUSDPrice(token.project?.markets?.[0]?.price?.value)}
|
||||
<PercentChangeInfoCell>
|
||||
<ArrowCell>{smallArrow}</ArrowCell>
|
||||
<DeltaText delta={delta}>{formattedDelta}</DeltaText>
|
||||
@@ -509,7 +509,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
width={width}
|
||||
height={height}
|
||||
tokenData={token}
|
||||
pricePercentChange={token.market?.pricePercentChange?.value}
|
||||
pricePercentChange={token.project?.markets?.[0]?.pricePercentChange?.value}
|
||||
sparklineMap={props.sparklineMap}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -4,4 +4,3 @@ export const LARGE_MEDIA_BREAKPOINT = '840px'
|
||||
export const MEDIUM_MEDIA_BREAKPOINT = '720px'
|
||||
export const SMALL_MEDIA_BREAKPOINT = '540px'
|
||||
export const MOBILE_MEDIA_BREAKPOINT = '420px'
|
||||
// export const SMALL_MOBILE_MEDIA_BREAKPOINT = '390px'
|
||||
|
||||
@@ -2,6 +2,8 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import AddressClaimModal from 'components/claim/AddressClaimModal'
|
||||
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
|
||||
import FiatOnrampModal from 'components/FiatOnrampModal'
|
||||
import TaxServiceBanner from 'components/TaxServiceModal/TaxServiceBanner'
|
||||
import { useTaxServiceBannerEnabled } from 'featureFlags/flags/taxServiceBanner'
|
||||
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
|
||||
import { lazy } from 'react'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
@@ -18,6 +20,7 @@ export default function TopLevelModals() {
|
||||
const { account } = useWeb3React()
|
||||
useAccountRiskCheck(account)
|
||||
const accountBlocked = Boolean(blockedAccountModalOpen && account)
|
||||
const taxServiceEnabled = useTaxServiceBannerEnabled()
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -27,6 +30,7 @@ export default function TopLevelModals() {
|
||||
<TransactionCompleteModal />
|
||||
<AirdropModal />
|
||||
<FiatOnrampModal />
|
||||
{taxServiceEnabled && <TaxServiceBanner />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,16 +12,14 @@ import { SupportedChainId } from 'constants/chains'
|
||||
import useCopyClipboard from 'hooks/useCopyClipboard'
|
||||
import useStablecoinPrice from 'hooks/useStablecoinPrice'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import ms from 'ms.macro'
|
||||
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { Copy, CreditCard, ExternalLink as ExternalLinkIcon, Info, Power } from 'react-feather'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useCurrencyBalanceString } from 'state/connection/hooks'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { useFiatOnrampAck } from 'state/user/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import styled, { css, keyframes } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
@@ -48,7 +46,7 @@ const BuyCryptoButtonBorderKeyframes = keyframes`
|
||||
}
|
||||
`
|
||||
|
||||
const BuyCryptoButton = styled(ThemeButton)<{ $animateBorder: boolean }>`
|
||||
const BuyCryptoButton = styled(ThemeButton)`
|
||||
border-color: transparent;
|
||||
border-radius: 12px;
|
||||
border-style: solid;
|
||||
@@ -60,7 +58,6 @@ const BuyCryptoButton = styled(ThemeButton)<{ $animateBorder: boolean }>`
|
||||
animation-fill-mode: none;
|
||||
animation-iteration-count: 2;
|
||||
animation-name: ${BuyCryptoButtonBorderKeyframes};
|
||||
animation-play-state: ${({ $animateBorder }) => ($animateBorder ? 'running' : 'paused')};
|
||||
animation-timing-function: ${({ theme }) => theme.transition.timing.inOut};
|
||||
`
|
||||
const WalletButton = styled(ThemeButton)`
|
||||
@@ -223,26 +220,6 @@ const AuthenticatedHeader = () => {
|
||||
closeModal()
|
||||
}, [clearCollectionFilters, closeModal, navigate, resetSellAssets, setSellPageState])
|
||||
|
||||
// animate the border of the buy crypto button when a user navigates here from the feature announcement
|
||||
// can be removed when components/FiatOnrampAnnouncment.tsx is no longer used
|
||||
const [acknowledgements, acknowledge] = useFiatOnrampAck()
|
||||
const animateBuyCryptoButtonBorder = acknowledgements?.user && !acknowledgements.system
|
||||
useEffect(() => {
|
||||
let stale = false
|
||||
let timeoutId = 0
|
||||
if (animateBuyCryptoButtonBorder) {
|
||||
timeoutId = setTimeout(() => {
|
||||
if (stale) return
|
||||
acknowledge({ system: true })
|
||||
}, ms`2 seconds`) as unknown as number
|
||||
// as unknown as number is necessary so it's not incorrectly typed as a NodeJS.Timeout
|
||||
}
|
||||
return () => {
|
||||
stale = true
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
}, [acknowledge, animateBuyCryptoButtonBorder])
|
||||
|
||||
const openFiatOnrampModal = useOpenModal(ApplicationModal.FIAT_ONRAMP)
|
||||
const openFoRModalWithAnalytics = useCallback(() => {
|
||||
sendAnalyticsEvent(InterfaceEventName.FIAT_ONRAMP_WIDGET_OPENED)
|
||||
@@ -314,7 +291,6 @@ const AuthenticatedHeader = () => {
|
||||
<Trans>View and sell NFTs</Trans>
|
||||
</ProfileButton>
|
||||
<BuyCryptoButton
|
||||
$animateBorder={animateBuyCryptoButtonBorder}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
onClick={handleBuyCryptoClick}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent, user } from '@uniswap/analytics'
|
||||
import { CustomUserProperties, InterfaceEventName, WalletConnectionResult } from '@uniswap/analytics-events'
|
||||
import { getWalletMeta } from '@uniswap/conedison/provider/meta'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { WalletConnect } from '@web3-react/walletconnect'
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
getIsInjected,
|
||||
getIsMetaMaskWallet,
|
||||
} from 'connection/utils'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
@@ -130,16 +132,19 @@ const sendAnalyticsEventAndUserInfo = (
|
||||
account: string,
|
||||
walletType: string,
|
||||
chainId: number | undefined,
|
||||
isReconnect: boolean
|
||||
isReconnect: boolean,
|
||||
peerWalletAgent: string | undefined
|
||||
) => {
|
||||
sendAnalyticsEvent(InterfaceEventName.WALLET_CONNECT_TXN_COMPLETED, {
|
||||
result: WalletConnectionResult.SUCCEEDED,
|
||||
wallet_address: account,
|
||||
wallet_type: walletType,
|
||||
is_reconnect: isReconnect,
|
||||
peer_wallet_agent: peerWalletAgent,
|
||||
})
|
||||
user.set(CustomUserProperties.WALLET_ADDRESS, account)
|
||||
user.set(CustomUserProperties.WALLET_TYPE, walletType)
|
||||
user.set(CustomUserProperties.PEER_WALLET_AGENT, peerWalletAgent ?? '')
|
||||
if (chainId) {
|
||||
user.postInsert(CustomUserProperties.ALL_WALLET_CHAIN_IDS, chainId)
|
||||
}
|
||||
@@ -156,7 +161,7 @@ export default function WalletModal({
|
||||
ENSName?: string
|
||||
}) {
|
||||
const dispatch = useAppDispatch()
|
||||
const { connector, account, chainId } = useWeb3React()
|
||||
const { connector, account, chainId, provider } = useWeb3React()
|
||||
const previousAccount = usePrevious(account)
|
||||
|
||||
const location = useLocation()
|
||||
@@ -203,7 +208,7 @@ export default function WalletModal({
|
||||
|
||||
// Keep the network connector in sync with any active user connector to prevent chain-switching on wallet disconnection.
|
||||
useEffect(() => {
|
||||
if (chainId && connector !== networkConnection.connector) {
|
||||
if (chainId && isSupportedChain(chainId) && connector !== networkConnection.connector) {
|
||||
networkConnection.connector.activate(chainId)
|
||||
}
|
||||
}, [chainId, connector])
|
||||
@@ -212,13 +217,14 @@ export default function WalletModal({
|
||||
useEffect(() => {
|
||||
if (account && account !== lastActiveWalletAddress) {
|
||||
const walletType = getConnectionName(getConnection(connector).type)
|
||||
const peerWalletAgent = provider ? getWalletMeta(provider)?.agent : undefined
|
||||
const isReconnect =
|
||||
connectedWallets.filter((wallet) => wallet.account === account && wallet.walletType === walletType).length > 0
|
||||
sendAnalyticsEventAndUserInfo(account, walletType, chainId, isReconnect)
|
||||
sendAnalyticsEventAndUserInfo(account, walletType, chainId, isReconnect, peerWalletAgent)
|
||||
if (!isReconnect) addWalletToConnectedWallets({ account, walletType })
|
||||
}
|
||||
setLastActiveWalletAddress(account)
|
||||
}, [connectedWallets, addWalletToConnectedWallets, lastActiveWalletAddress, account, connector, chainId])
|
||||
}, [connectedWallets, addWalletToConnectedWallets, lastActiveWalletAddress, account, connector, chainId, provider])
|
||||
|
||||
const tryActivation = useCallback(
|
||||
async (connector: Connector) => {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { t, Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent, TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { FiatOnrampAnnouncement } from 'components/FiatOnrampAnnouncement'
|
||||
import { IconWrapper } from 'components/Identicon/StatusIcon'
|
||||
import WalletDropdown from 'components/WalletDropdown'
|
||||
import { getConnection } from 'connection/utils'
|
||||
@@ -318,7 +317,6 @@ export default function Web3Status() {
|
||||
return (
|
||||
<span ref={ref}>
|
||||
<Web3StatusInner />
|
||||
<FiatOnrampAnnouncement />
|
||||
<WalletModal ENSName={ENSName ?? undefined} pendingTransactions={pending} confirmedTransactions={confirmed} />
|
||||
<Portal>
|
||||
<span ref={walletRef}>
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
SwapWidgetSkeleton,
|
||||
} from '@uniswap/widgets'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { usePermit2Enabled } from 'featureFlags/flags/permit2'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
import {
|
||||
formatPercentInBasisPointsNumber,
|
||||
@@ -154,8 +153,6 @@ export default function Widget({
|
||||
[initialQuoteDate, trace]
|
||||
)
|
||||
|
||||
const permit2Enabled = usePermit2Enabled()
|
||||
|
||||
if (!(inputs.value.INPUT || inputs.value.OUTPUT)) {
|
||||
return <WidgetSkeleton />
|
||||
}
|
||||
@@ -166,7 +163,7 @@ export default function Widget({
|
||||
<SwapWidget
|
||||
hideConnectionUI
|
||||
brandedFooter={false}
|
||||
permit2={permit2Enabled}
|
||||
permit2
|
||||
routerUrl={WIDGET_ROUTER_URL}
|
||||
locale={locale}
|
||||
theme={theme}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { InterfaceSectionName, SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Currency, Field, SwapController, SwapEventHandlers, TradeType } from '@uniswap/widgets'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
@@ -50,7 +51,7 @@ export function useSyncWidgetInputs({
|
||||
}, [defaultTokens, tokens])
|
||||
|
||||
useEffect(() => {
|
||||
if (chainId !== previousChainId && !!previousChainId) {
|
||||
if (chainId !== previousChainId && !!previousChainId && isSupportedChain(chainId)) {
|
||||
setTokens({
|
||||
...tokens,
|
||||
[Field.INPUT]: undefined,
|
||||
|
||||
@@ -107,6 +107,8 @@ export function useSyncWidgetTransactions() {
|
||||
const eventProperties = {
|
||||
...formatSwapSignedAnalyticsEventProperties({
|
||||
trade,
|
||||
// TODO: add once Widgets adds fiat values to callback
|
||||
fiatValues: { amountIn: undefined, amountOut: undefined },
|
||||
txHash: transaction.receipt?.transactionHash ?? '',
|
||||
}),
|
||||
...trace,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro'
|
||||
import { Trace } from '@uniswap/analytics'
|
||||
import { InterfaceModalName } from '@uniswap/analytics-events'
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { ReactNode, useCallback, useMemo, useState } from 'react'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer'
|
||||
@@ -42,8 +42,8 @@ export default function ConfirmSwapModal({
|
||||
swapErrorMessage: ReactNode | undefined
|
||||
onDismiss: () => void
|
||||
swapQuoteReceivedDate: Date | undefined
|
||||
fiatValueInput?: CurrencyAmount<Token> | null
|
||||
fiatValueOutput?: CurrencyAmount<Token> | null
|
||||
fiatValueInput?: number
|
||||
fiatValueOutput?: number
|
||||
}) {
|
||||
// shouldLogModalCloseEvent lets the child SwapModalHeader component know when modal has been closed
|
||||
// and an event triggered by modal closing should be logged.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import useTransactionDeadline from 'hooks/useTransactionDeadline'
|
||||
import {
|
||||
formatPercentInBasisPointsNumber,
|
||||
@@ -31,8 +31,8 @@ interface AnalyticsEventProps {
|
||||
isAutoRouterApi: boolean
|
||||
swapQuoteReceivedDate: Date | undefined
|
||||
routes: RoutingDiagramEntry[]
|
||||
fiatValueInput?: CurrencyAmount<Token> | null
|
||||
fiatValueOutput?: CurrencyAmount<Token> | null
|
||||
fiatValueInput?: number
|
||||
fiatValueOutput?: number
|
||||
}
|
||||
|
||||
const formatRoutesEventProperties = (routes: RoutingDiagramEntry[]) => {
|
||||
@@ -83,8 +83,8 @@ const formatAnalyticsEventProperties = ({
|
||||
token_out_symbol: trade.outputAmount.currency.symbol,
|
||||
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
|
||||
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
|
||||
token_in_amount_usd: fiatValueInput ? parseFloat(fiatValueInput.toFixed(2)) : undefined,
|
||||
token_out_amount_usd: fiatValueOutput ? parseFloat(fiatValueOutput.toFixed(2)) : undefined,
|
||||
token_in_amount_usd: fiatValueInput,
|
||||
token_out_amount_usd: fiatValueOutput,
|
||||
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
|
||||
allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage),
|
||||
is_auto_router_api: isAutoRouterApi,
|
||||
@@ -118,8 +118,8 @@ export default function SwapModalFooter({
|
||||
swapErrorMessage: ReactNode | undefined
|
||||
disabledConfirm: boolean
|
||||
swapQuoteReceivedDate: Date | undefined
|
||||
fiatValueInput?: CurrencyAmount<Token> | null
|
||||
fiatValueOutput?: CurrencyAmount<Token> | null
|
||||
fiatValueInput?: number
|
||||
fiatValueOutput?: number
|
||||
}) {
|
||||
const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch
|
||||
const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto'
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { SwapEventName, SwapPriceUpdateUserResponse } from '@uniswap/analytics-events'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { useUSDPrice } from 'hooks/useUSDPrice'
|
||||
import { getPriceUpdateBasisPoints } from 'lib/utils/analytics'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AlertTriangle, ArrowDown } from 'react-feather'
|
||||
@@ -9,7 +10,6 @@ import { Text } from 'rebass'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useStablecoinValue } from '../../hooks/useStablecoinPrice'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { isAddress, shortenAddress } from '../../utils'
|
||||
import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
|
||||
@@ -78,8 +78,8 @@ export default function SwapModalHeader({
|
||||
const [lastExecutionPrice, setLastExecutionPrice] = useState(trade.executionPrice)
|
||||
const [priceUpdate, setPriceUpdate] = useState<number | undefined>()
|
||||
|
||||
const fiatValueInput = useStablecoinValue(trade.inputAmount)
|
||||
const fiatValueOutput = useStablecoinValue(trade.outputAmount)
|
||||
const fiatValueInput = useUSDPrice(trade.inputAmount)
|
||||
const fiatValueOutput = useUSDPrice(trade.outputAmount)
|
||||
|
||||
useEffect(() => {
|
||||
if (!trade.executionPrice.equalTo(lastExecutionPrice)) {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { formatNumber, NumberType } from '@uniswap/conedison/format'
|
||||
import { Currency, Price } from '@uniswap/sdk-core'
|
||||
import useStablecoinPrice from 'hooks/useStablecoinPrice'
|
||||
import { useUSDPrice } from 'hooks/useUSDPrice'
|
||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { useCallback, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { formatDollar, formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers'
|
||||
import { formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers'
|
||||
|
||||
interface TradePriceProps {
|
||||
price: Price<Currency, Currency>
|
||||
@@ -30,7 +32,8 @@ const StyledPriceContainer = styled.button`
|
||||
export default function TradePrice({ price }: TradePriceProps) {
|
||||
const [showInverted, setShowInverted] = useState<boolean>(false)
|
||||
|
||||
const usdcPrice = useStablecoinPrice(showInverted ? price.baseCurrency : price.quoteCurrency)
|
||||
const { baseCurrency, quoteCurrency } = price
|
||||
const usdPrice = useUSDPrice(tryParseCurrencyAmount('1', showInverted ? baseCurrency : quoteCurrency))
|
||||
|
||||
let formattedPrice: string
|
||||
try {
|
||||
@@ -56,9 +59,9 @@ export default function TradePrice({ price }: TradePriceProps) {
|
||||
title={text}
|
||||
>
|
||||
<ThemedText.BodySmall>{text}</ThemedText.BodySmall>{' '}
|
||||
{usdcPrice && (
|
||||
{usdPrice && (
|
||||
<ThemedText.DeprecatedDarkGray>
|
||||
<Trans>({formatDollar({ num: priceToPreciseFloat(usdcPrice), isPrice: true })})</Trans>
|
||||
<Trans>({formatNumber(usdPrice, NumberType.FiatTokenPrice)})</Trans>
|
||||
</ThemedText.DeprecatedDarkGray>
|
||||
)}
|
||||
</StyledPriceContainer>
|
||||
|
||||
@@ -15,16 +15,22 @@ export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap('0x7a250d56
|
||||
|
||||
// celo v3 addresses
|
||||
const CELO_V3_CORE_FACTORY_ADDRESSES = '0xAfE208a311B21f13EF87E33A90049fC17A7acDEc'
|
||||
const CELO_ROUTER_ADDRESS = '0x5615CDAb10dc425a742d643d949a7F474C01abc4'
|
||||
const CELO_V3_MIGRATOR_ADDRESSES = '0x3cFd4d48EDfDCC53D3f173F596f621064614C582'
|
||||
const CELO_MULTICALL_ADDRESS = '0x633987602DE5C4F337e3DbF265303A1080324204'
|
||||
const CELO_QUOTER_ADDRESSES = '0x82825d0554fA07f7FC52Ab63c961F330fdEFa8E8'
|
||||
const CELO_NONFUNGIBLE_POSITION_MANAGER_ADDRESSES = '0x3d79EdAaBC0EaB6F08ED885C05Fc0B014290D95A'
|
||||
const CELO_TICK_LENS_ADDRESSES = '0x5f115D9113F88e0a0Db1b5033D90D4a9690AcD3D'
|
||||
|
||||
// optimism goerli addresses
|
||||
const OPTIMISM_GOERLI_V3_CORE_FACTORY_ADDRESSES = '0xB656dA17129e7EB733A557f4EBc57B76CFbB5d10'
|
||||
const OPTIMISM_GOERLI_V3_MIGRATOR_ADDRESSES = '0xf6c55fBe84B1C8c3283533c53F51bC32F5C7Aba8'
|
||||
const OPTIMISM_GOERLI_MULTICALL_ADDRESS = '0x07F2D8a2a02251B62af965f22fC4744A5f96BCCd'
|
||||
const OPTIMISM_GOERLI_QUOTER_ADDRESSES = '0x9569CbA925c8ca2248772A9A4976A516743A246F'
|
||||
const OPTIMISM_GOERLI_NONFUNGIBLE_POSITION_MANAGER_ADDRESSES = '0x39Ca85Af2F383190cBf7d7c41ED9202D27426EF6'
|
||||
const OPTIMISM_GOERLI_TICK_LENS_ADDRESSES = '0xe6140Bd164b63E8BfCfc40D5dF952f83e171758e'
|
||||
|
||||
// arbitrum goerli v3 addresses
|
||||
const ARBITRUM_GOERLI_V3_CORE_FACTORY_ADDRESSES = '0x4893376342d5D7b3e31d4184c08b265e5aB2A3f6'
|
||||
const ARBITRUM_GOERLI_ROUTER_ADDRESS = '0xab7664500b19a7a2362Ab26081e6DfB971B6F1B0'
|
||||
const ARBITRUM_GOERLI_V3_MIGRATOR_ADDRESSES = '0xA815919D2584Ac3F76ea9CB62E6Fd40a43BCe0C3'
|
||||
const ARBITRUM_GOERLI_MULTICALL_ADDRESS = '0x8260CB40247290317a4c062F3542622367F206Ee'
|
||||
const ARBITRUM_GOERLI_QUOTER_ADDRESSES = '0x1dd92b83591781D0C6d98d07391eea4b9a6008FA'
|
||||
@@ -35,13 +41,13 @@ const ARBITRUM_GOERLI_TICK_LENS_ADDRESSES = '0xb52429333da969a0C79a60930a4Bf0020
|
||||
export const V3_CORE_FACTORY_ADDRESSES: AddressMap = {
|
||||
...constructSameAddressMap(V3_FACTORY_ADDRESS, [
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISM_GOERLI,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.POLYGON_MUMBAI,
|
||||
SupportedChainId.POLYGON,
|
||||
]),
|
||||
[SupportedChainId.CELO]: CELO_V3_CORE_FACTORY_ADDRESSES,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_V3_CORE_FACTORY_ADDRESSES,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: OPTIMISM_GOERLI_V3_CORE_FACTORY_ADDRESSES,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_V3_CORE_FACTORY_ADDRESSES,
|
||||
}
|
||||
|
||||
@@ -53,12 +59,12 @@ export const V3_MIGRATOR_ADDRESSES: AddressMap = {
|
||||
]),
|
||||
[SupportedChainId.CELO]: CELO_V3_MIGRATOR_ADDRESSES,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_V3_MIGRATOR_ADDRESSES,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: OPTIMISM_GOERLI_V3_MIGRATOR_ADDRESSES,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_V3_MIGRATOR_ADDRESSES,
|
||||
}
|
||||
|
||||
export const MULTICALL_ADDRESS: AddressMap = {
|
||||
...constructSameAddressMap('0x1F98415757620B543A52E61c46B32eB19261F984', [
|
||||
SupportedChainId.OPTIMISM_GOERLI,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.POLYGON_MUMBAI,
|
||||
SupportedChainId.POLYGON,
|
||||
@@ -66,22 +72,10 @@ export const MULTICALL_ADDRESS: AddressMap = {
|
||||
[SupportedChainId.ARBITRUM_ONE]: '0xadF885960B47eA2CD9B55E6DAc6B42b7Cb2806dB',
|
||||
[SupportedChainId.CELO]: CELO_MULTICALL_ADDRESS,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_MULTICALL_ADDRESS,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: OPTIMISM_GOERLI_MULTICALL_ADDRESS,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_MULTICALL_ADDRESS,
|
||||
}
|
||||
|
||||
export const SWAP_ROUTER_ADDRESSES: AddressMap = {
|
||||
...constructSameAddressMap('0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', [
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISM_GOERLI,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.POLYGON_MUMBAI,
|
||||
]),
|
||||
[SupportedChainId.CELO]: CELO_ROUTER_ADDRESS,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_ROUTER_ADDRESS,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_ROUTER_ADDRESS,
|
||||
}
|
||||
|
||||
/**
|
||||
* The oldest V0 governance address
|
||||
*/
|
||||
@@ -114,26 +108,26 @@ export const ARGENT_WALLET_DETECTOR_ADDRESS: AddressMap = {
|
||||
export const QUOTER_ADDRESSES: AddressMap = {
|
||||
...constructSameAddressMap('0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6', [
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISM_GOERLI,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.POLYGON_MUMBAI,
|
||||
SupportedChainId.POLYGON,
|
||||
]),
|
||||
[SupportedChainId.CELO]: CELO_QUOTER_ADDRESSES,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_QUOTER_ADDRESSES,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: OPTIMISM_GOERLI_QUOTER_ADDRESSES,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_QUOTER_ADDRESSES,
|
||||
}
|
||||
|
||||
export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: AddressMap = {
|
||||
...constructSameAddressMap('0xC36442b4a4522E871399CD717aBDD847Ab11FE88', [
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.OPTIMISM_GOERLI,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.POLYGON_MUMBAI,
|
||||
SupportedChainId.POLYGON,
|
||||
]),
|
||||
[SupportedChainId.CELO]: CELO_NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: OPTIMISM_GOERLI_NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
|
||||
}
|
||||
|
||||
@@ -151,4 +145,5 @@ export const TICK_LENS_ADDRESSES: AddressMap = {
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_TICK_LENS_ADDRESSES,
|
||||
[SupportedChainId.CELO]: CELO_TICK_LENS_ADDRESSES,
|
||||
[SupportedChainId.CELO_ALFAJORES]: CELO_TICK_LENS_ADDRESSES,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: OPTIMISM_GOERLI_TICK_LENS_ADDRESSES,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { SupportedChainId as SdkSupportedChainId } from '@uniswap/sdk-core'
|
||||
|
||||
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from './chains'
|
||||
|
||||
describe('chains', () => {
|
||||
describe('ALL_SUPPORTED_CHAIN_IDS', () => {
|
||||
it('derives from sdk-core', () => {
|
||||
ALL_SUPPORTED_CHAIN_IDS.forEach((chainId) => {
|
||||
const chainName = SupportedChainId[chainId]
|
||||
expect(SdkSupportedChainId[chainId]).toBe(chainName)
|
||||
})
|
||||
})
|
||||
|
||||
it('contains all the values in the SupportedChainId enum', () => {
|
||||
Object.values(SupportedChainId)
|
||||
.filter((chainId) => typeof chainId === 'number')
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/**
|
||||
* List of all the networks supported by the Uniswap Interface
|
||||
* TODO(INFRA-90): Eventually this may be derived from sdk-core.
|
||||
/*
|
||||
* SupportedChainId must be defined inline, without using @uniswap/sdk-core, so that its members are their own types
|
||||
* {@see https://www.typescriptlang.org/docs/handbook/enums.html#union-enums-and-enum-member-types}. This allows the
|
||||
* derived const arrays and their types (eg {@link L1_CHAIN_IDS}, {@link SupportedL1ChainId}) to be narrowed and used
|
||||
* to enforce chain typing.
|
||||
*
|
||||
* Because this is not explicitly derived from @uniswap/sdk-core, there is a unit test to enforce conformance.
|
||||
*/
|
||||
export enum SupportedChainId {
|
||||
MAINNET = 1,
|
||||
@@ -49,7 +53,7 @@ export const SUPPORTED_GAS_ESTIMATE_CHAIN_IDS = [
|
||||
SupportedChainId.CELO,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
]
|
||||
] as const
|
||||
|
||||
/**
|
||||
* Unsupported networks for V2 pool behavior.
|
||||
@@ -59,7 +63,7 @@ export const UNSUPPORTED_V2POOL_CHAIN_IDS = [
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.ARBITRUM_GOERLI,
|
||||
]
|
||||
] as const
|
||||
|
||||
export const TESTNET_CHAIN_IDS = [
|
||||
SupportedChainId.GOERLI,
|
||||
|
||||
@@ -9,4 +9,5 @@ export enum FeatureFlag {
|
||||
gqlRouting = 'gqlRouting',
|
||||
statsigDummy = 'web_dummy_gate_amplitude_id',
|
||||
nftGraphql = 'nft_graphql_migration',
|
||||
taxService = 'tax_service_banner',
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function usePayWithAnyTokenFlag(): BaseVariant {
|
||||
@@ -8,17 +5,7 @@ export function usePayWithAnyTokenFlag(): BaseVariant {
|
||||
}
|
||||
|
||||
export function usePayWithAnyTokenEnabled(): boolean {
|
||||
const flagEnabled = usePayWithAnyTokenFlag() === BaseVariant.Enabled
|
||||
const { chainId } = useWeb3React()
|
||||
try {
|
||||
// Detect if the Universal Router is not yet deployed to chainId.
|
||||
// This is necessary so that we can fallback correctly on chains without a Universal Router deployment.
|
||||
// It will be removed once Universal Router is deployed on all supported chains.
|
||||
chainId && UNIVERSAL_ROUTER_ADDRESS(chainId)
|
||||
return flagEnabled
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
return usePayWithAnyTokenFlag() === BaseVariant.Enabled
|
||||
}
|
||||
|
||||
export { BaseVariant as PayWithAnyTokenVariant }
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function usePermit2Flag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.permit2, BaseVariant.Enabled)
|
||||
}
|
||||
|
||||
export function usePermit2Enabled(): boolean {
|
||||
const flagEnabled = usePermit2Flag() === BaseVariant.Enabled
|
||||
const { chainId } = useWeb3React()
|
||||
try {
|
||||
// Detect if the Universal Router is not yet deployed to chainId.
|
||||
// This is necessary so that we can fallback correctly on chains without a Universal Router deployment.
|
||||
// It will be removed once Universal Router is deployed on all supported chains.
|
||||
chainId && UNIVERSAL_ROUTER_ADDRESS(chainId)
|
||||
return flagEnabled
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export { BaseVariant as Permit2Variant }
|
||||
11
src/featureFlags/flags/taxServiceBanner.ts
Normal file
11
src/featureFlags/flags/taxServiceBanner.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useTaxServiceBannerFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.taxService, BaseVariant.Control)
|
||||
}
|
||||
|
||||
export function useTaxServiceBannerEnabled(): boolean {
|
||||
return useTaxServiceBannerFlag() === BaseVariant.Enabled
|
||||
}
|
||||
|
||||
export { BaseVariant as TaxServiceVariant }
|
||||
@@ -34,15 +34,6 @@ gql`
|
||||
symbol
|
||||
market(currency: USD) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: DAY) {
|
||||
id
|
||||
value
|
||||
}
|
||||
volume24H: volume(duration: DAY) {
|
||||
id
|
||||
value
|
||||
@@ -53,6 +44,17 @@ gql`
|
||||
id
|
||||
logoUrl
|
||||
safetyLevel
|
||||
markets(currencies: [USD]) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
}
|
||||
pricePercentChange(duration: DAY) {
|
||||
id
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,6 @@ gql`
|
||||
symbol
|
||||
market(currency: USD) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: DAY) {
|
||||
id
|
||||
value
|
||||
}
|
||||
volume24H: volume(duration: DAY) {
|
||||
id
|
||||
value
|
||||
@@ -36,6 +27,17 @@ gql`
|
||||
id
|
||||
logoUrl
|
||||
safetyLevel
|
||||
markets(currencies: [USD]) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
}
|
||||
pricePercentChange(duration: DAY) {
|
||||
id
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,24 +30,11 @@ gql`
|
||||
value
|
||||
currency
|
||||
}
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
volume24H: volume(duration: DAY) {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
priceHigh52W: priceHighLow(duration: YEAR, highLow: HIGH) {
|
||||
id
|
||||
value
|
||||
}
|
||||
priceLow52W: priceHighLow(duration: YEAR, highLow: LOW) {
|
||||
id
|
||||
value
|
||||
}
|
||||
}
|
||||
project {
|
||||
id
|
||||
@@ -60,6 +47,22 @@ gql`
|
||||
chain
|
||||
address
|
||||
}
|
||||
markets(currencies: [USD]) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
priceHigh52W: priceHighLow(duration: YEAR, highLow: HIGH) {
|
||||
id
|
||||
value
|
||||
}
|
||||
priceLow52W: priceHighLow(duration: YEAR, highLow: LOW) {
|
||||
id
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,19 @@ gql`
|
||||
id
|
||||
address
|
||||
chain
|
||||
market(currency: USD) {
|
||||
project {
|
||||
id
|
||||
price {
|
||||
markets(currencies: [USD]) {
|
||||
id
|
||||
value
|
||||
}
|
||||
priceHistory(duration: $duration) {
|
||||
id
|
||||
timestamp
|
||||
value
|
||||
price {
|
||||
id
|
||||
value
|
||||
}
|
||||
priceHistory(duration: $duration) {
|
||||
id
|
||||
timestamp
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
src/graphql/data/TokenSpotPrice.ts
Normal file
23
src/graphql/data/TokenSpotPrice.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
gql`
|
||||
query TokenSpotPrice($chain: Chain!, $address: String) {
|
||||
token(chain: $chain, address: $address) {
|
||||
id
|
||||
address
|
||||
chain
|
||||
name
|
||||
symbol
|
||||
project {
|
||||
id
|
||||
markets(currencies: [USD]) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -36,30 +36,32 @@ gql`
|
||||
standard
|
||||
market(currency: USD) {
|
||||
id
|
||||
totalValueLocked {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: $duration) {
|
||||
id
|
||||
currency
|
||||
value
|
||||
}
|
||||
volume(duration: $duration) {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
totalValueLocked {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
}
|
||||
project {
|
||||
id
|
||||
logoUrl
|
||||
markets(currencies: [USD]) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
}
|
||||
pricePercentChange(duration: $duration) {
|
||||
id
|
||||
currency
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,12 +73,14 @@ gql`
|
||||
id
|
||||
address
|
||||
chain
|
||||
market(currency: USD) {
|
||||
id
|
||||
priceHistory(duration: $duration) {
|
||||
project {
|
||||
markets(currencies: [USD]) {
|
||||
id
|
||||
timestamp
|
||||
value
|
||||
priceHistory(duration: $duration) {
|
||||
id
|
||||
timestamp
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,11 +96,15 @@ function useSortedTokens(tokens: TopTokens100Query['topTokens']) {
|
||||
let tokenArray = Array.from(tokens)
|
||||
switch (sortMethod) {
|
||||
case TokenSortMethod.PRICE:
|
||||
tokenArray = tokenArray.sort((a, b) => (b?.market?.price?.value ?? 0) - (a?.market?.price?.value ?? 0))
|
||||
tokenArray = tokenArray.sort(
|
||||
(a, b) => (b?.project?.markets?.[0]?.price?.value ?? 0) - (a?.project?.markets?.[0]?.price?.value ?? 0)
|
||||
)
|
||||
break
|
||||
case TokenSortMethod.PERCENT_CHANGE:
|
||||
tokenArray = tokenArray.sort(
|
||||
(a, b) => (b?.market?.pricePercentChange?.value ?? 0) - (a?.market?.pricePercentChange?.value ?? 0)
|
||||
(a, b) =>
|
||||
(b?.project?.markets?.[0]?.pricePercentChange?.value ?? 0) -
|
||||
(a?.project?.markets?.[0]?.pricePercentChange?.value ?? 0)
|
||||
)
|
||||
break
|
||||
case TokenSortMethod.TOTAL_VALUE_LOCKED:
|
||||
@@ -160,7 +168,8 @@ export function useTopTokens(chain: Chain): UseTopTokensReturnValue {
|
||||
const unwrappedTokens = sparklineQuery?.topTokens?.map((topToken) => unwrapToken(chainId, topToken))
|
||||
const map: SparklineMap = {}
|
||||
unwrappedTokens?.forEach(
|
||||
(current) => current?.address && (map[current.address] = current?.market?.priceHistory?.filter(isPricePoint))
|
||||
(current) =>
|
||||
current?.address && (map[current.address] = current?.project?.markets?.[0]?.priceHistory?.filter(isPricePoint))
|
||||
)
|
||||
return map
|
||||
}, [chainId, sparklineQuery?.topTokens])
|
||||
|
||||
@@ -16,15 +16,6 @@ gql`
|
||||
symbol
|
||||
market(currency: USD) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: DAY) {
|
||||
id
|
||||
value
|
||||
}
|
||||
volume24H: volume(duration: DAY) {
|
||||
id
|
||||
value
|
||||
@@ -35,6 +26,18 @@ gql`
|
||||
id
|
||||
logoUrl
|
||||
safetyLevel
|
||||
markets(currencies: [USD]) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
}
|
||||
pricePercentChange(duration: DAY) {
|
||||
id
|
||||
currency
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2205
src/graphql/data/__generated__/types-and-hooks.ts
generated
2205
src/graphql/data/__generated__/types-and-hooks.ts
generated
File diff suppressed because it is too large
Load Diff
@@ -86,23 +86,13 @@ gql`
|
||||
}
|
||||
`
|
||||
|
||||
interface useCollectionReturnProps {
|
||||
data: GenieCollection
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
export function useCollection(address: string): useCollectionReturnProps {
|
||||
const { data: queryData, loading } = useCollectionQuery({
|
||||
variables: {
|
||||
addresses: address,
|
||||
},
|
||||
})
|
||||
|
||||
const queryCollection = queryData?.nftCollections?.edges?.[0]?.node as NonNullable<NftCollection>
|
||||
export function formatCollectionQueryData(
|
||||
queryCollection: NonNullable<NftCollection>,
|
||||
address?: string
|
||||
): GenieCollection {
|
||||
const market = queryCollection?.markets?.[0]
|
||||
const traits = useMemo(() => {
|
||||
return {} as Record<string, Trait[]>
|
||||
}, [])
|
||||
if (!address && !queryCollection?.nftContracts?.[0]?.address) return {} as GenieCollection
|
||||
const traits = {} as Record<string, Trait[]>
|
||||
if (queryCollection?.traits) {
|
||||
queryCollection?.traits.forEach((trait) => {
|
||||
if (trait.name && trait.stats) {
|
||||
@@ -116,43 +106,60 @@ export function useCollection(address: string): useCollectionReturnProps {
|
||||
}
|
||||
})
|
||||
}
|
||||
return {
|
||||
address: address ?? queryCollection?.nftContracts?.[0]?.address ?? '',
|
||||
isVerified: queryCollection?.isVerified,
|
||||
name: queryCollection?.name,
|
||||
description: queryCollection?.description,
|
||||
standard: queryCollection?.nftContracts?.[0]?.standard,
|
||||
bannerImageUrl: queryCollection?.bannerImage?.url,
|
||||
stats: {
|
||||
num_owners: market?.owners,
|
||||
floor_price: market?.floorPrice?.value,
|
||||
one_day_volume: market?.volume?.value,
|
||||
one_day_change: market?.volumePercentChange?.value,
|
||||
one_day_floor_change: market?.floorPricePercentChange?.value,
|
||||
banner_image_url: queryCollection?.bannerImage?.url,
|
||||
total_supply: queryCollection?.numAssets,
|
||||
total_listings: market?.listings?.value,
|
||||
total_volume: market?.totalVolume?.value,
|
||||
},
|
||||
traits,
|
||||
marketplaceCount: market?.marketplaces?.map((market) => {
|
||||
return {
|
||||
marketplace: market.marketplace?.toLowerCase() ?? '',
|
||||
count: market.listings ?? 0,
|
||||
floorPrice: market.floorPrice ?? 0,
|
||||
}
|
||||
}),
|
||||
imageUrl: queryCollection?.image?.url ?? '',
|
||||
twitterUrl: queryCollection?.twitterName,
|
||||
instagram: queryCollection?.instagramName,
|
||||
discordUrl: queryCollection?.discordUrl,
|
||||
externalUrl: queryCollection?.homepageUrl,
|
||||
rarityVerified: false, // TODO update when backend supports
|
||||
// isFoundation: boolean, // TODO ask backend to add
|
||||
}
|
||||
}
|
||||
|
||||
interface useCollectionReturnProps {
|
||||
data: GenieCollection
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
export function useCollection(address: string, skip?: boolean): useCollectionReturnProps {
|
||||
const { data: queryData, loading } = useCollectionQuery({
|
||||
variables: {
|
||||
addresses: address,
|
||||
},
|
||||
skip,
|
||||
})
|
||||
|
||||
const queryCollection = queryData?.nftCollections?.edges?.[0]?.node as NonNullable<NftCollection>
|
||||
return useMemo(() => {
|
||||
return {
|
||||
data: {
|
||||
address,
|
||||
isVerified: queryCollection?.isVerified,
|
||||
name: queryCollection?.name,
|
||||
description: queryCollection?.description,
|
||||
standard: queryCollection?.nftContracts?.[0]?.standard,
|
||||
bannerImageUrl: queryCollection?.bannerImage?.url,
|
||||
stats: {
|
||||
num_owners: market?.owners,
|
||||
floor_price: market?.floorPrice?.value,
|
||||
one_day_volume: market?.volume?.value,
|
||||
one_day_change: market?.volumePercentChange?.value,
|
||||
one_day_floor_change: market?.floorPricePercentChange?.value,
|
||||
banner_image_url: queryCollection?.bannerImage?.url,
|
||||
total_supply: queryCollection?.numAssets,
|
||||
total_listings: market?.listings?.value,
|
||||
total_volume: market?.totalVolume?.value,
|
||||
},
|
||||
traits,
|
||||
marketplaceCount: market?.marketplaces?.map((market) => {
|
||||
return {
|
||||
marketplace: market.marketplace?.toLowerCase() ?? '',
|
||||
count: market.listings ?? 0,
|
||||
floorPrice: market.floorPrice ?? 0,
|
||||
}
|
||||
}),
|
||||
imageUrl: queryCollection?.image?.url ?? '',
|
||||
twitterUrl: queryCollection?.twitterName,
|
||||
instagram: queryCollection?.instagramName,
|
||||
discordUrl: queryCollection?.discordUrl,
|
||||
externalUrl: queryCollection?.homepageUrl,
|
||||
rarityVerified: false, // TODO update when backend supports
|
||||
// isFoundation: boolean, // TODO ask backend to add
|
||||
},
|
||||
data: formatCollectionQueryData(queryCollection, address),
|
||||
loading,
|
||||
}
|
||||
}, [address, loading, market, queryCollection, traits])
|
||||
}, [address, loading, queryCollection])
|
||||
}
|
||||
|
||||
88
src/graphql/data/nft/CollectionSearch.ts
Normal file
88
src/graphql/data/nft/CollectionSearch.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { isAddress } from '@ethersproject/address'
|
||||
import { useNftGraphqlEnabled } from 'featureFlags/flags/nftlGraphql'
|
||||
import gql from 'graphql-tag'
|
||||
import { GenieCollection } from 'nft/types'
|
||||
import { blocklistedCollections } from 'nft/utils'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { NftCollection, useCollectionSearchQuery } from '../__generated__/types-and-hooks'
|
||||
import { formatCollectionQueryData, useCollection } from './Collection'
|
||||
|
||||
const MAX_SEARCH_RESULTS = 6
|
||||
|
||||
gql`
|
||||
query CollectionSearch($query: String!) {
|
||||
nftCollections(filter: { nameQuery: $query }) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
image {
|
||||
url
|
||||
}
|
||||
isVerified
|
||||
name
|
||||
numAssets
|
||||
nftContracts {
|
||||
address
|
||||
chain
|
||||
name
|
||||
symbol
|
||||
totalSupply
|
||||
}
|
||||
markets(currencies: ETH) {
|
||||
floorPrice {
|
||||
currency
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
endCursor
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
interface useCollectionSearchReturnProps {
|
||||
data: GenieCollection[]
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
function useCollectionQuerySearch(query: string, skip?: boolean): useCollectionSearchReturnProps {
|
||||
const { data: queryData, loading } = useCollectionSearchQuery({
|
||||
variables: {
|
||||
query,
|
||||
},
|
||||
skip,
|
||||
})
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
data:
|
||||
queryData?.nftCollections?.edges
|
||||
?.filter(
|
||||
(collectionEdge) =>
|
||||
collectionEdge.node.nftContracts?.[0]?.address &&
|
||||
!blocklistedCollections.includes(collectionEdge.node.nftContracts?.[0]?.address)
|
||||
)
|
||||
.slice(0, MAX_SEARCH_RESULTS)
|
||||
.map((collectionEdge) => {
|
||||
const queryCollection = collectionEdge.node as NonNullable<NftCollection>
|
||||
return formatCollectionQueryData(queryCollection)
|
||||
}) ?? [],
|
||||
loading,
|
||||
}
|
||||
}, [loading, queryData])
|
||||
}
|
||||
|
||||
export function useCollectionSearch(queryOrAddress: string): useCollectionSearchReturnProps {
|
||||
const isNftGraphqlEnabled = useNftGraphqlEnabled()
|
||||
const isName = !isAddress(queryOrAddress.toLowerCase())
|
||||
const queryResult = useCollectionQuerySearch(queryOrAddress, isNftGraphqlEnabled ? !isName : true)
|
||||
const addressResult = useCollection(queryOrAddress, isNftGraphqlEnabled ? isName : true)
|
||||
return isName ? queryResult : { data: [addressResult.data], loading: addressResult.loading }
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { HistoryDuration, useTrendingCollectionsQuery } from '../__generated__/t
|
||||
|
||||
gql`
|
||||
query TrendingCollections($size: Int, $timePeriod: HistoryDuration) {
|
||||
topCollections(limit: $size, duration: $timePeriod) {
|
||||
topCollections(first: $size, duration: $timePeriod) {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
|
||||
@@ -73,6 +73,18 @@ export function chainIdToBackendName(chainId: number | undefined) {
|
||||
: CHAIN_ID_TO_BACKEND_NAME[SupportedChainId.MAINNET]
|
||||
}
|
||||
|
||||
const GQL_CHAINS: number[] = [
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.CELO,
|
||||
]
|
||||
|
||||
export function isGqlSupportedChain(chainId: number | undefined): chainId is SupportedChainId {
|
||||
return !!chainId && GQL_CHAINS.includes(chainId)
|
||||
}
|
||||
|
||||
const URL_CHAIN_PARAM_TO_BACKEND: { [key: string]: Chain } = {
|
||||
ethereum: Chain.Ethereum,
|
||||
polygon: Chain.Polygon,
|
||||
|
||||
5472
src/graphql/thegraph/__generated__/types-and-hooks.ts
generated
5472
src/graphql/thegraph/__generated__/types-and-hooks.ts
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
import { ApolloClient, ApolloLink, concat, HttpLink, InMemoryCache } from '@apollo/client'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
|
||||
import store, { AppState } from '../../state/index'
|
||||
import store from '../../state/index'
|
||||
|
||||
const CHAIN_SUBGRAPH_URL: Record<number, string> = {
|
||||
[SupportedChainId.MAINNET]: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3',
|
||||
@@ -21,7 +21,7 @@ const httpLink = new HttpLink({ uri: CHAIN_SUBGRAPH_URL[SupportedChainId.MAINNET
|
||||
// For more information: https://www.apollographql.com/docs/react/networking/advanced-http-networking/
|
||||
const authMiddleware = new ApolloLink((operation, forward) => {
|
||||
// add the authorization to the headers
|
||||
const chainId = (store.getState() as AppState).application.chainId
|
||||
const chainId = store.getState().application.chainId
|
||||
|
||||
operation.setContext(() => ({
|
||||
uri:
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import useSwapApproval from 'lib/hooks/swap/useSwapApproval'
|
||||
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
|
||||
import { ApprovalState, useApproval } from 'lib/hooks/useApproval'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
@@ -28,11 +26,3 @@ export function useApproveCallback(
|
||||
const [approval, getApproval] = useApproval(amountToApprove, spender, useHasPendingApproval)
|
||||
return [approval, useGetAndTrackApproval(getApproval)]
|
||||
}
|
||||
|
||||
export function useApproveCallbackFromTrade(
|
||||
trade: Trade<Currency, Currency, TradeType> | undefined,
|
||||
allowedSlippage: Percent
|
||||
): [ApprovalState, () => Promise<void>] {
|
||||
const [approval, getApproval] = useSwapApproval(trade, allowedSlippage, useHasPendingApproval)
|
||||
return [approval, useGetAndTrackApproval(getApproval)]
|
||||
}
|
||||
|
||||
@@ -15,6 +15,13 @@ import useIsWindowVisible from './useIsWindowVisible'
|
||||
const USDCAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '10000')
|
||||
const DAIAmount = CurrencyAmount.fromRawAmount(DAI, '10000')
|
||||
|
||||
jest.mock('@web3-react/core', () => {
|
||||
return {
|
||||
useWeb3React: () => ({
|
||||
chainId: 1,
|
||||
}),
|
||||
}
|
||||
})
|
||||
jest.mock('./useAutoRouterSupported')
|
||||
jest.mock('./useClientSideV3Trade')
|
||||
jest.mock('./useDebounce')
|
||||
@@ -69,8 +76,7 @@ describe('#useBestV3Trade ExactIn', () => {
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
|
||||
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, DAI, RouterPreference.CLIENT)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, USDCAmount, DAI)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
expect(result.current).toEqual({ state: TradeState.NO_ROUTE_FOUND, trade: undefined })
|
||||
})
|
||||
|
||||
describe('when routing api is in non-error state', () => {
|
||||
@@ -155,8 +161,7 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
USDC_MAINNET,
|
||||
RouterPreference.CLIENT
|
||||
)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
expect(result.current).toEqual({ state: TradeState.NO_ROUTE_FOUND, trade: undefined })
|
||||
})
|
||||
describe('when routing api is in non-error state', () => {
|
||||
it('does not compute client side v3 trade if routing api is LOADING', () => {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
|
||||
import { useMemo } from 'react'
|
||||
import { RouterPreference } from 'state/routing/slice'
|
||||
import { InterfaceTrade, TradeState } from 'state/routing/types'
|
||||
@@ -24,6 +26,7 @@ export function useBestTrade(
|
||||
state: TradeState
|
||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
||||
} {
|
||||
const { chainId } = useWeb3React()
|
||||
const autoRouterSupported = useAutoRouterSupported()
|
||||
const isWindowVisible = useIsWindowVisible()
|
||||
|
||||
@@ -32,16 +35,27 @@ export function useBestTrade(
|
||||
200
|
||||
)
|
||||
|
||||
const isAWrapTransaction = useMemo(() => {
|
||||
if (!chainId || !amountSpecified || !debouncedOtherCurrency) return false
|
||||
const weth = WRAPPED_NATIVE_CURRENCY[chainId]
|
||||
return (
|
||||
(amountSpecified.currency.isNative && weth?.equals(debouncedOtherCurrency)) ||
|
||||
(debouncedOtherCurrency.isNative && weth?.equals(amountSpecified.currency))
|
||||
)
|
||||
}, [amountSpecified, chainId, debouncedOtherCurrency])
|
||||
|
||||
const shouldGetTrade = !isAWrapTransaction && isWindowVisible
|
||||
|
||||
const [clientSideRouter] = useClientSideRouter()
|
||||
const routingAPITrade = useRoutingAPITrade(
|
||||
tradeType,
|
||||
autoRouterSupported && isWindowVisible ? debouncedAmount : undefined,
|
||||
autoRouterSupported && shouldGetTrade ? debouncedAmount : undefined,
|
||||
debouncedOtherCurrency,
|
||||
clientSideRouter ? RouterPreference.CLIENT : RouterPreference.API
|
||||
)
|
||||
|
||||
const isLoading = routingAPITrade.state === TradeState.LOADING
|
||||
const useFallback = !autoRouterSupported || routingAPITrade.state === TradeState.NO_ROUTE_FOUND
|
||||
const useFallback = (!autoRouterSupported || routingAPITrade.state === TradeState.NO_ROUTE_FOUND) && shouldGetTrade
|
||||
|
||||
// only use client side router if routing api trade failed or is not supported
|
||||
const bestV3Trade = useClientSideV3Trade(
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { splitSignature } from '@ethersproject/bytes'
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import JSBI from 'jsbi'
|
||||
import { useSingleCallResult } from 'lib/hooks/multicall'
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
import { SWAP_ROUTER_ADDRESSES } from '../constants/addresses'
|
||||
import { DAI, UNI, USDC_MAINNET } from '../constants/tokens'
|
||||
import { useEIP2612Contract } from './useContract'
|
||||
import useIsArgentWallet from './useIsArgentWallet'
|
||||
@@ -44,7 +42,7 @@ const PERMITTABLE_TOKENS: {
|
||||
},
|
||||
}
|
||||
|
||||
export enum UseERC20PermitState {
|
||||
enum UseERC20PermitState {
|
||||
// returned for any reason, e.g. it is an argent wallet, or the currency does not support it
|
||||
NOT_APPLICABLE,
|
||||
LOADING,
|
||||
@@ -73,7 +71,7 @@ interface AllowedSignatureData extends BaseSignatureData {
|
||||
allowed: true
|
||||
}
|
||||
|
||||
export type SignatureData = StandardSignatureData | AllowedSignatureData
|
||||
type SignatureData = StandardSignatureData | AllowedSignatureData
|
||||
|
||||
const EIP712_DOMAIN_TYPE = [
|
||||
{ name: 'name', type: 'string' },
|
||||
@@ -247,18 +245,3 @@ export function useERC20Permit(
|
||||
signatureData,
|
||||
])
|
||||
}
|
||||
|
||||
export function useERC20PermitFromTrade(
|
||||
trade: Trade<Currency, Currency, TradeType> | undefined,
|
||||
allowedSlippage: Percent,
|
||||
transactionDeadline: BigNumber | undefined
|
||||
) {
|
||||
const { chainId } = useWeb3React()
|
||||
const swapRouterAddress = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined
|
||||
const amountToApprove = useMemo(
|
||||
() => (trade ? trade.maximumAmountIn(allowedSlippage) : undefined),
|
||||
[trade, allowedSlippage]
|
||||
)
|
||||
|
||||
return useERC20Permit(amountToApprove, swapRouterAddress, transactionDeadline, null)
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { SwapRouter, Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { FeeOptions } from '@uniswap/v3-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { SWAP_ROUTER_ADDRESSES } from 'constants/addresses'
|
||||
import { useMemo } from 'react'
|
||||
import approveAmountCalldata from 'utils/approveAmountCalldata'
|
||||
|
||||
import { useArgentWalletContract } from './useArgentWalletContract'
|
||||
import useENS from './useENS'
|
||||
import { SignatureData } from './useERC20Permit'
|
||||
|
||||
interface SwapCall {
|
||||
address: string
|
||||
calldata: string
|
||||
value: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the swap calls that can be used to make the trade
|
||||
* @param trade trade to execute
|
||||
* @param allowedSlippage user allowed slippage
|
||||
* @param recipientAddressOrName the ENS name or address of the recipient of the swap output
|
||||
* @param signatureData the signature data of the permit of the input token amount, if available
|
||||
*/
|
||||
export function useSwapCallArguments(
|
||||
trade: Trade<Currency, Currency, TradeType> | undefined,
|
||||
allowedSlippage: Percent,
|
||||
recipientAddressOrName: string | null | undefined,
|
||||
signatureData: SignatureData | null | undefined,
|
||||
deadline: BigNumber | undefined,
|
||||
feeOptions: FeeOptions | undefined
|
||||
): SwapCall[] {
|
||||
const { account, chainId, provider } = useWeb3React()
|
||||
|
||||
const { address: recipientAddress } = useENS(recipientAddressOrName)
|
||||
const recipient = recipientAddressOrName === null ? account : recipientAddress
|
||||
const argentWalletContract = useArgentWalletContract()
|
||||
|
||||
return useMemo(() => {
|
||||
if (!trade || !recipient || !provider || !account || !chainId || !deadline) return []
|
||||
|
||||
const swapRouterAddress = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined
|
||||
if (!swapRouterAddress) return []
|
||||
|
||||
const { value, calldata } = SwapRouter.swapCallParameters(trade, {
|
||||
fee: feeOptions,
|
||||
recipient,
|
||||
slippageTolerance: allowedSlippage,
|
||||
...(signatureData
|
||||
? {
|
||||
inputTokenPermit:
|
||||
'allowed' in signatureData
|
||||
? {
|
||||
expiry: signatureData.deadline,
|
||||
nonce: signatureData.nonce,
|
||||
s: signatureData.s,
|
||||
r: signatureData.r,
|
||||
v: signatureData.v as any,
|
||||
}
|
||||
: {
|
||||
deadline: signatureData.deadline,
|
||||
amount: signatureData.amount,
|
||||
s: signatureData.s,
|
||||
r: signatureData.r,
|
||||
v: signatureData.v as any,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
|
||||
deadlineOrPreviousBlockhash: deadline.toString(),
|
||||
})
|
||||
|
||||
if (argentWalletContract && trade.inputAmount.currency.isToken) {
|
||||
return [
|
||||
{
|
||||
address: argentWalletContract.address,
|
||||
calldata: argentWalletContract.interface.encodeFunctionData('wc_multiCall', [
|
||||
[
|
||||
approveAmountCalldata(trade.maximumAmountIn(allowedSlippage), swapRouterAddress),
|
||||
{
|
||||
to: swapRouterAddress,
|
||||
value,
|
||||
data: calldata,
|
||||
},
|
||||
],
|
||||
]),
|
||||
value: '0x0',
|
||||
},
|
||||
]
|
||||
}
|
||||
return [
|
||||
{
|
||||
address: swapRouterAddress,
|
||||
calldata,
|
||||
value,
|
||||
},
|
||||
]
|
||||
}, [
|
||||
account,
|
||||
allowedSlippage,
|
||||
argentWalletContract,
|
||||
chainId,
|
||||
deadline,
|
||||
feeOptions,
|
||||
provider,
|
||||
recipient,
|
||||
signatureData,
|
||||
trade,
|
||||
])
|
||||
}
|
||||
@@ -1,16 +1,11 @@
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { usePermit2Enabled } from 'featureFlags/flags/permit2'
|
||||
import { PermitSignature } from 'hooks/usePermitAllowance'
|
||||
import { SwapCallbackState, useSwapCallback as useLibSwapCallBack } from 'lib/hooks/swap/useSwapCallback'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { useTransactionAdder } from '../state/transactions/hooks'
|
||||
import { TransactionType } from '../state/transactions/types'
|
||||
import { currencyId } from '../utils/currencyId'
|
||||
import useENS from './useENS'
|
||||
import { SignatureData } from './useERC20Permit'
|
||||
import useTransactionDeadline from './useTransactionDeadline'
|
||||
import { useUniversalRouterSwapCallback } from './useUniversalRouter'
|
||||
|
||||
@@ -18,38 +13,20 @@ import { useUniversalRouterSwapCallback } from './useUniversalRouter'
|
||||
// and the user has approved the slippage adjusted input amount for the trade
|
||||
export function useSwapCallback(
|
||||
trade: Trade<Currency, Currency, TradeType> | undefined, // trade to execute, required
|
||||
fiatValues: { amountIn: number | undefined; amountOut: number | undefined }, // usd values for amount in and out, logged for analytics
|
||||
allowedSlippage: Percent, // in bips
|
||||
recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
|
||||
signatureData: SignatureData | undefined | null,
|
||||
permitSignature: PermitSignature | undefined
|
||||
): { state: SwapCallbackState; callback: null | (() => Promise<string>); error: ReactNode | null } {
|
||||
const { account } = useWeb3React()
|
||||
|
||||
): { callback: null | (() => Promise<string>) } {
|
||||
const deadline = useTransactionDeadline()
|
||||
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
const { address: recipientAddress } = useENS(recipientAddressOrName)
|
||||
const recipient = recipientAddressOrName === null ? account : recipientAddress
|
||||
|
||||
const permit2Enabled = usePermit2Enabled()
|
||||
const {
|
||||
state,
|
||||
callback: libCallback,
|
||||
error,
|
||||
} = useLibSwapCallBack({
|
||||
trade: permit2Enabled ? undefined : trade,
|
||||
allowedSlippage,
|
||||
recipientAddressOrName: recipient,
|
||||
signatureData,
|
||||
deadline,
|
||||
})
|
||||
const universalRouterSwapCallback = useUniversalRouterSwapCallback(permit2Enabled ? trade : undefined, {
|
||||
const universalRouterSwapCallback = useUniversalRouterSwapCallback(trade, fiatValues, {
|
||||
slippageTolerance: allowedSlippage,
|
||||
deadline,
|
||||
permit: permitSignature,
|
||||
})
|
||||
const swapCallback = permit2Enabled ? universalRouterSwapCallback : libCallback
|
||||
const swapCallback = universalRouterSwapCallback
|
||||
|
||||
const callback = useMemo(() => {
|
||||
if (!trade || !swapCallback) return null
|
||||
@@ -82,8 +59,6 @@ export function useSwapCallback(
|
||||
}, [addTransaction, allowedSlippage, swapCallback, trade])
|
||||
|
||||
return {
|
||||
state,
|
||||
callback,
|
||||
error,
|
||||
}
|
||||
}
|
||||
|
||||
64
src/hooks/useUSDPrice.ts
Normal file
64
src/hooks/useUSDPrice.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Currency, CurrencyAmount, Price, SupportedChainId, TradeType } from '@uniswap/sdk-core'
|
||||
import { nativeOnChain } from 'constants/tokens'
|
||||
import { Chain, useTokenSpotPriceQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { chainIdToBackendName, isGqlSupportedChain, PollingInterval } from 'graphql/data/util'
|
||||
import { RouterPreference } from 'state/routing/slice'
|
||||
import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade'
|
||||
import { getNativeTokenDBAddress } from 'utils/nativeTokens'
|
||||
|
||||
import useStablecoinPrice from './useStablecoinPrice'
|
||||
|
||||
// ETH amounts used when calculating spot price for a given currency.
|
||||
// The amount is large enough to filter low liquidity pairs.
|
||||
const ETH_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Currency> } = {
|
||||
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.MAINNET), 100),
|
||||
[SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.ARBITRUM_ONE), 100),
|
||||
[SupportedChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.OPTIMISM), 100),
|
||||
[SupportedChainId.POLYGON]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.POLYGON), 10_000e6),
|
||||
}
|
||||
|
||||
function useETHValue(currencyAmount?: CurrencyAmount<Currency>): CurrencyAmount<Currency> | undefined {
|
||||
const chainId = currencyAmount?.currency?.chainId
|
||||
const amountOut = isGqlSupportedChain(chainId) ? ETH_AMOUNT_OUT[chainId] : undefined
|
||||
const { trade } = useRoutingAPITrade(
|
||||
TradeType.EXACT_OUTPUT,
|
||||
amountOut,
|
||||
currencyAmount?.currency,
|
||||
RouterPreference.PRICE
|
||||
)
|
||||
|
||||
// Get ETH value of ETH or WETH
|
||||
if (chainId && currencyAmount && currencyAmount.currency.wrapped.equals(nativeOnChain(chainId).wrapped)) {
|
||||
return new Price(currencyAmount.currency, currencyAmount.currency, '1', '1').quote(currencyAmount)
|
||||
}
|
||||
|
||||
if (!trade || !currencyAmount?.currency || !isGqlSupportedChain(chainId)) return
|
||||
|
||||
const { numerator, denominator } = trade.routes[0].midPrice
|
||||
const price = new Price(currencyAmount?.currency, nativeOnChain(chainId), denominator, numerator)
|
||||
return price.quote(currencyAmount)
|
||||
}
|
||||
|
||||
export function useUSDPrice(currencyAmount?: CurrencyAmount<Currency>): number | undefined {
|
||||
const chain = currencyAmount?.currency.chainId ? chainIdToBackendName(currencyAmount?.currency.chainId) : undefined
|
||||
const currency = currencyAmount?.currency
|
||||
const ethValue = useETHValue(currencyAmount)
|
||||
|
||||
const { data } = useTokenSpotPriceQuery({
|
||||
variables: { chain: chain ?? Chain.Ethereum, address: getNativeTokenDBAddress(chain ?? Chain.Ethereum) },
|
||||
skip: !chain || !isGqlSupportedChain(currency?.chainId),
|
||||
pollInterval: PollingInterval.Normal,
|
||||
})
|
||||
|
||||
// Use USDC price for chains not supported by backend yet
|
||||
const stablecoinPrice = useStablecoinPrice(!isGqlSupportedChain(currency?.chainId) ? currency : undefined)
|
||||
if (!isGqlSupportedChain(currency?.chainId) && currencyAmount && stablecoinPrice) {
|
||||
return parseFloat(stablecoinPrice.quote(currencyAmount).toSignificant())
|
||||
}
|
||||
|
||||
// Otherwise, get the price of the token in ETH, and then multiple by the price of ETH
|
||||
const ethUSDPrice = data?.token?.project?.markets?.[0]?.price?.value
|
||||
if (!ethUSDPrice || !ethValue) return undefined
|
||||
|
||||
return parseFloat(ethValue.toExact()) * ethUSDPrice
|
||||
}
|
||||
@@ -27,6 +27,7 @@ interface SwapOptions {
|
||||
|
||||
export function useUniversalRouterSwapCallback(
|
||||
trade: Trade<Currency, Currency, TradeType> | undefined,
|
||||
fiatValues: { amountIn: number | undefined; amountOut: number | undefined },
|
||||
options: SwapOptions
|
||||
) {
|
||||
const { account, chainId, provider } = useWeb3React()
|
||||
@@ -66,7 +67,7 @@ export function useUniversalRouterSwapCallback(
|
||||
.then((response) => {
|
||||
sendAnalyticsEvent(
|
||||
SwapEventName.SWAP_SIGNED,
|
||||
formatSwapSignedAnalyticsEventProperties({ trade, txHash: response.hash })
|
||||
formatSwapSignedAnalyticsEventProperties({ trade, fiatValues, txHash: response.hash })
|
||||
)
|
||||
if (tx.data !== response.data) {
|
||||
sendAnalyticsEvent(SwapEventName.SWAP_MODIFIED_IN_WALLET, { txHash: response.hash })
|
||||
@@ -84,6 +85,7 @@ export function useUniversalRouterSwapCallback(
|
||||
}, [
|
||||
account,
|
||||
chainId,
|
||||
fiatValues,
|
||||
options.deadline,
|
||||
options.feeOptions,
|
||||
options.permit,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import '@reach/dialog/styles.css'
|
||||
import 'inter-ui'
|
||||
import 'polyfills'
|
||||
import 'components/analytics'
|
||||
import 'integrations'
|
||||
|
||||
import { ApolloProvider } from '@apollo/client'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import { FeatureFlagsProvider } from 'featureFlags'
|
||||
import { apolloClient } from 'graphql/data/apollo'
|
||||
import { BlockNumberProvider } from 'lib/hooks/useBlockNumber'
|
||||
@@ -14,7 +13,6 @@ import { createRoot } from 'react-dom/client'
|
||||
import { QueryClient, QueryClientProvider } from 'react-query'
|
||||
import { Provider } from 'react-redux'
|
||||
import { HashRouter } from 'react-router-dom'
|
||||
import { isSentryEnabled } from 'utils/env'
|
||||
|
||||
import Web3Provider from './components/Web3Provider'
|
||||
import { LanguageProvider } from './i18n'
|
||||
@@ -33,13 +31,6 @@ if (window.ethereum) {
|
||||
window.ethereum.autoRefreshOnNetworkChange = false
|
||||
}
|
||||
|
||||
if (isSentryEnabled()) {
|
||||
Sentry.init({
|
||||
dsn: process.env.REACT_APP_SENTRY_DSN,
|
||||
release: process.env.REACT_APP_GIT_COMMIT_HASH,
|
||||
})
|
||||
}
|
||||
|
||||
function Updaters() {
|
||||
return (
|
||||
<>
|
||||
|
||||
35
src/integrations.ts
Normal file
35
src/integrations.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'components/analytics'
|
||||
|
||||
import * as Sentry from '@sentry/react'
|
||||
import { initializeAnalytics, OriginApplication } from '@uniswap/analytics'
|
||||
import { SharedEventName } from '@uniswap/analytics-events'
|
||||
import { isSentryEnabled } from 'utils/env'
|
||||
import { getEnvName, isProductionEnv } from 'utils/env'
|
||||
|
||||
// Dump some metadata into the window to allow client verification.
|
||||
window.GIT_COMMIT_HASH = process.env.REACT_APP_GIT_COMMIT_HASH
|
||||
|
||||
// Actual KEYs are set by proxy servers.
|
||||
const AMPLITUDE_DUMMY_KEY = '00000000000000000000000000000000'
|
||||
export const STATSIG_DUMMY_KEY = 'client-0000000000000000000000000000000000000000000'
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.REACT_APP_SENTRY_DSN,
|
||||
release: process.env.REACT_APP_GIT_COMMIT_HASH,
|
||||
environment: getEnvName(),
|
||||
enabled: isSentryEnabled(),
|
||||
/**
|
||||
* TODO(INFRA-143)
|
||||
* According to Sentry, this shouldn't be necessary, as they default to `3` when not set.
|
||||
* Unfortunately, that doesn't work right now, so we workaround it by explicitly setting
|
||||
* the `normalizeDepth` to `10`. This should be removed once the issue is fixed.
|
||||
*/
|
||||
normalizeDepth: 10,
|
||||
})
|
||||
|
||||
initializeAnalytics(AMPLITUDE_DUMMY_KEY, OriginApplication.INTERFACE, {
|
||||
proxyUrl: process.env.REACT_APP_AMPLITUDE_PROXY_URL,
|
||||
defaultEventName: SharedEventName.PAGE_VIEWED,
|
||||
commitHash: process.env.REACT_APP_GIT_COMMIT_HASH,
|
||||
isProductionEnv: isProductionEnv(),
|
||||
})
|
||||
@@ -1,152 +0,0 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import type { JsonRpcProvider, TransactionResponse } from '@ethersproject/providers'
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, TradeType } from '@uniswap/sdk-core'
|
||||
import { formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics'
|
||||
import { useMemo } from 'react'
|
||||
import { calculateGasMargin } from 'utils/calculateGasMargin'
|
||||
import isZero from 'utils/isZero'
|
||||
import { swapErrorToUserReadableMessage } from 'utils/swapErrorToUserReadableMessage'
|
||||
|
||||
interface SwapCall {
|
||||
address: string
|
||||
calldata: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface SwapCallEstimate {
|
||||
call: SwapCall
|
||||
}
|
||||
|
||||
interface SuccessfulCall extends SwapCallEstimate {
|
||||
call: SwapCall
|
||||
gasEstimate: BigNumber
|
||||
}
|
||||
|
||||
interface FailedCall extends SwapCallEstimate {
|
||||
call: SwapCall
|
||||
error: Error
|
||||
}
|
||||
|
||||
class InvalidSwapError extends Error {}
|
||||
|
||||
// returns a function that will execute a swap, if the parameters are all valid
|
||||
export default function useSendSwapTransaction(
|
||||
account: string | null | undefined,
|
||||
chainId: number | undefined,
|
||||
provider: JsonRpcProvider | undefined,
|
||||
trade: Trade<Currency, Currency, TradeType> | undefined, // trade to execute, required
|
||||
swapCalls: SwapCall[]
|
||||
): { callback: null | (() => Promise<TransactionResponse>) } {
|
||||
return useMemo(() => {
|
||||
if (!trade || !provider || !account || !chainId) {
|
||||
return { callback: null }
|
||||
}
|
||||
return {
|
||||
callback: async function onSwap(): Promise<TransactionResponse> {
|
||||
const estimatedCalls: SwapCallEstimate[] = await Promise.all(
|
||||
swapCalls.map((call) => {
|
||||
const { address, calldata, value } = call
|
||||
|
||||
const tx =
|
||||
!value || isZero(value)
|
||||
? { from: account, to: address, data: calldata }
|
||||
: {
|
||||
from: account,
|
||||
to: address,
|
||||
data: calldata,
|
||||
value,
|
||||
}
|
||||
|
||||
return provider
|
||||
.estimateGas(tx)
|
||||
.then((gasEstimate) => {
|
||||
return {
|
||||
call,
|
||||
gasEstimate,
|
||||
}
|
||||
})
|
||||
.catch((gasError) => {
|
||||
console.debug('Gas estimate failed, trying eth_call to extract error', call)
|
||||
|
||||
return provider
|
||||
.call(tx)
|
||||
.then((result) => {
|
||||
console.debug('Unexpected successful call after failed estimate gas', call, gasError, result)
|
||||
return { call, error: <Trans>Unexpected issue with estimating the gas. Please try again.</Trans> }
|
||||
})
|
||||
.catch((callError) => {
|
||||
console.debug('Call threw error', call, callError)
|
||||
return { call, error: swapErrorToUserReadableMessage(callError) }
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
// a successful estimation is a bignumber gas estimate and the next call is also a bignumber gas estimate
|
||||
let bestCallOption: SuccessfulCall | SwapCallEstimate | undefined = estimatedCalls.find(
|
||||
(el, ix, list): el is SuccessfulCall =>
|
||||
'gasEstimate' in el && (ix === list.length - 1 || 'gasEstimate' in list[ix + 1])
|
||||
)
|
||||
|
||||
// check if any calls errored with a recognizable error
|
||||
if (!bestCallOption) {
|
||||
const errorCalls = estimatedCalls.filter((call): call is FailedCall => 'error' in call)
|
||||
if (errorCalls.length > 0) throw errorCalls[errorCalls.length - 1].error
|
||||
const firstNoErrorCall = estimatedCalls.find<SwapCallEstimate>(
|
||||
(call): call is SwapCallEstimate => !('error' in call)
|
||||
)
|
||||
if (!firstNoErrorCall) throw new Error(t`Unexpected error. Could not estimate gas for the swap.`)
|
||||
bestCallOption = firstNoErrorCall
|
||||
}
|
||||
|
||||
const {
|
||||
call: { address, calldata, value },
|
||||
} = bestCallOption
|
||||
|
||||
return provider
|
||||
.getSigner()
|
||||
.sendTransaction({
|
||||
from: account,
|
||||
to: address,
|
||||
data: calldata,
|
||||
// let the wallet try if we can't estimate the gas
|
||||
...('gasEstimate' in bestCallOption ? { gasLimit: calculateGasMargin(bestCallOption.gasEstimate) } : {}),
|
||||
...(value && !isZero(value) ? { value } : {}),
|
||||
})
|
||||
.then((response) => {
|
||||
sendAnalyticsEvent(
|
||||
SwapEventName.SWAP_SIGNED,
|
||||
formatSwapSignedAnalyticsEventProperties({ trade, txHash: response.hash })
|
||||
)
|
||||
if (calldata !== response.data) {
|
||||
sendAnalyticsEvent(SwapEventName.SWAP_MODIFIED_IN_WALLET, { txHash: response.hash })
|
||||
throw new InvalidSwapError(
|
||||
t`Your swap was modified through your wallet. If this was a mistake, please cancel immediately or risk losing your funds.`
|
||||
)
|
||||
}
|
||||
return response
|
||||
})
|
||||
.catch((error) => {
|
||||
// if the user rejected the tx, pass this along
|
||||
if (error?.code === 4001) {
|
||||
throw new Error(t`Transaction rejected`)
|
||||
} else {
|
||||
// otherwise, the error was unexpected and we need to convey that
|
||||
console.error(`Swap failed`, error, address, calldata, value)
|
||||
|
||||
if (error instanceof InvalidSwapError) {
|
||||
throw error
|
||||
} else {
|
||||
throw new Error(t`Swap failed: ${swapErrorToUserReadableMessage(error)}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}, [account, chainId, provider, swapCalls, trade])
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { SWAP_ROUTER_ADDRESSES } from 'constants/addresses'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { useApproval } from '../useApproval'
|
||||
|
||||
// wraps useApproveCallback in the context of a swap
|
||||
export default function useSwapApproval(
|
||||
trade: Trade<Currency, Currency, TradeType> | undefined,
|
||||
allowedSlippage: Percent,
|
||||
useIsPendingApproval: (token?: Token, spender?: string) => boolean,
|
||||
amount?: CurrencyAmount<Currency> // defaults to trade.maximumAmountIn(allowedSlippage)
|
||||
) {
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const amountToApprove = useMemo(
|
||||
() => amount || (trade && trade.inputAmount.currency.isToken ? trade.maximumAmountIn(allowedSlippage) : undefined),
|
||||
[amount, trade, allowedSlippage]
|
||||
)
|
||||
const spender = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined
|
||||
|
||||
return useApproval(amountToApprove, spender, useIsPendingApproval)
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import type { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { FeeOptions } from '@uniswap/v3-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import useENS from 'hooks/useENS'
|
||||
import { SignatureData } from 'hooks/useERC20Permit'
|
||||
import { useSwapCallArguments } from 'hooks/useSwapCallArguments'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
|
||||
import useSendSwapTransaction from './useSendSwapTransaction'
|
||||
|
||||
export enum SwapCallbackState {
|
||||
INVALID,
|
||||
LOADING,
|
||||
VALID,
|
||||
}
|
||||
|
||||
interface UseSwapCallbackReturns {
|
||||
state: SwapCallbackState
|
||||
callback?: () => Promise<TransactionResponse>
|
||||
error?: ReactNode
|
||||
}
|
||||
interface UseSwapCallbackArgs {
|
||||
trade: Trade<Currency, Currency, TradeType> | undefined // trade to execute, required
|
||||
allowedSlippage: Percent // in bips
|
||||
recipientAddressOrName: string | null | undefined // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
|
||||
signatureData: SignatureData | null | undefined
|
||||
deadline: BigNumber | undefined
|
||||
feeOptions?: FeeOptions
|
||||
}
|
||||
|
||||
// returns a function that will execute a swap, if the parameters are all valid
|
||||
// and the user has approved the slippage adjusted input amount for the trade
|
||||
export function useSwapCallback({
|
||||
trade,
|
||||
allowedSlippage,
|
||||
recipientAddressOrName,
|
||||
signatureData,
|
||||
deadline,
|
||||
feeOptions,
|
||||
}: UseSwapCallbackArgs): UseSwapCallbackReturns {
|
||||
const { account, chainId, provider } = useWeb3React()
|
||||
|
||||
const swapCalls = useSwapCallArguments(
|
||||
trade,
|
||||
allowedSlippage,
|
||||
recipientAddressOrName,
|
||||
signatureData,
|
||||
deadline,
|
||||
feeOptions
|
||||
)
|
||||
const { callback } = useSendSwapTransaction(account, chainId, provider, trade, swapCalls)
|
||||
|
||||
const { address: recipientAddress } = useENS(recipientAddressOrName)
|
||||
const recipient = recipientAddressOrName === null ? account : recipientAddress
|
||||
|
||||
return useMemo(() => {
|
||||
if (!trade || !provider || !account || !chainId || !callback) {
|
||||
return { state: SwapCallbackState.INVALID, error: <Trans>Missing dependencies</Trans> }
|
||||
}
|
||||
if (!recipient) {
|
||||
if (recipientAddressOrName !== null) {
|
||||
return { state: SwapCallbackState.INVALID, error: <Trans>Invalid recipient</Trans> }
|
||||
} else {
|
||||
return { state: SwapCallbackState.LOADING }
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
state: SwapCallbackState.VALID,
|
||||
callback: async () => callback(),
|
||||
}
|
||||
}, [trade, provider, account, chainId, callback, recipient, recipientAddressOrName])
|
||||
}
|
||||
@@ -36,9 +36,11 @@ export const getPriceUpdateBasisPoints = (
|
||||
|
||||
export const formatSwapSignedAnalyticsEventProperties = ({
|
||||
trade,
|
||||
fiatValues,
|
||||
txHash,
|
||||
}: {
|
||||
trade: InterfaceTrade<Currency, Currency, TradeType> | Trade<Currency, Currency, TradeType>
|
||||
fiatValues: { amountIn: number | undefined; amountOut: number | undefined }
|
||||
txHash: string
|
||||
}) => ({
|
||||
transaction_hash: txHash,
|
||||
@@ -48,6 +50,8 @@ export const formatSwapSignedAnalyticsEventProperties = ({
|
||||
token_out_symbol: trade.outputAmount.currency.symbol,
|
||||
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
|
||||
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
|
||||
token_in_amount_usd: fiatValues.amountIn,
|
||||
token_out_amount_usd: fiatValues.amountOut,
|
||||
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
|
||||
chain_id:
|
||||
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
|
||||
|
||||
@@ -35,7 +35,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
import shallow from 'zustand/shallow'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
|
||||
import * as styles from './Bag.css'
|
||||
import { BagContent } from './BagContent'
|
||||
|
||||
@@ -36,7 +36,7 @@ import { InterfaceTrade, TradeState } from 'state/routing/types'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { switchChain } from 'utils/switchChain'
|
||||
import shallow from 'zustand/shallow'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
|
||||
const FooterContainer = styled.div`
|
||||
padding: 0px 12px;
|
||||
|
||||
@@ -428,7 +428,9 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
|
||||
const modifiedQuery = applyFiltersFromURL(location, collectionStats)
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
useCollectionFilters.setState(modifiedQuery as any)
|
||||
if (modifiedQuery) {
|
||||
useCollectionFilters.setState(modifiedQuery as any)
|
||||
}
|
||||
})
|
||||
|
||||
useCollectionFilters.subscribe((state) => {
|
||||
|
||||
@@ -26,7 +26,7 @@ import { ArrowLeft } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { BREAKPOINTS, ThemedText } from 'theme'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
import shallow from 'zustand/shallow'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
|
||||
import { ListModal } from './Modal/ListModal'
|
||||
import { NFTListingsGrid } from './NFTListingsGrid'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Listing, WalletAsset } from 'nft/types'
|
||||
import { useMemo, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { BREAKPOINTS } from 'theme'
|
||||
import shallow from 'zustand/shallow'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
|
||||
const BELOW_FLOOR_PRICE_THRESHOLD = 0.8
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ export const MarketplaceRow = ({
|
||||
>
|
||||
<FeeWrapper>
|
||||
<ThemedText.BodyPrimary color="textSecondary">
|
||||
{fees > 0 ? `${fees}${selectedMarkets.length > 1 ? t`% max` : '%'}` : '--%'}
|
||||
{fees > 0 ? `${fees.toFixed(2)}${selectedMarkets.length > 1 ? t`% max` : '%'}` : '--%'}
|
||||
</ThemedText.BodyPrimary>
|
||||
</FeeWrapper>
|
||||
</MouseoverTooltip>
|
||||
|
||||
@@ -16,7 +16,7 @@ import { X } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { BREAKPOINTS, ThemedText } from 'theme'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
import shallow from 'zustand/shallow'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
|
||||
import { TitleRow } from '../shared'
|
||||
import { ListModalSection, Section } from './ListModalSection'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { OPENSEA_CROSS_CHAIN_CONDUIT } from 'nft/queries/openSea'
|
||||
import { CollectionRow, ListingMarket, ListingRow, ListingStatus, WalletAsset } from 'nft/types'
|
||||
import { approveCollection, LOOKS_RARE_CREATOR_BASIS_POINTS, signListing } from 'nft/utils/listNfts'
|
||||
import { Dispatch, useEffect } from 'react'
|
||||
import shallow from 'zustand/shallow'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
|
||||
export async function approveCollectionRow(
|
||||
collectionRow: CollectionRow,
|
||||
|
||||
@@ -24,7 +24,7 @@ import InfiniteScroll from 'react-infinite-scroll-component'
|
||||
import { useInfiniteQuery } from 'react-query'
|
||||
import { easings, useSpring } from 'react-spring'
|
||||
import styled from 'styled-components/macro'
|
||||
import shallow from 'zustand/shallow'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
|
||||
import { EmptyWalletContent } from './EmptyWalletContent'
|
||||
import * as styles from './ProfilePage.css'
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
export const OPENSEA_FEE_ADDRESS = '0x0000a26b00c1F0DF003000390027140000fAa719'
|
||||
export const OPENSEA_DEFAULT_ZONE = '0x004c00500000ad104d7dbd00e3ae0a5c00560c00'
|
||||
export const OPENSEA_DEFAULT_CROSS_CHAIN_CONDUIT_KEY =
|
||||
'0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000'
|
||||
export const OPENSEA_CROSS_CHAIN_CONDUIT = '0x1e0049783f008a0085193e00003d00cd54003c71'
|
||||
export const OPENSEA_SEAPORT_V1_4_CONTRACT = '0x00000000000001ad428e4906ae43d8f9852d0dd6'
|
||||
export const OPENSEA_KEY_TO_CONDUIT = { [OPENSEA_DEFAULT_CROSS_CHAIN_CONDUIT_KEY]: OPENSEA_CROSS_CHAIN_CONDUIT }
|
||||
export const OPENSEA_DEFAULT_FEE = 0.025
|
||||
export const INVERSE_BASIS_POINTS = 10000
|
||||
|
||||
@@ -7,10 +7,11 @@ import { addressesByNetwork, MakerOrder, signMakerOrder, SupportedChainId } from
|
||||
import { Seaport } from '@opensea/seaport-js'
|
||||
import { ItemType } from '@opensea/seaport-js/lib/constants'
|
||||
import { ConsiderationInputItem } from '@opensea/seaport-js/lib/types'
|
||||
import { ZERO_ADDRESS } from 'constants/misc'
|
||||
import {
|
||||
OPENSEA_DEFAULT_CROSS_CHAIN_CONDUIT_KEY,
|
||||
OPENSEA_DEFAULT_ZONE,
|
||||
OPENSEA_KEY_TO_CONDUIT,
|
||||
OPENSEA_SEAPORT_V1_4_CONTRACT,
|
||||
} from 'nft/queries/openSea'
|
||||
|
||||
import ERC721 from '../../abis/erc721.json'
|
||||
@@ -21,7 +22,7 @@ import {
|
||||
newX2Y2Order,
|
||||
PostOpenSeaSellOrder,
|
||||
} from '../queries'
|
||||
import { INVERSE_BASIS_POINTS, OPENSEA_DEFAULT_FEE, OPENSEA_FEE_ADDRESS } from '../queries/openSea'
|
||||
import { INVERSE_BASIS_POINTS } from '../queries/openSea'
|
||||
import { ListingMarket, ListingStatus, WalletAsset } from '../types'
|
||||
import { createSellOrder, encodeOrder, OfferItem, OrderPayload, signOrderData } from './x2y2'
|
||||
|
||||
@@ -40,7 +41,7 @@ export const ListingMarkets: ListingMarket[] = [
|
||||
},
|
||||
{
|
||||
name: 'OpenSea',
|
||||
fee: 2.5,
|
||||
fee: 0,
|
||||
icon: '/nft/svgs/marketplaces/opensea.svg',
|
||||
},
|
||||
]
|
||||
@@ -58,14 +59,11 @@ const getConsiderationItems = (
|
||||
signerAddress: string
|
||||
): {
|
||||
sellerFee: ConsiderationInputItem
|
||||
openseaFee: ConsiderationInputItem
|
||||
creatorFee?: ConsiderationInputItem
|
||||
} => {
|
||||
const openSeaBasisPoints = OPENSEA_DEFAULT_FEE * INVERSE_BASIS_POINTS
|
||||
const creatorFeeBasisPoints = asset?.basisPoints ?? 0
|
||||
const sellerBasisPoints = INVERSE_BASIS_POINTS - openSeaBasisPoints - creatorFeeBasisPoints
|
||||
const sellerBasisPoints = INVERSE_BASIS_POINTS - creatorFeeBasisPoints
|
||||
|
||||
const openseaFee = price.mul(BigNumber.from(openSeaBasisPoints)).div(BigNumber.from(INVERSE_BASIS_POINTS)).toString()
|
||||
const creatorFee = price
|
||||
.mul(BigNumber.from(creatorFeeBasisPoints))
|
||||
.div(BigNumber.from(INVERSE_BASIS_POINTS))
|
||||
@@ -74,7 +72,6 @@ const getConsiderationItems = (
|
||||
|
||||
return {
|
||||
sellerFee: createConsiderationItem(sellerFee, signerAddress),
|
||||
openseaFee: createConsiderationItem(openseaFee, OPENSEA_FEE_ADDRESS),
|
||||
creatorFee:
|
||||
creatorFeeBasisPoints > 0
|
||||
? createConsiderationItem(creatorFee, asset?.asset_contract?.payout_address ?? '')
|
||||
@@ -126,6 +123,7 @@ export async function signListing(
|
||||
overrides: {
|
||||
defaultConduitKey: OPENSEA_DEFAULT_CROSS_CHAIN_CONDUIT_KEY,
|
||||
},
|
||||
seaportVersion: '1.4',
|
||||
})
|
||||
|
||||
const signerAddress = await signer.getAddress()
|
||||
@@ -135,8 +133,8 @@ export async function signListing(
|
||||
case 'OpenSea':
|
||||
try {
|
||||
const listingInWei = parseEther(`${listingPrice}`)
|
||||
const { sellerFee, openseaFee, creatorFee } = getConsiderationItems(asset, listingInWei, signerAddress)
|
||||
const considerationItems = [sellerFee, openseaFee, creatorFee].filter(
|
||||
const { sellerFee, creatorFee } = getConsiderationItems(asset, listingInWei, signerAddress)
|
||||
const considerationItems = [sellerFee, creatorFee].filter(
|
||||
(item): item is ConsiderationInputItem => item !== undefined
|
||||
)
|
||||
|
||||
@@ -147,21 +145,20 @@ export async function signListing(
|
||||
itemType: ItemType.ERC721,
|
||||
token: asset.asset_contract.address,
|
||||
identifier: asset.tokenId,
|
||||
amount: '1',
|
||||
},
|
||||
],
|
||||
consideration: considerationItems,
|
||||
endTime: asset.expirationTime.toString(),
|
||||
zone: OPENSEA_DEFAULT_ZONE,
|
||||
restrictedByZone: true,
|
||||
zone: ZERO_ADDRESS,
|
||||
allowPartialFills: true,
|
||||
},
|
||||
signerAddress
|
||||
)
|
||||
|
||||
const order = await executeAllActions()
|
||||
const seaportV14Order = { ...order, protocol_address: OPENSEA_SEAPORT_V1_4_CONTRACT }
|
||||
setStatus(ListingStatus.PENDING)
|
||||
const res = await PostOpenSeaSellOrder(order)
|
||||
const res = await PostOpenSeaSellOrder(seaportV14Order)
|
||||
res ? setStatus(ListingStatus.APPROVED) : setStatus(ListingStatus.FAILED)
|
||||
return res
|
||||
} catch (error) {
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from 'state/mint/v3/hooks'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { ButtonError, ButtonLight, ButtonPrimary, ButtonText, ButtonYellow } from '../../components/Button'
|
||||
import { ButtonError, ButtonLight, ButtonPrimary, ButtonText } from '../../components/Button'
|
||||
import { BlueCard, OutlineCard, YellowCard } from '../../components/Card'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
@@ -53,7 +53,7 @@ import { Bound, Field } from '../../state/mint/v3/actions'
|
||||
import { useTransactionAdder } from '../../state/transactions/hooks'
|
||||
import { TransactionType } from '../../state/transactions/types'
|
||||
import { useIsExpertMode, useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
import { ThemedText } from '../../theme'
|
||||
import approveAmountCalldata from '../../utils/approveAmountCalldata'
|
||||
import { calculateGasMargin } from '../../utils/calculateGasMargin'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
@@ -153,11 +153,6 @@ export default function AddLiquidity() {
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false)
|
||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
|
||||
|
||||
// capital efficiency warning
|
||||
const [showCapitalEfficiencyWarning, setShowCapitalEfficiencyWarning] = useState(false)
|
||||
|
||||
useEffect(() => setShowCapitalEfficiencyWarning(false), [baseCurrency, quoteCurrency, feeAmount])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
parsedQs.minPrice &&
|
||||
@@ -506,6 +501,8 @@ export default function AddLiquidity() {
|
||||
</AutoColumn>
|
||||
)
|
||||
|
||||
const currencyAFiat = usdcValues[Field.CURRENCY_A]
|
||||
const currencyBFiat = usdcValues[Field.CURRENCY_B]
|
||||
return (
|
||||
<>
|
||||
<ScrollablePage>
|
||||
@@ -656,7 +653,7 @@ export default function AddLiquidity() {
|
||||
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
|
||||
currency={currencies[Field.CURRENCY_A] ?? null}
|
||||
id="add-liquidity-input-tokena"
|
||||
fiatValue={usdcValues[Field.CURRENCY_A]}
|
||||
fiatValue={currencyAFiat && parseFloat(currencyAFiat.toSignificant())}
|
||||
showCommonBases
|
||||
locked={depositADisabled}
|
||||
/>
|
||||
@@ -668,7 +665,7 @@ export default function AddLiquidity() {
|
||||
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
|
||||
}}
|
||||
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
|
||||
fiatValue={usdcValues[Field.CURRENCY_B]}
|
||||
fiatValue={currencyBFiat && parseFloat(currencyBFiat.toSignificant())}
|
||||
currency={currencies[Field.CURRENCY_B] ?? null}
|
||||
id="add-liquidity-input-tokenb"
|
||||
showCommonBases
|
||||
@@ -805,7 +802,7 @@ export default function AddLiquidity() {
|
||||
disabled={!feeAmount || invalidPool || (noLiquidity && !startPriceTypedValue)}
|
||||
>
|
||||
<StackedContainer>
|
||||
<StackedItem style={{ opacity: showCapitalEfficiencyWarning ? '0.05' : 1 }}>
|
||||
<StackedItem>
|
||||
<AutoColumn gap="md">
|
||||
{noLiquidity && (
|
||||
<RowBetween>
|
||||
@@ -831,65 +828,12 @@ export default function AddLiquidity() {
|
||||
{!noLiquidity && (
|
||||
<PresetsButtons
|
||||
setFullRange={() => {
|
||||
setShowCapitalEfficiencyWarning(true)
|
||||
getSetFullRange()
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</StackedItem>
|
||||
|
||||
{showCapitalEfficiencyWarning && (
|
||||
<StackedItem zIndex={1}>
|
||||
<YellowCard
|
||||
padding="15px"
|
||||
$borderRadius="12px"
|
||||
height="100%"
|
||||
style={{
|
||||
borderColor: theme.deprecated_yellow3,
|
||||
border: '1px solid',
|
||||
}}
|
||||
>
|
||||
<AutoColumn gap="sm" style={{ height: '100%' }}>
|
||||
<RowFixed>
|
||||
<AlertTriangle stroke={theme.deprecated_yellow3} size="16px" />
|
||||
<ThemedText.DeprecatedYellow ml="12px" fontSize="15px">
|
||||
<Trans>Efficiency Comparison</Trans>
|
||||
</ThemedText.DeprecatedYellow>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
<ThemedText.DeprecatedYellow ml="12px" fontSize="13px" margin={0} fontWeight={400}>
|
||||
<Trans>
|
||||
Full range positions may earn less fees than concentrated positions. Learn more{' '}
|
||||
<ExternalLink
|
||||
style={{ color: theme.deprecated_yellow3, textDecoration: 'underline' }}
|
||||
href="https://help.uniswap.org/en/articles/5434296-can-i-provide-liquidity-over-the-full-range-in-v3"
|
||||
>
|
||||
here
|
||||
</ExternalLink>
|
||||
.
|
||||
</Trans>
|
||||
</ThemedText.DeprecatedYellow>
|
||||
</RowFixed>
|
||||
<Row>
|
||||
<ButtonYellow
|
||||
padding="8px"
|
||||
marginRight="8px"
|
||||
$borderRadius="8px"
|
||||
width="auto"
|
||||
onClick={() => {
|
||||
setShowCapitalEfficiencyWarning(false)
|
||||
getSetFullRange()
|
||||
}}
|
||||
>
|
||||
<ThemedText.DeprecatedBlack fontSize={13} color="black">
|
||||
<Trans>I understand</Trans>
|
||||
</ThemedText.DeprecatedBlack>
|
||||
</ButtonYellow>
|
||||
</Row>
|
||||
</AutoColumn>
|
||||
</YellowCard>
|
||||
</StackedItem>
|
||||
)}
|
||||
</StackedContainer>
|
||||
|
||||
{outOfRange ? (
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
getDeviceId,
|
||||
initializeAnalytics,
|
||||
OriginApplication,
|
||||
sendAnalyticsEvent,
|
||||
Trace,
|
||||
user,
|
||||
} from '@uniswap/analytics'
|
||||
import { getDeviceId, sendAnalyticsEvent, Trace, user } from '@uniswap/analytics'
|
||||
import { CustomUserProperties, getBrowser, InterfacePageName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Loader from 'components/Loader'
|
||||
@@ -13,6 +6,7 @@ import { MenuDropdown } from 'components/NavBar/MenuDropdown'
|
||||
import TopLevelModals from 'components/TopLevelModals'
|
||||
import { useFeatureFlagsIsLoaded } from 'featureFlags'
|
||||
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
|
||||
import { STATSIG_DUMMY_KEY } from 'integrations'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { lazy, Suspense, useEffect, useMemo, useState } from 'react'
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom'
|
||||
@@ -22,7 +16,7 @@ import styled from 'styled-components/macro'
|
||||
import { SpinnerSVG } from 'theme/components'
|
||||
import { flexRowNoWrap } from 'theme/styles'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
import { getEnvName, isProductionEnv } from 'utils/env'
|
||||
import { getEnvName } from 'utils/env'
|
||||
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
|
||||
|
||||
import { useAnalyticsReporter } from '../components/analytics'
|
||||
@@ -57,19 +51,6 @@ const Collection = lazy(() => import('nft/pages/collection'))
|
||||
const Profile = lazy(() => import('nft/pages/profile/profile'))
|
||||
const Asset = lazy(() => import('nft/pages/asset/Asset'))
|
||||
|
||||
// Placeholder API key. Actual API key used in the proxy server
|
||||
const AMPLITUDE_DUMMY_KEY = '00000000000000000000000000000000'
|
||||
const AMPLITUDE_PROXY_URL = process.env.REACT_APP_AMPLITUDE_PROXY_URL
|
||||
const STATSIG_DUMMY_KEY = 'client-0000000000000000000000000000000000000000000'
|
||||
const STATSIG_PROXY_URL = process.env.REACT_APP_STATSIG_PROXY_URL
|
||||
const COMMIT_HASH = process.env.REACT_APP_GIT_COMMIT_HASH
|
||||
initializeAnalytics(AMPLITUDE_DUMMY_KEY, OriginApplication.INTERFACE, {
|
||||
proxyUrl: AMPLITUDE_PROXY_URL,
|
||||
defaultEventName: SharedEventName.PAGE_VIEWED,
|
||||
commitHash: COMMIT_HASH,
|
||||
isProductionEnv: isProductionEnv(),
|
||||
})
|
||||
|
||||
const BodyWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -216,7 +197,7 @@ export default function App() {
|
||||
waitForInitialization={false}
|
||||
options={{
|
||||
environment: { tier: getEnvName() },
|
||||
api: STATSIG_PROXY_URL,
|
||||
api: process.env.REACT_APP_STATSIG_PROXY_URL,
|
||||
}}
|
||||
>
|
||||
<HeaderWrapper transparent={isHeaderTransparent}>
|
||||
|
||||
@@ -21,16 +21,16 @@ import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import Widget from 'components/Widget'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { usePermit2Enabled } from 'featureFlags/flags/permit2'
|
||||
import { useSwapWidgetEnabled } from 'featureFlags/flags/swapWidget'
|
||||
import useENSAddress from 'hooks/useENSAddress'
|
||||
import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance'
|
||||
import { useSwapCallback } from 'hooks/useSwapCallback'
|
||||
import useTransactionDeadline from 'hooks/useTransactionDeadline'
|
||||
import { useUSDPrice } from 'hooks/useUSDPrice'
|
||||
import JSBI from 'jsbi'
|
||||
import { formatSwapQuoteReceivedEventProperties } from 'lib/utils/analytics'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { ReactNode } from 'react'
|
||||
import { ArrowDown, CheckCircle, HelpCircle, Info } from 'react-feather'
|
||||
import { ArrowDown, Info } from 'react-feather'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import { useToggleWalletModal } from 'state/application/hooks'
|
||||
@@ -41,7 +41,7 @@ import invariant from 'tiny-invariant'
|
||||
import { currencyAmountToPreciseFloat, formatTransactionAmount } from 'utils/formatNumbers'
|
||||
|
||||
import AddressInputPanel from '../../components/AddressInputPanel'
|
||||
import { ButtonConfirmed, ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
|
||||
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
|
||||
import { GrayCard } from '../../components/Card'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import SwapCurrencyInputPanel from '../../components/CurrencyInputPanel/SwapCurrencyInputPanel'
|
||||
@@ -54,12 +54,7 @@ import SwapHeader from '../../components/swap/SwapHeader'
|
||||
import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
|
||||
import { TOKEN_SHORTHANDS } from '../../constants/tokens'
|
||||
import { useAllTokens, useCurrency } from '../../hooks/Tokens'
|
||||
import { ApprovalState, useApproveCallbackFromTrade } from '../../hooks/useApproveCallback'
|
||||
import useENSAddress from '../../hooks/useENSAddress'
|
||||
import { useERC20PermitFromTrade, UseERC20PermitState } from '../../hooks/useERC20Permit'
|
||||
import useIsArgentWallet from '../../hooks/useIsArgentWallet'
|
||||
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
|
||||
import { useStablecoinValue } from '../../hooks/useStablecoinPrice'
|
||||
import useWrapCallback, { WrapErrorText, WrapType } from '../../hooks/useWrapCallback'
|
||||
import { Field } from '../../state/swap/actions'
|
||||
import {
|
||||
@@ -235,16 +230,16 @@ export default function Swap({ className }: { className?: string }) {
|
||||
},
|
||||
[independentField, parsedAmount, showWrap, trade]
|
||||
)
|
||||
const fiatValueInput = useStablecoinValue(parsedAmounts[Field.INPUT])
|
||||
const fiatValueOutput = useStablecoinValue(parsedAmounts[Field.OUTPUT])
|
||||
const fiatValueInput = useUSDPrice(parsedAmounts[Field.INPUT])
|
||||
const fiatValueOutput = useUSDPrice(parsedAmounts[Field.OUTPUT])
|
||||
|
||||
const [routeNotFound, routeIsLoading, routeIsSyncing] = useMemo(
|
||||
() => [!trade?.swaps, TradeState.LOADING === tradeState, TradeState.SYNCING === tradeState],
|
||||
[trade, tradeState]
|
||||
)
|
||||
|
||||
const fiatValueTradeInput = useStablecoinValue(trade?.inputAmount)
|
||||
const fiatValueTradeOutput = useStablecoinValue(trade?.outputAmount)
|
||||
const fiatValueTradeInput = useUSDPrice(trade?.inputAmount)
|
||||
const fiatValueTradeOutput = useUSDPrice(trade?.outputAmount)
|
||||
const stablecoinPriceImpact = useMemo(
|
||||
() =>
|
||||
routeIsSyncing || !trade ? undefined : computeFiatValuePriceImpact(fiatValueTradeInput, fiatValueTradeOutput),
|
||||
@@ -303,19 +298,16 @@ export default function Swap({ className }: { className?: string }) {
|
||||
currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0))
|
||||
)
|
||||
|
||||
const permit2Enabled = usePermit2Enabled()
|
||||
const maximumAmountIn = useMemo(() => {
|
||||
const maximumAmountIn = trade?.maximumAmountIn(allowedSlippage)
|
||||
return maximumAmountIn?.currency.isToken ? (maximumAmountIn as CurrencyAmount<Token>) : undefined
|
||||
}, [allowedSlippage, trade])
|
||||
const allowance = usePermit2Allowance(
|
||||
permit2Enabled
|
||||
? maximumAmountIn ??
|
||||
(parsedAmounts[Field.INPUT]?.currency.isToken
|
||||
? (parsedAmounts[Field.INPUT] as CurrencyAmount<Token>)
|
||||
: undefined)
|
||||
: undefined,
|
||||
permit2Enabled && chainId ? UNIVERSAL_ROUTER_ADDRESS(chainId) : undefined
|
||||
maximumAmountIn ??
|
||||
(parsedAmounts[Field.INPUT]?.currency.isToken
|
||||
? (parsedAmounts[Field.INPUT] as CurrencyAmount<Token>)
|
||||
: undefined),
|
||||
isSupportedChain(chainId) ? UNIVERSAL_ROUTER_ADDRESS(chainId) : undefined
|
||||
)
|
||||
const isApprovalLoading = allowance.state === AllowanceState.REQUIRED && allowance.isApprovalLoading
|
||||
const [isAllowancePending, setIsAllowancePending] = useState(false)
|
||||
@@ -336,67 +328,20 @@ export default function Swap({ className }: { className?: string }) {
|
||||
}
|
||||
}, [allowance, chainId, maximumAmountIn?.currency.address, maximumAmountIn?.currency.symbol])
|
||||
|
||||
// check whether the user has approved the router on the input token
|
||||
const [approvalState, approveCallback] = useApproveCallbackFromTrade(
|
||||
permit2Enabled ? undefined : trade,
|
||||
allowedSlippage
|
||||
)
|
||||
const transactionDeadline = useTransactionDeadline()
|
||||
const {
|
||||
state: signatureState,
|
||||
signatureData,
|
||||
gatherPermitSignature,
|
||||
} = useERC20PermitFromTrade(permit2Enabled ? undefined : trade, allowedSlippage, transactionDeadline)
|
||||
|
||||
const [approvalPending, setApprovalPending] = useState<boolean>(false)
|
||||
const handleApprove = useCallback(async () => {
|
||||
setApprovalPending(true)
|
||||
try {
|
||||
if (signatureState === UseERC20PermitState.NOT_SIGNED && gatherPermitSignature) {
|
||||
try {
|
||||
await gatherPermitSignature()
|
||||
} catch (error) {
|
||||
// try to approve if gatherPermitSignature failed for any reason other than the user rejecting it
|
||||
if (error?.code !== 4001) {
|
||||
await approveCallback()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await approveCallback()
|
||||
|
||||
sendEvent({
|
||||
category: 'Swap',
|
||||
action: 'Approve',
|
||||
label: [TRADE_STRING, trade?.inputAmount?.currency.symbol].join('/'),
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
setApprovalPending(false)
|
||||
}
|
||||
}, [signatureState, gatherPermitSignature, approveCallback, trade?.inputAmount?.currency.symbol])
|
||||
|
||||
// check if user has gone through approval process, used to show two step buttons, reset on token change
|
||||
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
|
||||
|
||||
// mark when a user has submitted an approval, reset onTokenSelection for input field
|
||||
useEffect(() => {
|
||||
if (approvalState === ApprovalState.PENDING) {
|
||||
setApprovalSubmitted(true)
|
||||
}
|
||||
}, [approvalState, approvalSubmitted])
|
||||
|
||||
const maxInputAmount: CurrencyAmount<Currency> | undefined = useMemo(
|
||||
() => maxAmountSpend(currencyBalances[Field.INPUT]),
|
||||
[currencyBalances]
|
||||
)
|
||||
const showMaxButton = Boolean(maxInputAmount?.greaterThan(0) && !parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount))
|
||||
const swapFiatValues = useMemo(() => {
|
||||
return { amountIn: fiatValueTradeInput, amountOut: fiatValueTradeOutput }
|
||||
}, [fiatValueTradeInput, fiatValueTradeOutput])
|
||||
|
||||
// the callback to execute the swap
|
||||
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(
|
||||
const { callback: swapCallback } = useSwapCallback(
|
||||
trade,
|
||||
swapFiatValues,
|
||||
allowedSlippage,
|
||||
recipient,
|
||||
signatureData,
|
||||
allowance.state === AllowanceState.ALLOWED ? allowance.permitSignature : undefined
|
||||
)
|
||||
|
||||
@@ -460,19 +405,6 @@ export default function Swap({ className }: { className?: string }) {
|
||||
return { priceImpactSeverity: warningSeverity(largerPriceImpact), largerPriceImpact }
|
||||
}, [stablecoinPriceImpact, trade])
|
||||
|
||||
const isArgentWallet = useIsArgentWallet()
|
||||
|
||||
// show approve flow when: no error on inputs, not approved or pending, or approved in current session
|
||||
// never show if price impact is above threshold in non expert mode
|
||||
const showApproveFlow =
|
||||
!permit2Enabled &&
|
||||
!isArgentWallet &&
|
||||
!swapInputError &&
|
||||
(approvalState === ApprovalState.NOT_APPROVED ||
|
||||
approvalState === ApprovalState.PENDING ||
|
||||
(approvalSubmitted && approvalState === ApprovalState.APPROVED)) &&
|
||||
!(priceImpactSeverity > 3 && !isExpertMode)
|
||||
|
||||
const handleConfirmDismiss = useCallback(() => {
|
||||
setSwapState({ showConfirm: false, tradeToConfirm, attemptingTxn, swapErrorMessage, txHash })
|
||||
// if there was a tx hash, we want to clear the input
|
||||
@@ -487,7 +419,6 @@ export default function Swap({ className }: { className?: string }) {
|
||||
|
||||
const handleInputSelect = useCallback(
|
||||
(inputCurrency: Currency) => {
|
||||
setApprovalSubmitted(false) // reset 2 step UI for approvals
|
||||
onCurrencySelection(Field.INPUT, inputCurrency)
|
||||
},
|
||||
[onCurrencySelection]
|
||||
@@ -543,9 +474,6 @@ export default function Swap({ className }: { className?: string }) {
|
||||
setSwapQuoteReceivedDate,
|
||||
])
|
||||
|
||||
const approveTokenButtonDisabled =
|
||||
approvalState !== ApprovalState.NOT_APPROVED || approvalSubmitted || signatureState === UseERC20PermitState.SIGNED
|
||||
|
||||
const showDetailsDropdown = Boolean(
|
||||
!showWrap && userHasSpecifiedInputOutput && (trade || routeIsLoading || routeIsSyncing)
|
||||
)
|
||||
@@ -623,7 +551,6 @@ export default function Swap({ className }: { className?: string }) {
|
||||
>
|
||||
<ArrowContainer
|
||||
onClick={() => {
|
||||
setApprovalSubmitted(false) // reset 2 step UI for approvals
|
||||
onSwitchTokens()
|
||||
}}
|
||||
color={theme.textPrimary}
|
||||
@@ -725,89 +652,6 @@ export default function Swap({ className }: { className?: string }) {
|
||||
<Trans>Insufficient liquidity for this trade.</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
</GrayCard>
|
||||
) : showApproveFlow ? (
|
||||
<AutoRow style={{ flexWrap: 'nowrap', width: '100%' }}>
|
||||
<AutoColumn style={{ width: '100%' }} gap="12px">
|
||||
<ButtonConfirmed
|
||||
fontWeight={600}
|
||||
onClick={handleApprove}
|
||||
disabled={approveTokenButtonDisabled}
|
||||
width="100%"
|
||||
altDisabledStyle={approvalState === ApprovalState.PENDING} // show solid button while waiting
|
||||
confirmed={
|
||||
approvalState === ApprovalState.APPROVED || signatureState === UseERC20PermitState.SIGNED
|
||||
}
|
||||
>
|
||||
<AutoRow justify="space-between" style={{ flexWrap: 'nowrap' }} height="20px">
|
||||
{/* we need to shorten this string on mobile */}
|
||||
{approvalState === ApprovalState.APPROVED ||
|
||||
signatureState === UseERC20PermitState.SIGNED ? (
|
||||
<ThemedText.SubHeader width="100%" textAlign="center" color="textSecondary">
|
||||
<Trans>You can now trade {currencies[Field.INPUT]?.symbol}</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
) : (
|
||||
<ThemedText.SubHeader width="100%" textAlign="center" color="white">
|
||||
<Trans>Allow the Uniswap Protocol to use your {currencies[Field.INPUT]?.symbol}</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
)}
|
||||
|
||||
{approvalPending || approvalState === ApprovalState.PENDING ? (
|
||||
<Loader stroke={theme.white} />
|
||||
) : (approvalSubmitted && approvalState === ApprovalState.APPROVED) ||
|
||||
signatureState === UseERC20PermitState.SIGNED ? (
|
||||
<CheckCircle size="20" color={theme.accentSuccess} />
|
||||
) : (
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
You must give the Uniswap smart contracts permission to use your{' '}
|
||||
{currencies[Field.INPUT]?.symbol}. You only have to do this once per token.
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
<HelpCircle size="20" color={theme.white} style={{ marginLeft: '8px' }} />
|
||||
</MouseoverTooltip>
|
||||
)}
|
||||
</AutoRow>
|
||||
</ButtonConfirmed>
|
||||
<ButtonError
|
||||
onClick={() => {
|
||||
if (isExpertMode) {
|
||||
handleSwap()
|
||||
} else {
|
||||
setSwapState({
|
||||
tradeToConfirm: trade,
|
||||
attemptingTxn: false,
|
||||
swapErrorMessage: undefined,
|
||||
showConfirm: true,
|
||||
txHash: undefined,
|
||||
})
|
||||
}
|
||||
}}
|
||||
width="100%"
|
||||
id="swap-button"
|
||||
disabled={
|
||||
!isValid ||
|
||||
routeIsSyncing ||
|
||||
routeIsLoading ||
|
||||
(approvalState !== ApprovalState.APPROVED &&
|
||||
signatureState !== UseERC20PermitState.SIGNED) ||
|
||||
priceImpactTooHigh
|
||||
}
|
||||
error={isValid && priceImpactSeverity > 2}
|
||||
>
|
||||
<Text fontSize={16} fontWeight={600}>
|
||||
{priceImpactTooHigh ? (
|
||||
<Trans>High Price Impact</Trans>
|
||||
) : trade && priceImpactSeverity > 2 ? (
|
||||
<Trans>Swap Anyway</Trans>
|
||||
) : (
|
||||
<Trans>Swap</Trans>
|
||||
)}
|
||||
</Text>
|
||||
</ButtonError>
|
||||
</AutoColumn>
|
||||
</AutoRow>
|
||||
) : isValid && allowance.state === AllowanceState.REQUIRED ? (
|
||||
<ButtonPrimary
|
||||
onClick={updateAllowance}
|
||||
@@ -863,13 +707,9 @@ export default function Swap({ className }: { className?: string }) {
|
||||
routeIsSyncing ||
|
||||
routeIsLoading ||
|
||||
priceImpactTooHigh ||
|
||||
(permit2Enabled ? allowance.state !== AllowanceState.ALLOWED : Boolean(swapCallbackError))
|
||||
}
|
||||
error={
|
||||
isValid &&
|
||||
priceImpactSeverity > 2 &&
|
||||
(permit2Enabled ? allowance.state === AllowanceState.ALLOWED : !swapCallbackError)
|
||||
allowance.state !== AllowanceState.ALLOWED
|
||||
}
|
||||
error={isValid && priceImpactSeverity > 2 && allowance.state === AllowanceState.ALLOWED}
|
||||
>
|
||||
<Text fontSize={20} fontWeight={600}>
|
||||
{swapInputError ? (
|
||||
|
||||
2
src/react-app-env.d.ts
vendored
2
src/react-app-env.d.ts
vendored
@@ -5,6 +5,8 @@ declare module '@metamask/jazzicon' {
|
||||
}
|
||||
|
||||
interface Window {
|
||||
GIT_COMMIT_HASH?: string
|
||||
|
||||
// walletLinkExtension is injected by the Coinbase Wallet extension
|
||||
walletLinkExtension?: any
|
||||
ethereum?: {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
|
||||
import { AppState } from '../index'
|
||||
import { AppState } from '../types'
|
||||
import {
|
||||
addPopup,
|
||||
ApplicationModal,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import { useV2Pair } from '../../hooks/useV2Pairs'
|
||||
import { useTokenBalances } from '../connection/hooks'
|
||||
import { AppState } from '../index'
|
||||
import { AppState } from '../types'
|
||||
import { Field, typeInput } from './actions'
|
||||
|
||||
export function useBurnState(): AppState['burn'] {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { PositionDetails } from 'types/position'
|
||||
import { unwrappedToken } from 'utils/unwrappedToken'
|
||||
|
||||
import { AppState } from '../../index'
|
||||
import { AppState } from '../../types'
|
||||
import { selectPercent } from './actions'
|
||||
|
||||
export function useBurnV3State(): AppState['burnV3'] {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
|
||||
import { AppDispatch, AppState } from 'state'
|
||||
|
||||
export const useAppDispatch = () => useDispatch<AppDispatch>()
|
||||
export const useAppSelector: TypedUseSelectorHook<AppState> = useSelector
|
||||
import store from './index'
|
||||
|
||||
export const useAppDispatch = () => useDispatch<typeof store.dispatch>()
|
||||
export const useAppSelector: TypedUseSelectorHook<ReturnType<typeof store.getState>> = useSelector
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user