Compare commits

...

39 Commits

Author SHA1 Message Date
Tina
5c8b45c8e5 fix: Use non-subgraph fields for calculating USD prices on explore and token details pages (#6134)
* use non subgraph fields for querying usd prices

* undefined check

* remove unuecessary logoUrl field

* remove outdated test

* fix: switch recently fetched tokens to token project market & update missing chart message

---------

Co-authored-by: cartcrom <cartergcromer@gmail.com>
2023-03-11 14:01:38 -05:00
Tina
2eb5ff3c5c fix: USD price for WETH + don't call gql for testnets (#6133)
fix weth + dont call gql for testnets
2023-03-11 12:04:28 -05:00
Tina
a8864614c1 feat: Use ETH based pricing instead of USDC based pricing for quote USD values (#6132)
* add eth denominated usd pricing mecanism

* fix usd value change and trade price calculations

* remove console log

* break out 10k into constant

* put back faulty block number check for now - separate PR

* add 1 min poll interval

* use lib/utils/tryParseCurrencyAmount

---------

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2023-03-11 01:53:47 -06:00
Jordan Frankfurt
19d25de1d9 chore: bump dep versions (#6128)
* chore: bump dep versions

* drop sdk-core

* checkout lock from main, yarn i, dedupe
2023-03-10 19:20:42 -06:00
Vignesh Mohankumar
a9e7c6f560 chore: remove deprecated zustand import (#6129) 2023-03-10 18:42:59 -05:00
Vignesh Mohankumar
333c3a289a fix: check if supported chainId before calling universal-router-sdk (#6126) 2023-03-10 16:58:59 -05:00
Vignesh Mohankumar
d06451cb98 refactor: remove universal-router logic in pay with any token flag (#6127) 2023-03-10 16:44:41 -05:00
shortcircuit
c1297b2aa3 fix: Use html for linking to tax services (#6124)
* Use html for linking to tax services

* Add target blank

* styling

* override default styling

---------

Co-authored-by: Lynn Yu <lynn.yu@uniswap.org>
2023-03-10 13:57:48 -05:00
lynn
6ab6f4daa5 fix: Revert "fix: use chain query param properly when activating" (#6122)
Revert "fix: use `chain` query param properly when activating (#6114)"

This reverts commit ae559d164a.
2023-03-10 11:32:31 -05:00
Vignesh Mohankumar
ae559d164a fix: use chain query param properly when activating (#6114)
* fix: use `chain` query param properly when activating

* move

* Update src/hooks/useSyncChainQuery.ts

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>

---------

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2023-03-09 22:24:14 -05:00
Zach Pomerantz
a90318cbe9 fix: update test-size (#6118)
* fix: update test-size

* fix: update main size
2023-03-09 14:49:39 -08:00
Zach Pomerantz
93cf4b358e build: ignore types and hooks and update test-size (#6117)
build: gitignore types and hooks
2023-03-09 14:27:46 -08:00
Vignesh Mohankumar
ad8aff0b90 chore: update graphql types (#6115) 2023-03-09 16:50:14 -05:00
lynn
8757e413bb feat: tax service toast + modal (#6101)
* working

* unused export

* remove fiat on ramp toast

* design feedback tweaks

* add test + final design tweaks from fred

* add back fiat on ramp modal

* temp

* remove temp

* remove fiat on ramp stuff

* add back modal

* remove fiat on ramp ack from reducer

* respond to jordan

* mobile full width

* add feature flag

* fixes

* TESTING FEATURE FLAG

* testing w blair

* add stat sig server to prod env

* testing stat sig, wait for initialization

* revert stat sig testing stuff

* fix
2023-03-09 14:19:45 -05:00
Vignesh Mohankumar
78ac7650ee fix: remove full range warning in AddLiquidity (#6113)
* fix: remove full range warning in AddLiquidity

* rm button yellow
2023-03-09 13:59:03 -05:00
Jordan Frankfurt
f3e9b513ba fix: dont fetch a route for wrap transactions (#6102)
* fix: dont fetch a route for wrap transactions

* refactor logic into useBestTrade

* add mock for useWeb3React

* remove unnecessary diff

* fix tests

* Update src/hooks/useBestTrade.ts

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* Update src/hooks/useBestTrade.ts

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* remove unnecessary wethContract reference

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-03-09 11:16:33 -06:00
Annie Ke
412a10e0d9 fix: change optimism goerli addresses to actual deploy (#5542)
* fix: change optimism goerli addresses to actual deploy

* Update addresses.ts

---------

Co-authored-by: Vignesh Mohankumar <me@vig.xyz>
2023-03-09 11:56:15 -05:00
Jesse
411b690ae4 chore: update Celo logo with new version (#6111)
Update Celo logo SVG with new version
2023-03-09 11:38:34 -05:00
Tina
b721824c7f feat: Add USD amounts to swap analytics (#6108) 2023-03-09 10:07:46 -05:00
Charles Bachmeier
2451a5c9ec chore: Remove limit from topCollections query (#6104)
the limit does not exist

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-03-08 17:19:44 -08:00
eddie
d62dd585ea feat: upgrade widget again (#6106) 2023-03-08 15:43:44 -08:00
Zach Pomerantz
6ba3c6cf93 chore: group third-party initializations (#6097)
* chore: clean up 3P initialization

* chore: integrations
2023-03-08 13:45:04 -08:00
Charles Bachmeier
e069ba1153 feat: NFT Collection Search via GraphQL (#6081)
* add new search query and formatting helper fn

* working search

* skip gql search if not enabled

* minimize query

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-03-08 10:34:53 -08:00
Mike Grabowski
4ebc467c58 feat: implement Sentry for state tracking (#6015)
* feat: add sentry middleware

* chore: remove file

* chore: reorg

* chore: update txt

* chore: fix types

* chore

* chore: normalize

* add todo

* Update src/state/logging.ts

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* Update src/state/logging.ts

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* chore: update type

* nits

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-03-08 12:28:40 +04:00
Vignesh Mohankumar
7b7e4e61d2 chore: derive SupportedChainId from sdk-core (#6091)
* chore: derrive SupportedChainId from sdk-core

* fix

* fix more types

* wip

* Revert "wip"

This reverts commit dccd83f6ac.

* Revert "fix more types"

This reverts commit 4f11b5a10c.

* fix: use literal enum for derived types

* Update src/components/NetworkAlert/NetworkAlert.tsx

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-03-07 20:19:06 -05:00
Vignesh Mohankumar
e8936f500b chore: upgrade widgets (#6099) 2023-03-07 20:18:55 -05:00
Charles Bachmeier
3d8a5ed6fe feat: Support listing to Seaport v1.4 (#6094)
* upgrade seaport

* working 1.4 listing

* update OS fee to 0%

* update zone to null addr

* update var name

* rounding bug

* unused exports

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-03-07 16:34:38 -08:00
eddie
c3d2c43861 fix: unsupported chain bugs (#6098)
* feat: upgrade widget again

* fix: verify chainId is supported
2023-03-07 16:02:06 -08:00
Zach Pomerantz
de5816e573 feat: export window.GIT_COMMIT_HASH (#6080) 2023-03-07 13:54:59 -08:00
eddie
5a2f81c4bf fix: show error message when token is not found (#6057)
* fix: show error message when token is not found

* fix: make onContinue optional
2023-03-07 13:04:51 -08:00
eddie
c34742c23b chore: add reviewers to dependabot PRs (#6095)
* chore: add reviewers to dependabot PRs

* chore: update dependabot reviewers to team

* fix: remove web tokens

* Update .github/dependabot.yml

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-03-07 12:45:36 -08:00
Vignesh Mohankumar
6225462645 chore: upgrade web3-react, zustand (#6090)
fix
2023-03-07 15:31:41 -05:00
eddie
c21b9379fd feat: add Peer Wallet Name to WALLET_CONNECT_TXN_COMPLETED event (#6084)
* feat: add Peer Wallet Name to WALLET_CONNECT_TXN_COMPLETED event

* fix: yarn dedup

* feat: use agent instead of name

* fix: rename name to agent
2023-03-07 10:48:49 -08:00
Vignesh Mohankumar
1610356a18 fix: Revert "fix: deep link by swapping sooner after user interaction" (#6092)
Revert "fix: deep link by swapping sooner after user interaction (#6073)"

This reverts commit 3eeb467266.
2023-03-07 13:29:10 -05:00
Jack Short
1b8cee7e87 fix: react router updated appropriate fix (#6086) 2023-03-07 13:05:54 -05:00
Zach Pomerantz
a7c6ce499d test: enforce package size (#4098)
* build: use fewer babel versions

* build: dedup

* test: test deps dedups

* fix: test.yml

* fix: typo

* test: failing

* fix: dedup

* fix: dedup

* test: comment dedup tests

* chore: whitespace

* test: package size

* build: scripts

* test: update test-size

* docs: CONTRIBUTING
2023-03-07 09:27:20 -08:00
Charles Bachmeier
e9fc55550d fix: Revert "chore: upgrade web3-react, widgets" (#6085)
Revert "chore: upgrade web3-react, widgets (#6077)"

This reverts commit 521b9b8e52.
2023-03-06 18:44:08 -08:00
Zach Pomerantz
3eeb467266 fix: deep link by swapping sooner after user interaction (#6073)
* build: conedison@1.5

* fix: use conedison/sendTransaction when able

* build: dedup

* build: upgrade conedison again

* build: deduplicate

* fix: nits
2023-03-06 15:14:47 -08:00
Vignesh Mohankumar
585d67c44a refactor: always enable permit2 (#6068)
* refactor: always enable permit2

* rm from modal

* one more

* rm

* more

* more

* more

* rm useswapcallback

* rm useapprovecallback

* unused hooks

* unused hooks

* rm

* lint
2023-03-06 18:07:19 -05:00
114 changed files with 1329 additions and 9137 deletions

View File

@@ -9,3 +9,5 @@ updates:
- dependency-name: '@uniswap/default-token-list'
- dependency-name: '@uniswap/token-lists'
- dependency-name: '@uniswap/widgets'
reviewers:
- 'Uniswap/dependabot-reviewers'

View File

@@ -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
View File

@@ -9,6 +9,7 @@
/src/locales/**/pseudo.po
# generated graphql types
/src/graphql/**/__generated__
schema.graphql
# dependencies

View File

@@ -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.

View File

@@ -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', () => {

View File

@@ -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')
})
})
})
})

View File

@@ -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(() => {

View File

@@ -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",

View File

@@ -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
View 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)
}

View File

@@ -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

View File

@@ -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:

View File

@@ -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 }}>
{' '}

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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>
)
}

View File

@@ -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};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 ? (

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View 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>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View 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()
})

View 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>
)
})

View File

@@ -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>
)
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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}
/>
)

View File

@@ -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'

View File

@@ -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 />}
</>
)
}

View File

@@ -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}

View File

@@ -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) => {

View File

@@ -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}>

View File

@@ -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}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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.

View File

@@ -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'

View File

@@ -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)) {

View File

@@ -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>

View File

@@ -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,
}

View File

@@ -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')

View File

@@ -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,

View File

@@ -9,4 +9,5 @@ export enum FeatureFlag {
gqlRouting = 'gqlRouting',
statsigDummy = 'web_dummy_gate_amplitude_id',
nftGraphql = 'nft_graphql_migration',
taxService = 'tax_service_banner',
}

View File

@@ -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 }

View File

@@ -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 }

View 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 }

View File

@@ -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
}
}
}
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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
}
}
}
}

View 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
}
}
}
}
}
`

View File

@@ -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])

View File

@@ -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
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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])
}

View 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 }
}

View File

@@ -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

View File

@@ -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,

File diff suppressed because it is too large Load Diff

View File

@@ -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:

View File

@@ -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)]
}

View File

@@ -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', () => {

View File

@@ -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(

View File

@@ -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)
}

View File

@@ -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,
])
}

View File

@@ -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
View 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
}

View File

@@ -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,

View File

@@ -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
View 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(),
})

View File

@@ -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])
}

View File

@@ -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)
}

View File

@@ -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])
}

View File

@@ -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

View File

@@ -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'

View File

@@ -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;

View File

@@ -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) => {

View File

@@ -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'

View File

@@ -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

View File

@@ -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>

View File

@@ -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'

View File

@@ -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,

View File

@@ -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'

View File

@@ -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

View File

@@ -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) {

View File

@@ -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 ? (

View File

@@ -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}>

View File

@@ -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 ? (

View File

@@ -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?: {

View File

@@ -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,

View File

@@ -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'] {

View File

@@ -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'] {

View File

@@ -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