Compare commits

...

93 Commits

Author SHA1 Message Date
e2d5f85ce0 no need actions 2024-01-05 17:28:30 +00:00
9ee5aa173f rpcs 2024-01-05 17:25:52 +00:00
f99f20fe18 fuck telemetry 2024-01-05 17:25:33 +00:00
fb0108196c add torn to default tokens list 2023-12-22 07:12:38 +00:00
986cf07391 remove warnings 2023-12-22 07:12:12 +00:00
48eb3f0005 remove prohibited & blocked lists checking 2023-12-19 17:21:56 +00:00
a95c8fab5a change mainnet rpc bcs infura blocks connections not from uni 2023-12-19 17:21:10 +00:00
1c131ee496 fix installation graphql script 2023-12-19 13:56:52 +00:00
911af900ed remove tokens censoring 2023-12-19 13:16:53 +00:00
Kristie Huang
fc7ecc7e3b
feat: [info] add multi-chain balances on TDP ()
* feat: wip, [info] add TDP crosschain balances

* very wip new balances

* progress on balances

* wip new balance

* add todo for native tokens

* fix bridge info caching

* fix bridge info caching & clean up

* cleanup query logic

* remove pollinginterval enum change

* fix logo flickering

* minor comment cleanup

* more minor comment cleanup

* use gqlToCurrency instead

* css changes for balance box

* css changes for mobile balance summary footer

* fix apollo client caching tokens merge

* clarify comment

* make chainId required

* comment cleanup

* fix: balance fetch caching

* fix prefetchbalancewrapper css jank

* remove padding

* delete extraneous borderRadius

* update comment

* should not show balancecard at all if no balances

* rename to multichain

* changes to mobile bar css

* use surface1 theme background

* oops add back bottom-bar

* fix cypress tests ??

* revert change

* broken apollo merge??

* remove extraneous tokens call

* remove apollo merge for portfolio>tokens

* oops fix some pr review

* load portfolio balances as it updates

* pr review

* update comment linear ticket

* remove extraneous chainId prop

* increase timeout time

* should not do symbols check

* pr review

* pr review

* refactor multichainbalances into map

* remove address native

* nit pr review

* use portfoliobalance fragment

* fix typechecking gql

* TYPES

---------

Co-authored-by: cartcrom <cartergcromer@gmail.com>
2023-11-27 13:40:19 -05:00
Kristie Huang
4a5a41c59e
fix: fix uniswapx feature flag test cleanup () 2023-11-21 14:42:40 -05:00
Kristie Huang
4bec816e6c
fix: disable fees and uniswapx tests + skip localStorage reads for bl… ()
fix: disable fees and uniswapx tests + skip localStorage reads for blocked addresses ()

* fix: disable fees tests

* skip uniswapx tests for now

* turn off uniswapx for classic swap test

* skip local cache reads for blocked accounts

* fix: broken pools test ()

* test: update hardhat blocknumber ()

* init

* fix: remove console log

* fix: add comment

---------

Co-authored-by: Tina <59578595+tinaszheng@users.noreply.github.com>
Co-authored-by: cartcrom <cartergcromer@gmail.com>
Co-authored-by: cartcrom <39385577+cartcrom@users.noreply.github.com>
2023-11-21 14:10:28 -05:00
Kristie Huang
1d1b15f4ac
fix: android banner DownloadButton onClick should not propagate up ()
* android banner link - remove linktarget

* stop propagation?

* linter
2023-11-20 12:36:02 -05:00
Tina
1ffaf723de
fix: dont show USD price difference for wraps ()
dont show stableconi price difference on wraps
2023-11-17 17:46:16 -05:00
Tina
dd4b2dc764
fix: only change input currency to weth after eth wrap completes for uniswapx eth input trades ()
* only change input currency to weth after wrap completes

* add e2e test

* update test
2023-11-17 10:47:16 -05:00
eddie
5ded55e061
feat: x rollout cleanup ()
* feat: cleanup post x rollout

* feat: remove feature flag

* fix: remove more unused styled components

* fix: delete deprecated value from redux store

* fix: lint

* fix: remove userOptedOutOfUniswapX

* fix: migrate verion in edge case, add test
2023-11-16 13:59:36 -08:00
eddie
0f4ca592f2
fix: remove /increase route ()
* fix: remove /increase route

* fix: rename confusing components
2023-11-16 13:36:57 -08:00
Kristie Huang
90497dc08a
chore: refactor TDP time selector ()
* feat: [info] add tdp charts toggle, WIP

* some refactoring

* remove chartType related changes after cherrypick

* nit

* remove setTransition
2023-11-16 13:46:25 -05:00
eddie
2e618fb2aa
fix: remove 3 launched feature flags ()
* fix: remove 3 launched feature flags

* fix: remove unused componnent

* fix: remove another apple logo
2023-11-15 11:17:02 -08:00
cartcrom
0aa5727cdd
fix: don't format wrap input amount ()
* fix: don't format wrap input amount

* lint
2023-11-15 13:58:57 -05:00
Tina
79e74e1d13
fix: disable showing approve cost for arbitrum ()
disable showing approve cost for arbitrum
2023-11-15 13:24:13 -05:00
eddie
52dc441e31
feat: swap component refactor limits ()
* feat: add limits tab, flag

* feat: add unit test

* fix: update snapshot
2023-11-15 09:00:03 -08:00
Tina
ff6d1cc510
feat: read token taxes from backend response ()
* read token taxes from backend

* revert env changes

* upgrade router-sdk for updated price impact logic

* add tax information to trade currencies instead of directly on trade object

* consolidate getTradeCurrencies with getSwapCurrenciesWithTaxInfo

* delete feature flag for token taxes!

* run yarn dedupe again

* fix unit tests

* update logic for disabling inputs

* update snapshot again

* fix return value for uniswapx

* remove unused constants and update comment

* pr review

* re-add useSwapTaxes for token descriptor page

* add in client-side tax fetching on currency level

* revert removing newline

* typecheck....

* typecheck...

* remove inputTax, outputTax from routing-api arguments because they are now unused

* dont pass in tax info to preview trade
2023-11-15 09:57:43 -05:00
cartcrom
76157c057e
fix: portfolioLogo alignment ()
* fix: portfolioLogo alignment

* fix: snapshot
2023-11-14 17:02:16 -05:00
eddie
a1bd6f5eb4
feat: update default router preference in redux () 2023-11-14 11:05:47 -08:00
Jack Short
f903eedc15
fix: redux migration to flush german locale ()
* fix: redux migration to flush german locale

* lint

* my linter was not workking
2023-11-14 13:29:17 -05:00
eddie
1feeaea181
test: update e2e tests after X rollout ()
test: updatea e2e tests after X rollout
2023-11-14 09:13:57 -08:00
eddie
7b10c94e4d
fix: update robots.txt file () 2023-11-14 09:13:44 -08:00
eddie
f2f59d52cb
feat: update legacy redux migration post X rollout () 2023-11-13 15:19:28 -08:00
eddie
a5034cb1c0
fix: reset token selections when changing chains on /add ()
* fix: reset token selections when  changing chains on /add

* fix: tests

* fix: add e2e test

* fix: remove .only
2023-11-13 15:10:44 -08:00
eddie
2227a38276
fix: set current redux version to 3 ()
* fix: set current redux version to 3

* fix: tests
2023-11-13 14:21:50 -08:00
Thomas Thachil
9f06747958
fix(): deeplink for android wc () 2023-11-13 16:46:15 -05:00
Kristie Huang
c6b44bb5c9
fix: use NativeCurrency for polygon matic ()
* fix: use NativeCurrency for polygon matic

* add comment

* update snapshots??

* Revert "update snapshots??"

This reverts commit 280758be118610cc9e13afcd6e420985e8a200d2.
2023-11-13 16:11:33 -05:00
Zach Pomerantz
1d64d24d31
fix: update function tests for 404ing collections () 2023-11-13 15:39:51 -05:00
cartcrom
d8e43f0834
fix: broken pools test () 2023-11-09 19:57:01 -05:00
cartcrom
82f27186cf
test: update hardhat blocknumber ()
* init

* fix: remove console log

* fix: add comment
2023-11-09 18:43:10 -05:00
Kristie Huang
876d3a1cc3
feat: [info] add new tdp nav ()
* feat: [info] add new tdp nav

* tidy up code

* pr review

* nit remove unused component style

* pr review

* lol extraneous extra
2023-11-09 14:51:23 -05:00
Nate Wienert
712f82cb1a
chore: align eslint version with wallet repo () 2023-11-09 08:11:13 -10:00
Tina
682215a574
feat: use useUSDPrice hook for calculating token usd value for auto slippage () 2023-11-09 13:08:54 -05:00
Kristie Huang
395b390df6
feat: add android announcement banner ()
* feat [wip]: add android announcement banner

* feat: [wip] add android announcement banner

* finish css

* remove hideBaseWallet references

* phil changes

* minor lint nit

* pr review

* growth copy
2023-11-08 16:18:16 -05:00
Tina
cee3390b71
fix: skip all quote / pricing requests if window is not visible ()
* skip all quote / pricing requests if window is not visible

* add unit tests

* add ts-ignore comment
2023-11-08 15:14:50 -05:00
Jack Short
418ee08b00
chore: adds e2e test for when usd quote fetch fails ()
* chore: adds e2e test for when usd quote fetch fails

* Update src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx

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

* Update cypress/e2e/swap/errors.test.ts

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

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-11-08 13:27:10 -05:00
Charles Bachmeier
ebfcd8fbbe
refactor: move pool details components to the components folder ()
* refactor: move pool details components to the components folder

* cleanup index imports

* update snapshot
2023-11-08 10:24:59 -08:00
Jack Short
c27e70b87c
chore: e2e insufficient liquidity test ()
* chore: e2e insufficient liquidity test

* Update cypress/e2e/swap/errors.test.ts

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

* fixing lint

* waiting for quote

* stubbing insufficient liquidity response

* req reply

* trying full url

* maybe cors

* moving before visit

* adding back timeout

* Revert "adding back timeout"

This reverts commit 89cff3afb815f5e5db005347f20812b83e047057.

* in describe block

* moving to new file

* moving to errors test file

* moving comment

* removing extra describe

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-11-08 13:11:41 -05:00
Jack Short
b4f3555600
chore: add appropriate padding and update chain selector to styled ()
* chore: add appropriate padding and update chain selector to styled

* light gray on hover remove

* Update src/components/NavBar/ChainSelector.tsx

Co-authored-by: Charles Bachmeier <charles@bachmeier.io>

---------

Co-authored-by: Charles Bachmeier <charles@bachmeier.io>
2023-11-08 12:31:45 -05:00
Jack Short
0e87c38548
chore: disabling exact output for fot tokens ()
* chore: disabling exact output for fot tokens

* fixing currency symbol
2023-11-08 12:28:23 -05:00
Charles Bachmeier
245d0eed06
fix: hide invalid nft collections with 0 items from search bar ()
fix: hide invalid collecitons with 0 items
2023-11-08 12:21:14 -05:00
Kristie Huang
46c8caa09c
feat: update app download tracking for Android launch ()
* feat: change UniwalletModal android text

* very wip android WC

* adding android/ios disambiguated event names

* put analytics events in todos

* use analytics package

* use isAndroidGALaunched

* fix ternary

* add navbar menu element

* broken onelink changes

* replace utm with onelinks

* use microsite link in address redirect

* fix unit tests, no longer discriminate between platforms expected behavior

* nit lint
2023-11-07 17:24:44 -05:00
Jack Short
aa056adaf9
chore: removing check mark on token selector () 2023-11-07 15:20:49 -05:00
Charles Bachmeier
ce4df4f79e
fix: add chainId to dependency array ()
fix: add chain to dependency array
2023-11-07 09:58:25 -08:00
Kristie Huang
769a7ab9b5
fix: rename WrappedTokenInfo class ()
* fix: rename WrappedTokenInfo class

* delete old wrappedTokenInfo file

* rename to TokenFromList

* move back to state/lists

* appease linter

* fix nftlistrow
2023-11-06 16:15:41 -05:00
Kristie Huang
5cbc56cf65
feat: [info] add new stats box ()
* feat: [info] add new stats section, wip

* add stats section

* implement fdv and market cap

* use fdv from backend gql

* code cleanup

* update cypress tests

* should only wrap if screen width <= 640

* minor design nits

* remove sitemap change

* nit pr review
2023-11-06 15:54:43 -05:00
eddie
098c7b9cbe
fix: update uniwallet wc link ()
* fix: update uniwallet wc link

* fix: undo url change for redirect
2023-11-06 12:22:03 -08:00
Jack Short
a0d880cf81
fix: spam tokens showing up in currency selector ()
* fix: spam tokens showing up in currency selector

* chain id check

* token chain id
2023-11-06 14:40:36 -05:00
Kristie Huang
443a00a777
fix: remove version check in redux lists update + migrate outdated USDC saved tokens ()
* fix: remove version check lists update

* add migration and test

* pr review
2023-11-06 14:20:55 -05:00
Jack Short
9eaa22f644
chore: converting useStablecoinPrice to useUsdPrice in autoslippage () 2023-11-03 12:51:04 -04:00
Nate Wienert
1c92482855
fix: basel font using wrong variation settings ()
* fix: basel font using wrong variation settings
2023-11-02 11:05:13 -10:00
Nate Wienert
274d79dfde
chore: align uniswap deps with wallet repo ()
* chore: align uniswap deps with wallet repo
2023-11-02 11:02:09 -10:00
Charles Bachmeier
c2ca9ab93e
feat: [info] update token links ()
* update pdp link styles

* dynamic link text

* move links to their own file

* border width case

* todo comments

* add explorer icon

* hide chain logos on other chain

* remove quotes

* clean up

* unused style
2023-11-02 13:53:18 -07:00
Tina
0fbc826581
feat: Remove logging whole error object for analytics () 2023-11-02 16:32:12 -04:00
Charles Bachmeier
e9f784b2bc
refactor: clean up getExplorerLink helper fn () 2023-11-02 09:11:13 -07:00
Charles Bachmeier
f9a9469523
feat: [info] Add token description to TDP ()
* update token description for TDP

* add tooltip to fee

* show buy/sell fees

* remove token description from PDP

* remove unused styles

* update snapshots

* undo fee component testing

* isInfoTDPEnabled

* update explorer fn
2023-11-02 08:32:58 -07:00
Jack Short
b995f4d671
fix: fixing ugly scrollbar () 2023-11-01 14:27:07 -04:00
Kristie Huang
3f62bcf2f0
feat: add Android feature flag & change some app logos ()
* fix: generalize iOS language to app, and add color app icon

* remove apple logo

* delete more apple logos

* remove learn more arrow

* update snapshots

* add feature flags to android changes
2023-11-01 14:25:15 -04:00
Jack Short
bd30721989
chore: updating nft numbers ()
* format price impact

* format price

* adding confirm swap modal

* removing export

* adding export back

* half of test functions done

* format numbers tests

* formatting

* making numberFormat local

* making formatCurrencyAmount internal

* price impact

* formatSlippage

* formatTickPrice

* formatNumberOrString

* formatFiatPrice

* removing formatPreciseFloat

* formatReviewSwapCurrencyAmount

* correct active currency

* explore table

* deleting formatTickPrice

* fixing explore table

* nft assset details

* removing all instancees of ethnumberstandardformatter

* removing format wei impls

* explore table

* collectino stats

* removing almost everything from nft/numbers

* filter button

* final nft fixes

* removing put commas

* explore page

* listing page

* extraneous functions

* responding to comments

* formatEhter

* updating formatter names

* dep array

* comments

---------

Co-authored-by: John Short <john.short@CORN-Jack-899.local>
2023-11-01 12:44:45 -04:00
Jack Short
802d56231a
fix: updating currency search panel for number formatting () 2023-10-31 16:46:02 -04:00
Kristie Huang
9536df2ff1
fix: remove bridged USDC from quickselect ()
* fix: remove bridged usdc from quickselect

* oops remove migrations logic

* update routing test
2023-10-31 16:26:44 -04:00
Jack Short
ff3ed31dd7
chore: cleaning up previous uk blocking functionality () 2023-10-31 13:45:26 -04:00
Jack Short
719f82c7c4
feat: pool page currency conversion () 2023-10-31 13:44:38 -04:00
mr-uniswap
0937e35095
ci: removed snyk files ()
removed snyk files
2023-10-31 13:36:24 -04:00
Matthew Spector
3f9b436c86
fix: Changing some language around fees ()
* fix: Changing some language around fees

* adding chain name to toolti

* lowercase network

* fixing tests
2023-10-31 09:34:04 -07:00
Kristie Huang
40afc7388a
fix: add space to migrateV2 link () 2023-10-31 01:52:36 -04:00
mr-uniswap
eff6484a10
ci: basic semgrep configuration ()
* basic semgrep configuration

* only main branch
2023-10-30 16:00:40 -04:00
Kristie Huang
635875345e
fix: add native USDC to Optimism quick-select ()
* fix: add native USDC to Optimism quick-select

* fix jest test
2023-10-30 13:08:28 -04:00
Kristie Huang
b670affd4c
fix: add native USDC to Polygon quick-select ()
* fix: add native USDC to Polygon quick-select

* remove usdce

* pr review
2023-10-30 11:35:45 -04:00
Charles Bachmeier
6798bf3cf1
feat: [info] add PDP loading skeleton ()
* initial skeleton setup

* responsive table skeleton

* correct table widht

* right side column added

* add comments

* move loading components to their corresponding component

* remove extra bubble and adjust some styles

* move table skeleton to its own file

* add shared styles and skele file

* add loading skeleton tests

* design style nits

* update tests

* bips_base

* fix regression
2023-10-20 13:11:22 -07:00
Kristie Huang
8734ee5986
fix: dedupe matic native token ()
* fix: dedupe matic native token, wip

* use precompile address

* prefill swap currency with page chain, not connected chain

* fix token-explore test
2023-10-20 14:13:58 -04:00
Charles Bachmeier
226fc441a7
refactor: allow missing input for useSwapTaxes () 2023-10-20 09:54:13 -07:00
eddie
36242d14b0
feat: add temporary logging to swap_signed () 2023-10-19 13:49:13 -07:00
eddie
b02352e8bf
fix: dont try to log unserializable objects ()
* fix: dont log unserializable objects to amplitude

* fix: add more fields

* fix: nits

* fix: add chainid
2023-10-19 13:38:16 -07:00
Nate Wienert
819e2f5712
fix: fix contrast on l2 network text ()
* fix: fix contrast on l2 network text
2023-10-19 13:05:26 -04:00
Jack Short
5357c58ac9
chore: removing german from supported languages ()
* chore: removing german from supported languages

* updating tests

* Update src/utils/formatNumbers.test.ts

Co-authored-by: Charles Bachmeier <charles@bachmeier.io>

---------

Co-authored-by: Charles Bachmeier <charles@bachmeier.io>
2023-10-18 16:01:47 -04:00
Zach Pomerantz
aada666c1a
fix: de-flake Cypress through various means ()
* build: reduce retries to discourage flakes

* fix: lazy-load asset logos

* chore: simplify logging test

* fix: guard against dutch orders for pricing

* test: only stub non-pricing quotes

* fix: opt in flicker

* test: mock statsig
2023-10-18 12:44:52 -07:00
cartcrom
86fc15907a
refactor: consolidate percent formatting util ()
* refactor: consolidate percent formatting util

* refactor: use generic percent formatter

* fix: add descriptive comment for formatDelta
2023-10-18 14:34:37 -04:00
Kristie Huang
c5f2df4bc0
fix: update USDbC to default USDC on Base ()
* fix: update usdbc to default usdc

* add base to chainid logo lookup

* remove nit

* replace dai with usdbc
2023-10-18 14:03:55 -04:00
ksvat
71d3661b22
fix: currency balance for search token () 2023-10-18 10:54:47 -07:00
eddie
740db0fe16
fix: merge portfolio tokens w/ default token list in Currency Selector () 2023-10-17 14:00:39 -07:00
Kristie Huang
ed6afb50de
feat: [info] add explore page ()
* feat: add explore page

* add explore filter options

* add search bar defocus

* use Tokens Tab state for filters

* flag-gate /tokens

* filter bar css

* remove duplicate Explore page, use flag instead

* fixes

* create tabbednav interface

* rename Tokens to Explore

* simplify routing

* nit

* padding nit

* pr review

* fix menu flyouts

* fix TDP nav

* add analytics events + ui updates + pr review

* nit

* nit

* add small comment

* menu flyout nit

* fix merge conflict

* min width expand menu flyouts

* fix redirects

* update routing snapshot

* nit css

* oops

* breakpoints

* fix tab routing

* pr review

* add better tab dynamic routing

* fix redirects

* error handle edge urls

* further fix routing

* define return val for useExploreParams

* Update snapshot
2023-10-17 15:34:34 -04:00
eddie
9d439e7f62
feat: organize sitemaps () 2023-10-17 11:07:23 -07:00
charity-sock-pacifist
6b60855362
fix: learn more url [main] ()
* fix: learn more url

* fix: snapshots

---------

Co-authored-by: charity-sock-pacifist <interface-github@example.com>
2023-10-17 13:58:16 -04:00
dependabot[bot]
53b53c2207
build(deps-dev): bump @uniswap/default-token-list from 11.2.0 to 11.8.0 ()
Bumps [@uniswap/default-token-list](https://github.com/Uniswap/default-token-list) from 11.2.0 to 11.8.0.
- [Release notes](https://github.com/Uniswap/default-token-list/releases)
- [Commits](https://github.com/Uniswap/default-token-list/commits)

---
updated-dependencies:
- dependency-name: "@uniswap/default-token-list"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 12:12:41 -04:00
charity-sock-pacifist
2818167131
feat: swap fees [main] () 2023-10-16 19:32:39 -04:00
eddie
c2440d1080
fix: use a different ipfs gateway () 2023-10-16 14:20:44 -07:00
314 changed files with 12779 additions and 8041 deletions
.env.env.production
.github
.snykcypress.config.ts
cypress
functions
api/image/nfts/collection
nfts
asset/__snapshots__
collection
tokens/__snapshots__
hardhat.config.jslingui.config.tspackage.json
public
scripts
src

10
.env

@ -1,8 +1,8 @@
# These API keys are intentionally public. Please do not report them - thank you for your concern.
ESLINT_NO_DEV_ERRORS=true
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
REACT_APP_AMPLITUDE_PROXY_URL="https://null.null"
REACT_APP_AWS_API_REGION="us-east-2"
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
REACT_APP_AWS_API_ENDPOINT="https://null.null"
REACT_APP_BNB_RPC_URL="https://rough-sleek-hill.bsc.quiknode.pro/413cc98cbc776cda8fdf1d0f47003583ff73d9bf"
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_QUICKNODE_MAINNET_RPC_URL="https://magical-alien-tab.quiknode.pro/669e87e569a8277d3fbd9e202f9df93189f19f4c"
@ -10,7 +10,7 @@ REACT_APP_MOONPAY_API="https://api.moonpay.com"
REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLinkV2?platform=web&env=staging"
REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_test_DycfESRid31UaSxhI5yWKe1r5E5kKSz"
REACT_APP_SENTRY_DSN="https://a3c62e400b8748b5a8d007150e2f38b7@o1037921.ingest.sentry.io/4504255148851200"
REACT_APP_STATSIG_PROXY_URL="https://api.uniswap.org/v1/statsig-proxy"
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
REACT_APP_UNISWAP_API_URL="https://api.uniswap.org/v2"
REACT_APP_STATSIG_PROXY_URL="https://null.null"
REACT_APP_TEMP_API_URL="https://null.null"
REACT_APP_UNISWAP_API_URL="https://null.null"
REACT_APP_WALLET_CONNECT_PROJECT_ID="c6c9bacd35afa3eb9e6cccf6d8464395"

@ -1,16 +1,16 @@
# These API keys are intentionally public. Please do not report them - thank you for your concern.
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
REACT_APP_AWS_API_ENDPOINT="https://api.uniswap.org/v1/graphql"
REACT_APP_AMPLITUDE_PROXY_URL="https://null.null"
REACT_APP_AWS_API_ENDPOINT="https://null.null"
REACT_APP_BNB_RPC_URL="https://old-wispy-arrow.bsc.quiknode.pro/f5c060177236065c1058531a0615ab4f7a34a2fd"
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
REACT_APP_MOONPAY_API="https://api.moonpay.com"
REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLinkV2?platform=web&env=production"
REACT_APP_MOONPAY_LINK="https://null.null"
REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_live_uQG4BJC4w3cxnqpcSqAfohdBFDTsY6E"
REACT_APP_SENTRY_ENABLED=true
REACT_APP_SENTRY_TRACES_SAMPLE_RATE=0.00003
REACT_APP_STATSIG_PROXY_URL="https://api.uniswap.org/v1/statsig-proxy"
REACT_APP_STATSIG_PROXY_URL="https://null.null"
REACT_APP_QUICKNODE_MAINNET_RPC_URL="https://ultra-blue-flower.quiknode.pro/770b22d5f362c537bc8fe19b034c45b22958f880"
THE_GRAPH_SCHEMA_ENDPOINT="https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3?source=uniswap"

1
.github/CODEOWNERS vendored

@ -1 +0,0 @@
* @uniswap/web-reviewers

@ -1,22 +0,0 @@
---
name: Bug Report
about: Describe an issue in the Uniswap Interface
title: ''
labels: bug
assignees: ''
---
**Bug Description**
A clear and concise description of the bug.
**Steps to Reproduce**
1. Go to ...
2. Click on ...
...
**Expected Behavior**
A clear and concise description of what you expected to happen.
**Additional Context**
Add any other context about the problem here (screenshots, whether the bug only occurs only in certain mobile/desktop/browser environments, etc.)

@ -1,8 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Support
url: https://discord.gg/FCfyBSbCU5
about: Please ask and answer questions here
- name: List a token
url: https://github.com/Uniswap/default-token-list#adding-a-token
about: Any requests to add a token to Uniswap should go here

@ -1,19 +0,0 @@
---
name: Feature Request
about: Suggest an idea for improving the UX of the Uniswap Interface
title: ''
labels: 'improvement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

@ -1,48 +0,0 @@
name: Report
description: Report test failures via Slack
inputs:
name:
description: The name of the failing test
required: true
SLACK_WEBHOOK_URL:
description: The webhook URL to send the report to
required: true
runs:
using: composite
steps:
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
with:
payload: |
{
"text": "${{ inputs.name }} failing on `${{ github.ref_name }}`",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${{ inputs.name }} failing on `${{ github.ref_name }}`:* <https://github.com/${{ github.repository}}/actions/runs/${{ github.run_id }}|view failing action>"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "_This is blocking pull requests and branch promotions._\n_Please prioritize fixing the build._"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ inputs.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
# The !oncall bot requires its own message:
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
with:
payload: |
{
"text": "!oncall web"
}
env:
SLACK_WEBHOOK_URL: ${{ inputs.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

@ -1,49 +0,0 @@
name: Setup
description: checkout repo, setup node, and install node_modules
runs:
using: composite
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
registry-url: https://registry.npmjs.org
# cache is intentionally omitted, as it is faster with yarn v1 to cache node_modules.
- uses: actions/cache@v3
id: install-cache
with:
# node_modules/.cache is intentionally omitted, as this is used for build tool caches.
path: |
node_modules
!node_modules/.cache
key: ${{ runner.os }}-install-${{ hashFiles('yarn.lock') }}
- if: steps.install-cache.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile --ignore-scripts
shell: bash
# Run patch-package to apply patches to dependencies.
- run: yarn patch-package
shell: bash
# Contracts are compiled from source. If source hasn't changed, the contracts do not need to be re-compiled.
- uses: actions/cache@v3
id: contracts-cache
with:
path: |
src/abis/types
src/types/v3
key: ${{ runner.os }}-contracts-${{ hashFiles('src/abis/**/*.json', 'node_modules/@uniswap/**/artifacts/contracts/**/*.json') }}
- if: steps.contracts-cache.outputs.cache-hit != 'true'
run: yarn contracts
shell: bash
# These operations cannot be cached, so they are run concurrently
# - ajv: Validators compile quickly, so caching can be omitted.
# - graphql: GraphQL is generated from schema and client-side graphql queries. The schema is always fetched and
# changes to client-side queries are hard to detect, so it is always re-generated.
# - i18n: Messages are extracted from source and compiled. No caching extractor is available (out-of-the-box).
- run: yarn concurrently --max-processes=100% npm:ajv npm:graphql npm:i18n
shell: bash

@ -1,12 +0,0 @@
version: 2
updates:
- package-ecosystem: npm
# Files stored in repository root
directory: '/'
schedule:
interval: 'daily'
allow:
- dependency-name: '@uniswap/default-token-list'
- dependency-name: '@uniswap/token-lists'
reviewers:
- 'Uniswap/dependabot-reviewers'

@ -1,52 +0,0 @@
<!-- Your PR title must follow conventional commits: https://github.com/Uniswap/interface#pr-title -->
## Description
<!-- Summary of change, including motivation and context. -->
<!-- Use verb-driven language: "Fixes XYZ" instead of "This change fixes XYZ" -->
<!-- Delete inapplicable lines: -->
_Linear ticket:_
_Slack thread:_
_Relevant docs:_
<!-- Delete this section if your change does not affect UI. -->
## Screen capture
### Before
| Mobile | Desktop |
| ------------ | ------------ |
| paste_before | paste_before |
### After
| Mobile | Desktop |
| ------------ | ----------- |
| paste_after | paste_after |
## Test plan
<!-- Delete this section if your change is not a bug fix. -->
### Reproducing the error
<!-- Include steps to reproduce the bug. -->
1.
### QA (ie manual testing)
<!-- Include steps to test the change, ensuring no regression. -->
- [ ] N/A
#### Devices
<!-- If applicable, include different devices and screen sizes that may be affected, and how you've tested them. -->
### Automated testing
<!-- If N/A, check and note so it is obvious to your reviewers and does not show up as an incomplete task. -->
<!-- eg - [x] Unit test N/A -->
- [ ] Unit test
- [ ] Integration/E2E test

@ -1,73 +0,0 @@
name: 1 | Push main -> staging
# This CI job is responsible for pushing the current contents of the `main` branch to the
# `releases/staging` branch, which will in turn kick off a deploy to the staging environment.
on:
workflow_dispatch:
# https://stackoverflow.com/questions/57921401/push-to-origin-from-github-action
jobs:
push-staging:
name: 'Push to staging branch'
runs-on: ubuntu-latest
environment:
name: push/staging
steps:
- name: Check test status
uses: actions/github-script@v6.4.1
with:
script: |
const statuses = await github.rest.repos.listCommitStatusesForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha
})
const status = statuses.data.find(status => status.context === 'Test / promotion')?.state || 'missing'
core.info('Status: ' + status)
if (status !== 'success') {
core.setFailed('"Test / promotion" must be successful before pushing')
}
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
token: ${{ secrets.RELEASE_SERVICE_ACCESS_TOKEN }}
ref: main
# The source file must exist for the corresponding translation messages to be downloaded.
- run: touch src/locales/en-US.po
- name: Download translations
uses: crowdin/github-action@3133cc916c35590475cf6705f482fb653d8e36e9
with:
upload_sources: false
download_translations: true
project_id: 458284
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN_SECRET }}
source: 'src/locales/en-US.po'
translation: 'src/locales/%locale%.po'
localization_branch_name: main
create_pull_request: false
push_translations: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Git config
run: |
git config user.name 'UL Service Account'
git config user.email 'hello-happy-puppy@users.noreply.github.com'
- name: Add translations
run: |
rm src/locales/en-US.po
git add -f src/locales/*.po
git commit -m 'ci(t9n): download translations from crowdin'
- name: Add CODEOWNERS
run: |
echo '* @uniswap/web-admins' > CODEOWNERS
git add CODEOWNERS
git commit -m 'ci: add global CODEOWNERS'
- name: Git push
run: |
git push origin main:releases/staging --force

@ -1,64 +0,0 @@
name: 2 | Deploy staging
on:
push:
branches:
- 'releases/staging'
jobs:
deploy-to-staging:
runs-on: ubuntu-latest
environment:
name: deploy/staging
steps:
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
with:
payload: |
{
"text": "Deploy _started_ for ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn build
env:
REACT_APP_STAGING: 1
- name: Update Cloudflare Pages deployment
id: pages-deployment
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: interface-staging
directory: build
githubToken: ${{ secrets.GITHUB_TOKEN }}
# Cloudflare uses `main` as the default production branch, so we push using the `main` branch so that it can be aliased by a custom domain.
branch: main
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
if: always()
with:
payload: |
{
"text": "Deploy *${{ steps.pages-deployment.outcome }}* for ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: Upload source maps to Sentry
uses: getsentry/action-release@bd5f874fcda966ba48139b0140fb3ec0cb3aabdd
continue-on-error: true
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
with:
environment: staging
sourcemaps: './build/static/js'
url_prefix: '~/static/js'

@ -1,42 +0,0 @@
name: 3 | Push staging -> prod
# This CI job is responsible for force pushing the content of releases/staging to releases/prod. It
# is restricted to web-reviewers through virtue of the GitHub environment protection rules for the
# prod environment.
on:
workflow_dispatch:
jobs:
push-prod:
name: 'Push to prod branch'
runs-on: ubuntu-latest
environment:
name: push/prod
steps:
- name: Check test status
uses: actions/github-script@v6.4.1
with:
script: |
const statuses = await github.rest.repos.listCommitStatusesForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha
})
const status = statuses.data.find(status => status.context === 'Test / promotion')?.state || 'missing'
core.info('Status: ' + status)
if (status !== 'success') {
core.setFailed('"Test / promotion" must be successful before pushing')
}
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
token: ${{ secrets.RELEASE_SERVICE_ACCESS_TOKEN }}
ref: releases/staging
- name: Git config
run: |
git config user.name "UL Service Account"
git config user.email "hello-happy-puppy@users.noreply.github.com"
- name: Git push
run: |
git push origin releases/staging:releases/prod --force

@ -1,111 +0,0 @@
name: 4 | Deploy prod
on:
push:
branches:
- 'releases/prod'
jobs:
deploy-to-prod:
runs-on: ubuntu-latest
environment:
name: deploy/prod
steps:
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
with:
payload: |
{
"text": "Deploy _started_ for ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn build
- name: Bump and tag
id: github-tag-action
uses: mathieudutour/github-tag-action@d745f2e74aaf1ee82e747b181f7a0967978abee0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
release_branches: releases/prod
default_bump: patch
- name: Pin to IPFS
id: pinata
uses: anantaramdas/ipfs-pinata-deploy-action@39bbda1ce1fe24c69c6f57861b8038278d53688d
with:
pin-name: Uniswap ${{ steps.github-tag-action.outputs.new_tag }}
path: './build'
pinata-api-key: ${{ secrets.PINATA_API_KEY }}
pinata-secret-api-key: ${{ secrets.PINATA_API_SECRET_KEY }}
- name: Convert CIDv0 to CIDv1
id: convert-cidv0
uses: uniswap/convert-cidv0-cidv1@v1.0.0
with:
cidv0: ${{ steps.pinata.outputs.hash }}
- name: Publish release
uses: actions/create-release@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.github-tag-action.outputs.new_tag }}
release_name: Release ${{ steps.github-tag-action.outputs.new_tag }}
body: |
IPFS hash of the deployment:
- CIDv0: `${{ steps.pinata.outputs.hash }}`
- CIDv1: `${{ steps.convert-cidv0.outputs.cidv1 }}`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
You can also access the Uniswap Interface from an IPFS gateway.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.dweb.link/
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
- [ipfs://${{ steps.pinata.outputs.hash }}/](ipfs://${{ steps.pinata.outputs.hash }}/)
${{ steps.github-tag-action.outputs.changelog }}
- name: Update Cloudflare Pages deployment
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
id: pages-deployment
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
directory: build
githubToken: ${{ secrets.GITHUB_TOKEN }}
# Cloudflare uses `main` as the default production branch, so we push using the `main` branch so that it can be aliased by a custom domain.
branch: main
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
if: always()
with:
payload: |
{
"text": "Deploy *${{ steps.pages-deployment.outcome }}* for ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: Upload source maps to Sentry
uses: getsentry/action-release@4744f6a65149f441c5f396d5b0877307c0db52c7
continue-on-error: true
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
with:
environment: production
sourcemaps: './build/static/js'
url_prefix: '~/static/js'

@ -1,17 +0,0 @@
name: Check PR Title
on:
pull_request_target:
types:
- opened
- edited
- synchronize
jobs:
# Ensures that the PR title adheres to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).
conventional-commit:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v3.4.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

@ -1,26 +0,0 @@
name: Crowdin Upload
on:
push:
branches:
- main
jobs:
upload-sources:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn i18n:extract
- name: Upload Crowdin sources
uses: crowdin/github-action@3133cc916c35590475cf6705f482fb653d8e36e9
with:
upload_sources: true
download_translations: false
project_id: 458284
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN_SECRET }}
source: 'src/locales/en-US.po'
translation: 'src/locales/%locale%.po'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

@ -1,91 +0,0 @@
name: Slack notification on pushes to releases/*
# This CI job will push notifications to Slack whenever code is merged into any releases/* branch
#
# The steps of the command line kung-fu shown below are as follows:
# First we take the JSON-formatted Github context
# echo $GITHUB_CONTEXT \
# Then we parse out the specific fields we want for our messages using jq and format it into tab-separated values
# | jq '.event.commits[] | [.url, .id[0:7], .author.username, .timestamp, .message] | @tsv' \
# We need to do some cleaning on this output - specifically removing quotes and replacing newlines with something easier to split
# | sed 's/"//g' | sed 's/\\t/;/g' | sed 's/\\n/;/g' | sed 's/\\//g' \
# We then use awk to format the TSV into a Slack message
# | awk -F';' '{print "• <"$1"|"$2"> (<https://github.com/"$3"|"$3">, "$4") - "$5}' \
# We need to deal with some escaping issues with newlines so that we don't break the Slack message format
# | sed 's/$/\\n/g' | tr -d '\n' \
# Finally we have to truncate the message to 3,000 characters max, otherwise Slack will reject it
# | awk '{print substr($0,0,3000);}' \
# Then shove the bytes into a file to store them in their exact format
# > /tmp/parsed_github_context
on:
push:
branches:
- 'releases/*'
jobs:
notify-slack:
runs-on: ubuntu-latest
environment:
name: notify/releases
steps:
- name: Parse event to slug
id: parse-slug
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
# Formats the contents of the GitHub event into slugs: one line per commit, formatted for Slack.
# Explanation for each line is in the comments above.
run: |
echo $GITHUB_CONTEXT \
| jq '.event.commits[] | [.url, .id[0:7], .author.username, .timestamp, .message] | @tsv' \
| sed 's/"//g' | sed 's/\\t/;/g' | sed 's/\\n/;/g' | sed 's/\\//g' \
| awk -F';' '{print "• <"$1"|"$2"> (<https://github.com/"$3"|"$3">, "$4") - "$5}' \
| sed 's/$/\\n/g' | tr -d '\n' \
| awk '{print substr($0,0,3000);}' \
> /tmp/parsed_github_context
echo "SLACK_COMMITS=$(cat /tmp/parsed_github_context)" >> "$GITHUB_OUTPUT"
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
with:
payload: |
{
"text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}",
"blocks": [
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Code merged to <https://github.com/Uniswap/interface/tree/${{ github.ref }}|${{ github.ref_name }}> branch:*\n"
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Actor*: <https://github.com/${{ github.triggering_actor }}/|${{ github.triggering_actor }}>\n*Force pushed*: ${{ github.event.forced || false }}\n"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "${{ steps.parse-slug.outputs.SLACK_COMMITS || 'New branch created' }}"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<${{ github.event.compare}}|View Diff>"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

@ -1,278 +0,0 @@
name: Test
# Many build steps have their own caches, so each job has its own cache to improve subsequent build times.
# Build tools are configured to cache to node_modules/.cache, so they are cached independently of node_modules.
# Caches are saved every run (by keying on github.run_id), and the most recent available cache is loaded.
# See https://jongleberry.medium.com/speed-up-your-ci-and-dx-with-node-modules-cache-ac8df82b7bb0.
on:
push:
branches:
- main
- releases/staging
pull_request:
workflow_dispatch:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-eslint-${{ github.run_id }}
restore-keys: ${{ runner.os }}-eslint-
- run: yarn lint
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Lint
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-tsc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-tsc-
- run: yarn typecheck
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Typecheck
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
deps-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn yarn-deduplicate --strategy=highest --list --fail
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Dependency checks
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-jest-${{ github.run_id }}
restore-keys: ${{ runner.os }}-jest-
- run: yarn test --coverage --maxWorkers=100%
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
flags: unit-tests
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Unit tests
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.swc
key: ${{ runner.os }}-swc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-swc-
- run: yarn build
- uses: actions/upload-artifact@v3
with:
name: build
path: build
if-no-files-found: error
cypress-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-cypress-tsc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cypress-tsc-
- run: yarn typecheck:cypress
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cypress typecheck
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
# Allows for parallel re-runs of cypress tests without re-building.
cypress-rerun:
runs-on: ubuntu-latest
steps:
- run: exit 0
cypress-test-matrix:
needs: [build, cypress-rerun]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
containers: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: /root/.cache/Cypress
key: ${{ runner.os }}-cypress-${{ hashFiles('**/node_modules/cypress/package.json') }}
- run: |
yarn cypress install
yarn cypress info
- uses: actions/download-artifact@v3
with:
name: build
path: build
- uses: actions/cache/restore@v3
with:
path: cache
key: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-
- uses: cypress-io/github-action@v4
with:
install: false
record: true
parallel: true
start: yarn serve
wait-on: 'http://localhost:3000'
browser: electron
group: e2e
spec: ${{ github.ref_name == 'releases/staging' && 'cypress/{e2e,staging}/**/*.test.ts' || 'cypress/e2e/**/*.test.ts' }}
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_INFO_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message }}
COMMIT_INFO_AUTHOR: ${{ github.event.sender.login || github.event.head_commit.author.login }}
# Cypress requires an email for filtering by author, but GitHub does not expose one.
# GitHub's public profile email can be deterministically produced from user id/login.
COMMIT_INFO_EMAIL: ${{ github.event.sender.id || github.event.head_commit.author.id }}+${{ github.event.sender.login || github.event.head_commit.author.login }}@users.noreply.github.com
COMMIT_INFO_SHA: ${{ github.event.pull_request.head.sha || github.event.head_commit.sha }}
COMMIT_INFO_TIMESTAMP: ${{ github.event.pull_request.updated_at || github.event.head_commit.timestamp }}
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number }}
CYPRESS_PULL_REQUEST_URL: ${{ github.event.pull_request.html_url }}
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cypress tests
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
- uses: actions/upload-artifact@v3
with:
name: hardhat-cache
path: cache
hardhat-cache:
needs: [cypress-test-matrix]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: hardhat-cache
path: cache
- uses: actions/cache/save@v3
with:
path: cache
key: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-${{ github.run_id }}
cloud-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-cloud-tsc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cloud-tsc-
- run: yarn typecheck:cloud
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cloud typecheck
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
cloud-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-cloud-jest-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cloud-jest-
# Ignore start:cloud output so it doesn't flood the test output.
# Only use 1 worker for testing, as the other is used to run start:cloud (the proxy server under test).
- run: yarn start-server-and-test 'yarn start:cloud >/dev/null' 3000 'yarn test:cloud --coverage --maxWorkers=1'
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
flags: cloud-tests
pre:
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6.4.1
with:
script: |
github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.sha,
state: 'pending',
context: 'Test / promotion',
description: 'Running tests...',
target_url: 'https://github.com/Uniswap/interface/actions/runs/' + context.runId
})
post:
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
needs: [pre, lint, typecheck, deps-tests, unit-tests, cypress-test-matrix, cloud-tests]
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6.4.1
with:
script: |
github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.sha,
state: ${{ env.STATUS }} ? 'success' : 'failure',
context: 'Test / promotion',
description: ${{ env.STATUS }} ? 'All tests passed' : 'One or more tests failed and are blocking promotion',
target_url: 'https://github.com/Uniswap/interface/actions/runs/' + context.runId
})
env:
STATUS: |
${{ needs.lint.result == 'success' }} &&
${{ needs.typecheck.result == 'success' }} &&
${{ needs.deps-tests.result == 'success' }} &&
${{ needs.unit-tests.result == 'success' }} &&
${{ needs.cypress-test-matrix.result == 'success' }} &&
${{ needs.cloud-tests.result == 'success' }}

25
.snyk

@ -1,25 +0,0 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.25.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
SNYK-JS-OPENZEPPELINCONTRACTS-2964946:
- '*':
reason: None Given
expires: 2099-01-01T00:00:00.000Z
created: 2022-12-08T16:25:57.347Z
SNYK-JS-OPENZEPPELINCONTRACTS-2958047:
- '*':
reason: None Given
expires: 2099-01-01T00:00:00.000Z
created: 2022-12-08T16:26:09.720Z
SNYK-JS-OPENZEPPELINCONTRACTS-2958050:
- '*':
reason: None Given
expires: 2099-01-01T00:00:00.000Z
created: 2022-12-08T16:26:17.702Z
SNYK-JS-OPENZEPPELINCONTRACTS-2965580:
- '*':
reason: None Given
expires: 2099-01-01T00:00:00.000Z
created: 2022-12-08T16:26:34.283Z
patch: {}

@ -6,7 +6,7 @@ export default defineConfig({
defaultCommandTimeout: 24000, // 2x average block time
chromeWebSecurity: false,
experimentalMemoryManagement: true, // better memory management, see https://github.com/cypress-io/cypress/pull/25462
retries: { runMode: process.env.CYPRESS_RETRIES ? +process.env.CYPRESS_RETRIES : 2 },
retries: { runMode: process.env.CYPRESS_RETRIES ? +process.env.CYPRESS_RETRIES : 1 },
video: false, // GH provides 2 CPUs, and cypress video eats one up, see https://github.com/cypress-io/cypress/issues/20468#issuecomment-1307608025
e2e: {
async setupNodeEvents(on, config) {

@ -16,6 +16,16 @@ describe('Add Liquidity', () => {
cy.contains('0.05% fee tier')
})
it('clears the token selection when chain changes', () => {
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/ETH/500')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'UNI')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH')
cy.get('[data-testid="chain-selector"]').last().click()
cy.contains('Polygon').click()
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('not.contain.text', 'UNI')
})
it('does not crash if token is duplicated', () => {
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'UNI')
@ -52,7 +62,7 @@ describe('Add Liquidity', () => {
cy.visit('/add/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/ETH')
cy.wait('@FeeTierDistribution')
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.3% fee tier')
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.30% fee tier')
cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '40% select')
})
})

@ -40,6 +40,26 @@ describe('Landing Page', () => {
cy.url().should('include', '/pools')
})
it('does not render landing page when / path is blocked', () => {
cy.intercept('/', (req) => {
req.reply((res) => {
const parser = new DOMParser()
const doc = parser.parseFromString(res.body, 'text/html')
const meta = document.createElement('meta')
meta.setAttribute('property', 'x:blocked-paths')
meta.setAttribute('content', '/,/buy')
doc.head.appendChild(meta)
res.body = doc.documentElement.outerHTML
})
})
cy.visit('/', { userState: DISCONNECTED_WALLET_USER_STATE })
cy.get(getTestSelector('landing-page')).should('not.exist')
cy.get(getTestSelector('buy-fiat-button')).should('not.exist')
cy.url().should('include', '/swap')
})
it('does not render uk compliance banner in US', () => {
cy.visit('/swap')
cy.contains('UK disclaimer').should('not.exist')

@ -59,9 +59,10 @@ describe('Mini Portfolio account drawer', () => {
cy.get(getTestSelector('mini-portfolio-navbar')).contains('NFTs').click()
cy.get(getTestSelector('mini-portfolio-page')).contains('I Got Plenty')
cy.intercept(/graphql/, { fixture: 'mini-portfolio/pools.json' })
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Pools').click()
cy.get(getTestSelector('mini-portfolio-page')).contains('No pools yet')
// Skip this for now, someone sent test account an NFT on block 17445713 that causes this test to fail
// cy.intercept(/graphql/, { fixture: 'mini-portfolio/pools.json' })
// cy.get(getTestSelector('mini-portfolio-navbar')).contains('Pools').click()
// cy.get(getTestSelector('mini-portfolio-page')).contains('No pools yet')
cy.intercept(/graphql/, { fixture: 'mini-portfolio/full_activity.json' })
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click()

@ -1,4 +1,5 @@
import { BigNumber } from '@ethersproject/bignumber'
import { InterfaceSectionName } from '@uniswap/analytics-events'
import { CurrencyAmount } from '@uniswap/sdk-core'
import { DEFAULT_DEADLINE_FROM_NOW } from '../../../src/constants/misc'
@ -86,6 +87,7 @@ describe('Swap errors', () => {
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.get(getTestSelector('max-slippage-settings')).click()
cy.get(getTestSelector('slippage-input')).clear().type('0.01')
cy.get(getTestSelector('toggle-uniswap-x-button')).click() // turn off uniswapx
cy.get('body').click('topRight') // close modal
cy.get(getTestSelector('slippage-input')).should('not.exist')
@ -116,4 +118,18 @@ describe('Swap errors', () => {
getBalance(DAI).should('be.closeTo', initialBalance + 200, 1)
})
})
it('insufficient liquidity', () => {
// The API response is too variable so stubbing a 404.
cy.intercept('POST', 'https://api.uniswap.org/v2/quote', {
statusCode: 404,
fixture: 'insufficientLiquidity.json',
})
cy.visit(`/swap?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
cy.get('#swap-currency-output .token-amount-input').type('100000000000000').should('have.value', '100000000000000') // 100 trillion
cy.contains('Insufficient liquidity for this trade.')
cy.get('#swap-button').should('not.exist')
cy.get(getTestSelector(`fiat-value-${InterfaceSectionName.CURRENCY_OUTPUT_PANEL}`)).contains('-')
})
})

@ -0,0 +1,146 @@
import { CurrencyAmount } from '@uniswap/sdk-core'
import { FeatureFlag } from 'featureFlags'
import { USDC_MAINNET } from '../../../src/constants/tokens'
import { getBalance, getTestSelector } from '../../utils'
describe.skip('Swap with fees', () => {
describe('Classic swaps', () => {
beforeEach(() => {
cy.visit('/swap', { featureFlags: [{ name: FeatureFlag.feesEnabled, value: true }] })
// Store trade quote into alias
cy.intercept({ url: 'https://api.uniswap.org/v2/quote' }, (req) => {
// Avoid tracking stablecoin pricing fetches
if (JSON.parse(req.body).intent !== 'pricing') req.alias = 'quoteFetch'
})
})
it('displays $0 fee on swaps without fees', () => {
// Set up a stablecoin <> stablecoin swap (no fees)
cy.get('#swap-currency-input .open-currency-select-button').click()
cy.contains('DAI').click()
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.contains('USDC').click()
cy.get('#swap-currency-output .token-amount-input').type('1')
// Verify 0 fee UI is displayed
cy.get(getTestSelector('swap-details-header-row')).click()
cy.contains('Fee')
cy.contains('$0')
})
it('swaps ETH for USDC exact-out with swap fee', () => {
cy.hardhat().then((hardhat) => {
getBalance(USDC_MAINNET).then((initialBalance) => {
// Set up swap
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.contains('USDC').click()
cy.get('#swap-currency-output .token-amount-input').type('1')
cy.wait('@quoteFetch')
.its('response.body')
.then(({ quote: { portionBips, portionRecipient, portionAmount } }) => {
// Fees are generally expected to always be enabled for ETH -> USDC swaps
// If the routing api does not include a fee, end the test early rather than manually update routes and hardcode fee vars
if (portionRecipient) return
cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((initialRecipientBalance) => {
const feeCurrencyAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, portionAmount)
// Initiate transaction
cy.get('#swap-button').click()
cy.contains('Review swap')
// Verify fee percentage and amount is displayed
cy.contains(`Fee (${portionBips / 100}%)`)
// Confirm transaction
cy.contains('Confirm swap').click()
// Verify transaction
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
cy.get(getTestSelector('popups')).contains('Swapped')
// Verify the post-fee output is the expected exact-out amount
const finalBalance = initialBalance + 1
cy.get('#swap-currency-output').contains(`Balance: ${finalBalance}`)
getBalance(USDC_MAINNET).should('eq', finalBalance)
// Verify fee recipient received fee
cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((finalRecipientBalance) => {
const expectedFinalRecipientBalance = initialRecipientBalance.add(feeCurrencyAmount)
cy.then(() => finalRecipientBalance.equalTo(expectedFinalRecipientBalance)).should('be.true')
})
})
})
})
})
})
it('swaps ETH for USDC exact-in with swap fee', () => {
cy.hardhat().then((hardhat) => {
// Set up swap
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.contains('USDC').click()
cy.get('#swap-currency-input .token-amount-input').type('.01')
cy.wait('@quoteFetch')
.its('response.body')
.then(({ quote: { portionBips, portionRecipient } }) => {
// Fees are generally expected to always be enabled for ETH -> USDC swaps
// If the routing api does not include a fee, end the test early rather than manually update routes and hardcode fee vars
if (portionRecipient) return
cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((initialRecipientBalance) => {
// Initiate transaction
cy.get('#swap-button').click()
cy.contains('Review swap')
// Verify fee percentage and amount is displayed
cy.contains(`Fee (${portionBips / 100}%)`)
// Confirm transaction
cy.contains('Confirm swap').click()
// Verify transaction
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
cy.get(getTestSelector('popups')).contains('Swapped')
// Verify fee recipient received fee
cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((finalRecipientBalance) => {
cy.then(() => finalRecipientBalance.greaterThan(initialRecipientBalance)).should('be.true')
})
})
})
})
})
})
describe('UniswapX swaps', () => {
it('displays UniswapX fee in UI', () => {
cy.visit('/swap', {
featureFlags: [{ name: FeatureFlag.feesEnabled, value: true }],
})
// Intercept the trade quote
cy.intercept({ url: 'https://api.uniswap.org/v2/quote' }, (req) => {
// Avoid intercepting stablecoin pricing fetches
if (JSON.parse(req.body).intent !== 'pricing') {
req.reply({ fixture: 'uniswapx/feeQuote.json' })
}
})
// Setup swap
cy.get('#swap-currency-input .open-currency-select-button').click()
cy.contains('USDC').click()
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.contains('ETH').click()
cy.get('#swap-currency-input .token-amount-input').type('200')
// Verify fee UI is displayed
cy.get(getTestSelector('swap-details-header-row')).click()
cy.contains('Fee (0.15%)')
})
})
})

@ -1,18 +1,35 @@
import { ChainId, CurrencyAmount } from '@uniswap/sdk-core'
import { FeatureFlag } from 'featureFlags'
import { CyHttpMessages } from 'cypress/types/net-stubbing'
import { DAI, nativeOnChain, USDC_MAINNET } from '../../../src/constants/tokens'
import { getTestSelector } from '../../utils'
const QuoteEndpoint = 'https://api.uniswap.org/v2/quote'
const QuoteWhereUniswapXIsBetter = 'uniswapx/quote1.json'
const QuoteWithEthInput = 'uniswapx/quote2.json'
const QuoteEndpoint = 'https://api.uniswap.org/v2/quote'
const OrderSubmissionEndpoint = 'https://api.uniswap.org/v2/order'
const OrderStatusEndpoint =
'https://api.uniswap.org/v2/orders?swapper=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266&orderHashes=0xa9dd6f05ad6d6c79bee654c31ede4d0d2392862711be0f3bc4a9124af24a6a19'
/**
* Stubs quote to return a quote for non-price requests
* Price quotes are blocked with 409, as the backend would not accept them regardless
*/
function stubNonPriceQuoteWith(fixture: string) {
cy.intercept(QuoteEndpoint, (req: CyHttpMessages.IncomingHttpRequest) => {
let body = req.body
if (typeof body === 'string') {
body = JSON.parse(body)
}
if (body.intent === 'pricing') {
req.reply({ statusCode: 409 })
} else {
req.reply({ fixture })
}
}).as('quote')
}
/** Stubs the provider to return a tx receipt corresponding to the mock filled uniswapx order's txHash */
function stubSwapTxReceipt() {
cy.hardhat().then((hardhat) => {
@ -24,70 +41,39 @@ function stubSwapTxReceipt() {
})
}
describe('UniswapX Toggle', () => {
// TODO: FIX THESE TESTS where we should NOT stub for pricing requests
describe.skip('UniswapX Toggle', () => {
beforeEach(() => {
cy.intercept(QuoteEndpoint, { fixture: QuoteWhereUniswapXIsBetter })
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
})
stubNonPriceQuoteWith(QuoteWhereUniswapXIsBetter)
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
})
it('only displays uniswapx ui when setting is on', () => {
it('displays uniswapx ui when setting is on', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
// UniswapX UI should not be visible
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('not.exist')
// Opt-in to UniswapX
cy.contains('Try it now').click()
cy.wait('@quote')
// UniswapX UI should be visible
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('exist')
})
it('prompts opt-in if UniswapX is better', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
// UniswapX should not display in gas estimate row before opt-in
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('not.exist')
// UniswapX mustache should be visible
cy.contains('Try it now').click()
// Opt-in dialog should now be hidden
cy.contains('Try it now').should('not.be.visible')
// UniswapX should display in gas estimate row
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('exist')
// Opt-in dialog should not reappear if user manually toggles UniswapX off
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.get(getTestSelector('toggle-uniswap-x-button')).click()
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.contains('Try it now').should('not.be.visible')
})
})
describe('UniswapX Orders', () => {
describe.skip('UniswapX Orders', () => {
beforeEach(() => {
cy.intercept(QuoteEndpoint, { fixture: QuoteWhereUniswapXIsBetter })
stubNonPriceQuoteWith(QuoteWhereUniswapXIsBetter)
cy.intercept(OrderSubmissionEndpoint, { fixture: 'uniswapx/orderResponse.json' })
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/openStatusResponse.json' })
stubSwapTxReceipt()
cy.hardhat().then((hardhat) => hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8)))
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
})
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
})
it('can swap exact-in trades using uniswapX', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
cy.wait('@quote')
// Submit uniswapx order signature
cy.get('#swap-button').click()
@ -106,7 +92,7 @@ describe('UniswapX Orders', () => {
it('can swap exact-out trades using uniswapX', () => {
// Setup a swap
cy.get('#swap-currency-output .token-amount-input').type('300')
cy.contains('Try it now').click()
cy.wait('@quote')
// Submit uniswapx order signature
cy.get('#swap-button').click()
@ -125,7 +111,7 @@ describe('UniswapX Orders', () => {
it('renders proper view if uniswapx order expires', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
cy.wait('@quote')
// Submit uniswapx order signature
cy.get('#swap-button').click()
@ -141,7 +127,7 @@ describe('UniswapX Orders', () => {
it('renders proper view if uniswapx order has insufficient funds', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
cy.wait('@quote')
// Submit uniswapx order signature
cy.get('#swap-button').click()
@ -155,9 +141,9 @@ describe('UniswapX Orders', () => {
})
})
describe('UniswapX Eth Input', () => {
describe.skip('UniswapX Eth Input', () => {
beforeEach(() => {
cy.intercept(QuoteEndpoint, { fixture: QuoteWithEthInput })
stubNonPriceQuoteWith(QuoteWithEthInput)
cy.intercept(OrderSubmissionEndpoint, { fixture: 'uniswapx/orderResponse.json' })
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/openStatusResponse.json' })
@ -169,15 +155,14 @@ describe('UniswapX Eth Input', () => {
stubSwapTxReceipt()
cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`, {
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
})
cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`)
})
it('can swap using uniswapX with ETH as input', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('1')
cy.contains('Try it now').click()
cy.wait('@quote')
// Prompt ETH wrap to use for order
cy.get('#swap-button').click()
@ -206,10 +191,10 @@ describe('UniswapX Eth Input', () => {
cy.contains('Swapped')
})
it('switches swap input to WETH after wrap', () => {
it('keeps ETH as the input currency before wrap completes', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('1')
cy.contains('Try it now').click()
cy.wait('@quote')
// Prompt ETH wrap and confirm
cy.get('#swap-button').click()
@ -218,16 +203,25 @@ describe('UniswapX Eth Input', () => {
// Close review modal before wrap is confirmed on chain
cy.get(getTestSelector('confirmation-close-icon')).click()
// Confirm ETH is still the input token before wrap succeeds
cy.contains('ETH')
})
it('switches swap input to WETH after wrap', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('1')
cy.wait('@quote')
// Prompt ETH wrap and confirm
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
// Confirm wrap is successful and WETH is now input token
cy.contains('Wrapped')
cy.contains('WETH')
// Reopen review modal and continue swap
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
// Approve WETH spend
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
@ -242,10 +236,15 @@ describe('UniswapX Eth Input', () => {
// Verify swap success
cy.contains('Swapped')
// Close modal
cy.get(getTestSelector('confirmation-close-icon')).click()
// The input currency should now be WETH
cy.contains('WETH')
})
})
describe('UniswapX activity history', () => {
describe.skip('UniswapX activity history', () => {
beforeEach(() => {
cy.intercept(QuoteEndpoint, { fixture: QuoteWhereUniswapXIsBetter })
cy.intercept(OrderSubmissionEndpoint, { fixture: 'uniswapx/orderResponse.json' })
@ -256,15 +255,12 @@ describe('UniswapX activity history', () => {
cy.hardhat().then(async (hardhat) => {
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8))
})
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }],
})
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
})
it('can view UniswapX order status progress in activity', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
// Submit uniswapx order signature
cy.get('#swap-button').click()
@ -292,7 +288,6 @@ describe('UniswapX activity history', () => {
it('can view UniswapX order status progress in activity upon expiry', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
// Submit uniswapx order signature
cy.get('#swap-button').click()
@ -319,7 +314,6 @@ describe('UniswapX activity history', () => {
it('deduplicates remote vs local uniswapx orders', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
// Submit uniswapx order signature
cy.get('#swap-button').click()
@ -344,14 +338,13 @@ describe('UniswapX activity history', () => {
// Open activity history
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click()
// Ensure gql and local order have been deduped, such that there is only one swap activity listed
// Ensure gql and local order have been deduped, such that there is one swap activity listed
cy.get(getTestSelector('activity-content')).contains('Swapped').should('have.length', 1)
})
it('balances should refetch after uniswapx swap', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
const gqlSpy = cy.spy().as('gqlSpy')
cy.intercept(/graphql/, (req) => {

@ -1,4 +1,5 @@
import { ChainId, WETH9 } from '@uniswap/sdk-core'
import { FeatureFlag } from 'featureFlags'
import { ARB, UNI } from '../../src/constants/tokens'
import { getTestSelector } from '../utils'
@ -14,8 +15,9 @@ describe('Token details', () => {
it('Uniswap token should have all information populated', () => {
// Uniswap token
cy.visit(`/tokens/ethereum/${UNI_ADDRESS}`)
cy.visit(`/tokens/ethereum/${UNI_ADDRESS}`, {
featureFlags: [{ name: FeatureFlag.infoTDP, value: false }],
})
// Price chart should be filled in
cy.get('[data-cy="chart-header"]').should('include.text', '$')
cy.get('[data-cy="price-chart"]').should('exist')
@ -47,6 +49,22 @@ describe('Token details', () => {
cy.contains(UNI_ADDRESS).should('exist')
})
it('Uniswap token should have correct stats boxes if infoTDP flag on', () => {
// Uniswap token
cy.visit(`/tokens/ethereum/${UNI_ADDRESS}`, {
featureFlags: [{ name: FeatureFlag.infoTDP, value: true }],
})
// Stats should have: TVL, FDV, market cap, 24H volume
cy.get(getTestSelector('token-details-stats')).should('exist')
cy.get(getTestSelector('token-details-stats')).within(() => {
cy.get('[data-cy="tvl"]').should('include.text', '$')
cy.get('[data-cy="fdv"]').should('include.text', '$')
cy.get('[data-cy="market-cap"]').should('include.text', '$')
cy.get('[data-cy="volume-24h"]').should('include.text', '$')
})
})
it('token with warning and low trading volume should have all information populated', () => {
// Null token created for this test, 0 trading volume and has warning modal
cy.visit('/tokens/ethereum/0x1eFBB78C8b917f67986BcE54cE575069c0143681')

@ -59,9 +59,7 @@ describe('Token explore', () => {
// in metamask modal using plain cypress. this is a workaround.
cy.visit('/tokens/polygon')
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Polygon')
cy.get(getTestSelector('token-table-row-NATIVE'))
.find(getTestSelector('name-cell'))
.should('include.text', 'Polygon Matic')
cy.get(getTestSelector('token-table-row-NATIVE')).find(getTestSelector('name-cell')).should('include.text', 'Matic')
})
it('should update when token explore table network changed', () => {

@ -49,6 +49,34 @@ describe('Wallet Dropdown', () => {
})
itChangesTheme()
itChangesLocale()
it('should not show buy crypto button in uk', () => {
cy.document().then((doc) => {
const meta = document.createElement('meta')
meta.setAttribute('property', 'x:blocked-paths')
meta.setAttribute('content', '/,/nfts,/buy')
doc.head.appendChild(meta)
})
cy.get(getTestSelector('wallet-buy-crypto')).should('not.exist')
})
})
describe('do not render buy button when /buy is blocked', () => {
beforeEach(() => {
cy.document().then((doc) => {
const meta = document.createElement('meta')
meta.setAttribute('property', 'x:blocked-paths')
meta.setAttribute('content', '/buy')
doc.head.appendChild(meta)
})
cy.visit('/')
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
})
it('should not render buy button', () => {
cy.get(getTestSelector('wallet-buy-crypto')).should('not.exist')
})
})
describe('should change locale with feature flag', () => {

@ -0,0 +1,5 @@
{
"errorCode": "QUOTE_ERROR",
"detail": "No quotes available",
"id": "63363cc1-d474-4584-b386-7c356814b79f"
}

@ -0,0 +1,562 @@
{
"routing": "DUTCH_LIMIT",
"quote": {
"orderInfo": {
"chainId": 1,
"permit2Address": "0x000000000022d473030f116ddee9f6b43ac78ba3",
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F",
"nonce": "1993353164669688581970088190602701610528397285201889446578254799128576197633",
"deadline": 1697481666,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x",
"decayStartTime": 1697481594,
"decayEndTime": 1697481654,
"exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb",
"exclusivityOverrideBps": "100",
"input": {
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"startAmount": "200000000",
"endAmount": "200000000"
},
"outputs": [
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": "123803169993201727",
"endAmount": "117908377342236273",
"recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F"
},
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": "185983730585681",
"endAmount": "177128258400955",
"recipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
}
]
},
"encodedOrder": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000652d837a00000000000000000000000000000000000000000000000000000000652d83b6000000000000000000000000aafb85ad4a412dd8adc49611496a7695a22f4aeb0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000bebc200000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000000000000000002000000000000000000000000006000da47483062a0d734ba3dc7576ce6a0b645c40000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f046832aa305880d33daa871e5041a0cd4853599a9ead518917239e206765040100000000000000000000000000000000000000000000000000000000652d83c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b7d653c183183f00000000000000000000000000000000000000000000000001a2e50b6386d6710000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a926b63210510000000000000000000000000000000000000000000000000000a118e2ebf2bb00000000000000000000000037a8f295612602f2774d331e562be9e61b83a327",
"quoteId": "7b924043-f2d8-4f2e-abaa-9f65fbe5f890",
"requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f",
"orderHash": "0xb5b4e3be188f6eb9dbe7e1489595829184a9ebfb5389185ed7ba7c03142278c9",
"startTimeBufferSecs": 45,
"auctionPeriodSecs": 60,
"deadlineBufferSecs": 12,
"slippageTolerance": "0.5",
"permitData": {
"domain": {
"name": "Permit2",
"chainId": 1,
"verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
},
"types": {
"PermitWitnessTransferFrom": [
{
"name": "permitted",
"type": "TokenPermissions"
},
{
"name": "spender",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "witness",
"type": "ExclusiveDutchOrder"
}
],
"TokenPermissions": [
{
"name": "token",
"type": "address"
},
{
"name": "amount",
"type": "uint256"
}
],
"ExclusiveDutchOrder": [
{
"name": "info",
"type": "OrderInfo"
},
{
"name": "decayStartTime",
"type": "uint256"
},
{
"name": "decayEndTime",
"type": "uint256"
},
{
"name": "exclusiveFiller",
"type": "address"
},
{
"name": "exclusivityOverrideBps",
"type": "uint256"
},
{
"name": "inputToken",
"type": "address"
},
{
"name": "inputStartAmount",
"type": "uint256"
},
{
"name": "inputEndAmount",
"type": "uint256"
},
{
"name": "outputs",
"type": "DutchOutput[]"
}
],
"OrderInfo": [
{
"name": "reactor",
"type": "address"
},
{
"name": "swapper",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "additionalValidationContract",
"type": "address"
},
{
"name": "additionalValidationData",
"type": "bytes"
}
],
"DutchOutput": [
{
"name": "token",
"type": "address"
},
{
"name": "startAmount",
"type": "uint256"
},
{
"name": "endAmount",
"type": "uint256"
},
{
"name": "recipient",
"type": "address"
}
]
},
"values": {
"permitted": {
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"amount": {
"type": "BigNumber",
"hex": "0x0bebc200"
}
},
"spender": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"nonce": {
"type": "BigNumber",
"hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401"
},
"deadline": 1697481666,
"witness": {
"info": {
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F",
"nonce": {
"type": "BigNumber",
"hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401"
},
"deadline": 1697481666,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x"
},
"decayStartTime": 1697481594,
"decayEndTime": 1697481654,
"exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb",
"exclusivityOverrideBps": {
"type": "BigNumber",
"hex": "0x64"
},
"inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"inputStartAmount": {
"type": "BigNumber",
"hex": "0x0bebc200"
},
"inputEndAmount": {
"type": "BigNumber",
"hex": "0x0bebc200"
},
"outputs": [
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": {
"type": "BigNumber",
"hex": "0x01b7d653c183183f"
},
"endAmount": {
"type": "BigNumber",
"hex": "0x01a2e50b6386d671"
},
"recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F"
},
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": {
"type": "BigNumber",
"hex": "0xa926b6321051"
},
"endAmount": {
"type": "BigNumber",
"hex": "0xa118e2ebf2bb"
},
"recipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
}
]
}
}
},
"portionBips": 15,
"portionAmount": "185983730585681",
"portionRecipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
},
"requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f",
"allQuotes": [
{
"routing": "DUTCH_LIMIT",
"quote": {
"orderInfo": {
"chainId": 1,
"permit2Address": "0x000000000022d473030f116ddee9f6b43ac78ba3",
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F",
"nonce": "1993353164669688581970088190602701610528397285201889446578254799128576197633",
"deadline": 1697481666,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x",
"decayStartTime": 1697481594,
"decayEndTime": 1697481654,
"exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb",
"exclusivityOverrideBps": "100",
"input": {
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"startAmount": "200000000",
"endAmount": "200000000"
},
"outputs": [
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": "123803169993201727",
"endAmount": "117908377342236273",
"recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F"
},
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": "185983730585681",
"endAmount": "177128258400955",
"recipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
}
]
},
"encodedOrder": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000652d837a00000000000000000000000000000000000000000000000000000000652d83b6000000000000000000000000aafb85ad4a412dd8adc49611496a7695a22f4aeb0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000bebc200000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000000000000000002000000000000000000000000006000da47483062a0d734ba3dc7576ce6a0b645c40000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f046832aa305880d33daa871e5041a0cd4853599a9ead518917239e206765040100000000000000000000000000000000000000000000000000000000652d83c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b7d653c183183f00000000000000000000000000000000000000000000000001a2e50b6386d6710000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a926b63210510000000000000000000000000000000000000000000000000000a118e2ebf2bb00000000000000000000000037a8f295612602f2774d331e562be9e61b83a327",
"quoteId": "7b924043-f2d8-4f2e-abaa-9f65fbe5f890",
"requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f",
"orderHash": "0xb5b4e3be188f6eb9dbe7e1489595829184a9ebfb5389185ed7ba7c03142278c9",
"startTimeBufferSecs": 45,
"auctionPeriodSecs": 60,
"deadlineBufferSecs": 12,
"slippageTolerance": "0.5",
"permitData": {
"domain": {
"name": "Permit2",
"chainId": 1,
"verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
},
"types": {
"PermitWitnessTransferFrom": [
{
"name": "permitted",
"type": "TokenPermissions"
},
{
"name": "spender",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "witness",
"type": "ExclusiveDutchOrder"
}
],
"TokenPermissions": [
{
"name": "token",
"type": "address"
},
{
"name": "amount",
"type": "uint256"
}
],
"ExclusiveDutchOrder": [
{
"name": "info",
"type": "OrderInfo"
},
{
"name": "decayStartTime",
"type": "uint256"
},
{
"name": "decayEndTime",
"type": "uint256"
},
{
"name": "exclusiveFiller",
"type": "address"
},
{
"name": "exclusivityOverrideBps",
"type": "uint256"
},
{
"name": "inputToken",
"type": "address"
},
{
"name": "inputStartAmount",
"type": "uint256"
},
{
"name": "inputEndAmount",
"type": "uint256"
},
{
"name": "outputs",
"type": "DutchOutput[]"
}
],
"OrderInfo": [
{
"name": "reactor",
"type": "address"
},
{
"name": "swapper",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "additionalValidationContract",
"type": "address"
},
{
"name": "additionalValidationData",
"type": "bytes"
}
],
"DutchOutput": [
{
"name": "token",
"type": "address"
},
{
"name": "startAmount",
"type": "uint256"
},
{
"name": "endAmount",
"type": "uint256"
},
{
"name": "recipient",
"type": "address"
}
]
},
"values": {
"permitted": {
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"amount": {
"type": "BigNumber",
"hex": "0x0bebc200"
}
},
"spender": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"nonce": {
"type": "BigNumber",
"hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401"
},
"deadline": 1697481666,
"witness": {
"info": {
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F",
"nonce": {
"type": "BigNumber",
"hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401"
},
"deadline": 1697481666,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x"
},
"decayStartTime": 1697481594,
"decayEndTime": 1697481654,
"exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb",
"exclusivityOverrideBps": {
"type": "BigNumber",
"hex": "0x64"
},
"inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"inputStartAmount": {
"type": "BigNumber",
"hex": "0x0bebc200"
},
"inputEndAmount": {
"type": "BigNumber",
"hex": "0x0bebc200"
},
"outputs": [
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": {
"type": "BigNumber",
"hex": "0x01b7d653c183183f"
},
"endAmount": {
"type": "BigNumber",
"hex": "0x01a2e50b6386d671"
},
"recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F"
},
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": {
"type": "BigNumber",
"hex": "0xa926b6321051"
},
"endAmount": {
"type": "BigNumber",
"hex": "0xa118e2ebf2bb"
},
"recipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
}
]
}
}
},
"portionBips": 15,
"portionAmount": "185983730585681",
"portionRecipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
}
},
{
"routing": "CLASSIC",
"quote": {
"methodParameters": {
"calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000652d85d0000000000000000000000000000000000000000000000000000000000000000308060c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000001bdf1285753b47400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000037a8f295612602f2774d331e562be9e61b83a327000000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f00000000000000000000000000000000000000000000000001bd45ea74e458eb",
"value": "0x00",
"to": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD"
},
"blockNumber": "18364784",
"amount": "200000000",
"amountDecimals": "200",
"quote": "126149127803342909",
"quoteDecimals": "0.126149127803342909",
"quoteGasAdjusted": "122888348391508943",
"quoteGasAdjustedDecimals": "0.122888348391508943",
"quoteGasAndPortionAdjusted": "122699124699803928",
"quoteGasAndPortionAdjustedDecimals": "0.122699124699803928",
"gasUseEstimateQuote": "3260779411833966",
"gasUseEstimateQuoteDecimals": "0.003260779411833966",
"gasUseEstimate": "240911",
"gasUseEstimateUSD": "5.153332510477604328",
"simulationStatus": "SUCCESS",
"simulationError": false,
"gasPriceWei": "13535203506",
"route": [
[
{
"type": "v2-pool",
"address": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc",
"tokenIn": {
"chainId": 1,
"decimals": "6",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC"
},
"tokenOut": {
"chainId": 1,
"decimals": "18",
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"symbol": "WETH"
},
"reserve0": {
"token": {
"chainId": 1,
"decimals": "6",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC"
},
"quotient": "27487668611269"
},
"reserve1": {
"token": {
"chainId": 1,
"decimals": "18",
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"symbol": "WETH"
},
"quotient": "17390022942803382004255"
},
"amountIn": "200000000",
"amountOut": "125959904111637894"
}
]
],
"routeString": "[V2] 100.00% = USDC -- [0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc] --> WETH",
"quoteId": "f46cf31c-251e-470c-bd57-13209015694e",
"portionBips": 15,
"portionRecipient": "0x37a8f295612602f2774d331e562be9e61B83a327",
"portionAmount": "189223691705014",
"portionAmountDecimals": "0.000189223691705014",
"requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f",
"tradeType": "EXACT_INPUT",
"slippage": 0.5
}
}
]
}

@ -74,18 +74,14 @@ Cypress.Commands.overwrite(
}
)
Cypress.Commands.add('waitForAmplitudeEvent', (eventName, timeout = 5000 /* 5s */) => {
const startTime = new Date().getTime()
Cypress.Commands.add('waitForAmplitudeEvent', (eventName) => {
function checkRequest() {
return cy.wait('@amplitude', { timeout }).then((interception) => {
return cy.wait('@amplitude').then((interception) => {
const events = interception.request.body.events
const event = events.find((event: any) => event.event_type === eventName)
if (event) {
return cy.wrap(event)
} else if (new Date().getTime() - startTime > timeout) {
throw new Error(`Event ${eventName} not found within the specified timeout`)
} else {
return checkRequest()
}

@ -34,6 +34,9 @@ beforeEach(() => {
)
}).intercept('https://*.sentry.io', { statusCode: 200 })
// Mock statsig to allow us to mock flags.
cy.intercept(/statsig/, { statusCode: 409 })
// Mock our own token list responses to avoid the latency of IPFS.
cy.intercept('https://gateway.ipfs.io/ipns/tokens.uniswap.org', TokenListJSON)
.intercept('https://gateway.ipfs.io/ipns/extendedtokens.uniswap.org', { statusCode: 404 })

@ -5,7 +5,7 @@
"incremental": true,
"isolatedModules": false,
"noImplicitAny": false,
"target": "ES5",
"target": "ES6",
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/cypress", // avoid clobbering the build tsbuildinfo
"types": ["cypress", "node"],
},

@ -7,19 +7,19 @@ const collectionImageUrls = [
'http://127.0.0.1:3000/api/image/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b',
]
const nonexistentImageUrls = [
'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545',
]
test.each([...collectionImageUrls, ...nonexistentImageUrls])('collectionImageUrl', async (url) => {
test.each([...collectionImageUrls])('collectionImageUrl', async (url) => {
const response = await fetch(new Request(url))
expect(response.status).toBe(200)
expect(response.headers.get('content-type')).toBe('image/png')
})
const nonexistentImageUrls = [
'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545',
]
const invalidCollectionImageUrls = ['http://127.0.0.1:3000/api/image/nfts/collection/0xd3adb33f']
test.each(invalidCollectionImageUrls)('invalidAssetImageUrl', async (url) => {
test.each([...invalidCollectionImageUrls, ...nonexistentImageUrls])('invalidAssetImageUrl', async (url) => {
const response = await fetch(new Request(url))
expect(response.status).toBeOneOf([404, 500])
})

@ -41,8 +41,9 @@ exports[`should inject metadata for valid assets 1`] = `
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@ -58,9 +59,9 @@ exports[`should inject metadata for valid assets 1`] = `
font-weight: 535;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Medium.woff) format('woff');
url('/fonts/Basel-Medium.woff2') format('woff2'),
url('/fonts/Basel-Medium.woff') format('woff');
}
@font-face {
@ -68,15 +69,14 @@ exports[`should inject metadata for valid assets 1`] = `
font-weight: 485;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Book.woff) format('woff');
url('/fonts/Basel-Book.woff') format('woff2'),
url('/fonts/Basel-Book.woff') format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}
@ -189,8 +189,9 @@ exports[`should inject metadata for valid assets 2`] = `
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@ -206,9 +207,9 @@ exports[`should inject metadata for valid assets 2`] = `
font-weight: 535;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Medium.woff) format('woff');
url('/fonts/Basel-Medium.woff2') format('woff2'),
url('/fonts/Basel-Medium.woff') format('woff');
}
@font-face {
@ -216,15 +217,14 @@ exports[`should inject metadata for valid assets 2`] = `
font-weight: 485;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Book.woff) format('woff');
url('/fonts/Basel-Book.woff') format('woff2'),
url('/fonts/Basel-Book.woff') format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}
@ -337,8 +337,9 @@ exports[`should inject metadata for valid assets 3`] = `
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@ -354,9 +355,9 @@ exports[`should inject metadata for valid assets 3`] = `
font-weight: 535;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Medium.woff) format('woff');
url('/fonts/Basel-Medium.woff2') format('woff2'),
url('/fonts/Basel-Medium.woff') format('woff');
}
@font-face {
@ -364,15 +365,14 @@ exports[`should inject metadata for valid assets 3`] = `
font-weight: 485;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Book.woff) format('woff');
url('/fonts/Basel-Book.woff') format('woff2'),
url('/fonts/Basel-Book.woff') format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}

@ -41,8 +41,9 @@ exports[`should inject metadata for collections 1`] = `
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@ -58,9 +59,9 @@ exports[`should inject metadata for collections 1`] = `
font-weight: 535;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Medium.woff) format('woff');
url('/fonts/Basel-Medium.woff2') format('woff2'),
url('/fonts/Basel-Medium.woff') format('woff');
}
@font-face {
@ -68,15 +69,14 @@ exports[`should inject metadata for collections 1`] = `
font-weight: 485;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Book.woff) format('woff');
url('/fonts/Basel-Book.woff') format('woff2'),
url('/fonts/Basel-Book.woff') format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}
@ -189,8 +189,9 @@ exports[`should inject metadata for collections 2`] = `
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@ -206,9 +207,9 @@ exports[`should inject metadata for collections 2`] = `
font-weight: 535;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Medium.woff) format('woff');
url('/fonts/Basel-Medium.woff2') format('woff2'),
url('/fonts/Basel-Medium.woff') format('woff');
}
@font-face {
@ -216,15 +217,14 @@ exports[`should inject metadata for collections 2`] = `
font-weight: 485;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Book.woff) format('woff');
url('/fonts/Basel-Book.woff') format('woff2'),
url('/fonts/Basel-Book.woff') format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}
@ -337,8 +337,9 @@ exports[`should inject metadata for collections 3`] = `
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@ -354,9 +355,9 @@ exports[`should inject metadata for collections 3`] = `
font-weight: 535;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Medium.woff) format('woff');
url('/fonts/Basel-Medium.woff2') format('woff2'),
url('/fonts/Basel-Medium.woff') format('woff');
}
@font-face {
@ -364,15 +365,14 @@ exports[`should inject metadata for collections 3`] = `
font-weight: 485;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Book.woff) format('woff');
url('/fonts/Basel-Book.woff') format('woff2'),
url('/fonts/Basel-Book.woff') format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}
@ -443,151 +443,3 @@ exports[`should inject metadata for collections 3`] = `
</html>
"
`;
exports[`should inject metadata for collections 4`] = `
"<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<!--
will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#fff" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' 'unsafe-inline'"
/>
<!--
Apple Smart App Banner for Safari on iOS
https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners
-->
<meta name="apple-itunes-app" content="app-id=6443944476">
<!--
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="/manifest.json" />
<link rel="preconnect" href="https://api.uniswap.org/" crossorigin/>
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<style>
* {
font-family: 'Basel', sans-serif;
box-sizing: border-box;
}
/**
Explicitly load Basel var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Basel';
font-weight: 535;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Medium.woff) format('woff');
}
@font-face {
font-family: 'Basel';
font-weight: 485;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Book.woff) format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-weight: 485;
font-variant: none;
font-smooth: always;
text-rendering: optimizeLegibility !important;
-webkit-font-smoothing: antialiased !important;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Use this to apply network-specific gradient backgrounds, in RadialGradientByChainUpdater.ts */
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html,
body,
#root {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background: linear-gradient(rgb(19, 19, 19) 0%, rgb(19, 19, 19) 100%);
}
}
@media (prefers-color-scheme: light) {
html {
background: radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0) 0%, rgba(255, 255, 255, 0) 100%), rgb(255, 255, 255);
}
}
</style>
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="0xed5af388653567af2f388e6224dc7c4b3241c545 on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="0xed5af388653567af2f388e6224dc7c4b3241c545 on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="0xed5af388653567af2f388e6224dc7c4b3241c545 on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545"/><meta property="twitter:image:alt" content="0xed5af388653567af2f388e6224dc7c4b3241c545 on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
</body>
</html>
"
`;

@ -16,15 +16,7 @@ const collections = [
},
]
const nonexistentCollections = [
{
address: '0xed5af388653567af2f388e6224dc7c4b3241c545',
collectionName: '0xed5af388653567af2f388e6224dc7c4b3241c545',
image: 'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545',
},
]
test.each([...collections, ...nonexistentCollections])('should inject metadata for collections', async (collection) => {
test.each([...collections])('should inject metadata for collections', async (collection) => {
const url = 'http://127.0.0.1:3000/nfts/collection/' + collection.address
const body = await fetch(new Request(url)).then((res) => res.text())
expect(body).toMatchSnapshot()
@ -42,24 +34,33 @@ test.each([...collections, ...nonexistentCollections])('should inject metadata f
expect(body).toContain(`<meta property="twitter:image:alt" content="${collection.collectionName} on Uniswap"/>`)
})
const nonexistentCollections = [
{
address: '0xed5af388653567af2f388e6224dc7c4b3241c545',
},
]
const invalidCollections = [
{
address: '0xd3adb33f',
},
]
test.each(invalidCollections)('should not inject metadata for nonexistent collections', async (collection) => {
const url = 'http://127.0.0.1:3000/nfts/collection/' + collection.address
const body = await fetch(new Request(url)).then((res) => res.text())
expect(body).not.toContain('og:title')
expect(body).not.toContain('og:image')
expect(body).not.toContain('og:image:width')
expect(body).not.toContain('og:image:height')
expect(body).not.toContain('og:type')
expect(body).not.toContain('og:url')
expect(body).not.toContain('og:image:alt')
expect(body).not.toContain('twitter:card')
expect(body).not.toContain('twitter:title')
expect(body).not.toContain('twitter:image')
expect(body).not.toContain('twitter:image:alt')
})
test.each([...invalidCollections, ...nonexistentCollections])(
'should not inject metadata for nonexistent collections',
async (collection) => {
const url = 'http://127.0.0.1:3000/nfts/collection/' + collection.address
const body = await fetch(new Request(url)).then((res) => res.text())
expect(body).not.toContain('og:title')
expect(body).not.toContain('og:image')
expect(body).not.toContain('og:image:width')
expect(body).not.toContain('og:image:height')
expect(body).not.toContain('og:type')
expect(body).not.toContain('og:url')
expect(body).not.toContain('og:image:alt')
expect(body).not.toContain('twitter:card')
expect(body).not.toContain('twitter:title')
expect(body).not.toContain('twitter:image')
expect(body).not.toContain('twitter:image:alt')
}
)

@ -41,8 +41,9 @@ exports[`should inject metadata for valid tokens 1`] = `
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@ -58,9 +59,9 @@ exports[`should inject metadata for valid tokens 1`] = `
font-weight: 535;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Medium.woff) format('woff');
url('/fonts/Basel-Medium.woff2') format('woff2'),
url('/fonts/Basel-Medium.woff') format('woff');
}
@font-face {
@ -68,15 +69,14 @@ exports[`should inject metadata for valid tokens 1`] = `
font-weight: 485;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Book.woff) format('woff');
url('/fonts/Basel-Book.woff') format('woff2'),
url('/fonts/Basel-Book.woff') format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}
@ -189,8 +189,9 @@ exports[`should inject metadata for valid tokens 2`] = `
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@ -206,9 +207,9 @@ exports[`should inject metadata for valid tokens 2`] = `
font-weight: 535;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Medium.woff) format('woff');
url('/fonts/Basel-Medium.woff2') format('woff2'),
url('/fonts/Basel-Medium.woff') format('woff');
}
@font-face {
@ -216,15 +217,14 @@ exports[`should inject metadata for valid tokens 2`] = `
font-weight: 485;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Book.woff) format('woff');
url('/fonts/Basel-Book.woff') format('woff2'),
url('/fonts/Basel-Book.woff') format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}
@ -337,8 +337,9 @@ exports[`should inject metadata for valid tokens 3`] = `
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@ -354,9 +355,9 @@ exports[`should inject metadata for valid tokens 3`] = `
font-weight: 535;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Medium.woff) format('woff');
url('/fonts/Basel-Medium.woff2') format('woff2'),
url('/fonts/Basel-Medium.woff') format('woff');
}
@font-face {
@ -364,15 +365,14 @@ exports[`should inject metadata for valid tokens 3`] = `
font-weight: 485;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Book.woff) format('woff');
url('/fonts/Basel-Book.woff') format('woff2'),
url('/fonts/Basel-Book.woff') format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}
@ -485,8 +485,9 @@ exports[`should inject metadata for valid tokens 4`] = `
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
<link rel="preload" href="/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@ -502,9 +503,9 @@ exports[`should inject metadata for valid tokens 4`] = `
font-weight: 535;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Medium.woff) format('woff');
url('/fonts/Basel-Medium.woff2') format('woff2'),
url('/fonts/Basel-Medium.woff') format('woff');
}
@font-face {
@ -512,15 +513,14 @@ exports[`should inject metadata for valid tokens 4`] = `
font-weight: 485;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(/fonts/Basel-Book.woff) format('woff');
url('/fonts/Basel-Book.woff') format('woff2'),
url('/fonts/Basel-Book.woff') format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}

@ -13,7 +13,8 @@ const forkingConfig = {
const forks = {
[ChainId.MAINNET]: {
url: `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
blockNumber: UNIVERSAL_ROUTER_CREATION_BLOCK(ChainId.MAINNET),
// Temporarily hardcoding this to fix e2e tests as we investigate source of swap tests failing on older blocknumbers
blockNumber: 18537387,
...forkingConfig,
},
[ChainId.POLYGON]: {

@ -28,7 +28,6 @@ const linguiConfig = {
'ca-ES',
'cs-CZ',
'da-DK',
'de-DE',
'el-GR',
'en-US',
'es-ES',

@ -116,7 +116,7 @@
"@types/uuid": "^8.3.4",
"@types/wcag-contrast": "^3.0.0",
"@types/xml2js": "^0.4.12",
"@uniswap/default-token-list": "^11.2.0",
"@uniswap/default-token-list": "^11.8.0",
"@uniswap/eslint-config": "^1.2.0",
"@vanilla-extract/jest-transform": "^1.1.1",
"@vanilla-extract/webpack-plugin": "^2.2.0",
@ -129,7 +129,7 @@
"cypress": "12.12.0",
"cypress-hardhat": "^2.5.0",
"env-cmd": "^10.1.0",
"eslint": "^7.11.0",
"eslint": "8.44.0",
"eslint-plugin-import": "^2.27",
"eslint-plugin-rulesdir": "^0.2.2",
"hardhat": "^2.14.0",
@ -157,7 +157,6 @@
"terser": "^5.19.4",
"terser-webpack-plugin": "^5.3.9",
"ts-jest": "^29.1.1",
"ts-transform-graphql-tag": "^0.2.1",
"tsafe": "^1.6.4",
"typechain": "^5.0.0",
"typescript": "^4.9.4",
@ -166,6 +165,9 @@
"wrangler": "^3.7.0",
"yarn-deduplicate": "^6.0.0"
},
"resolutions": {
"@uniswap/sdk-core": "4.0.7"
},
"dependencies": {
"@apollo/client": "^3.7.2",
"@coinbase/wallet-sdk": "^3.6.4",
@ -194,18 +196,18 @@
"@types/react-helmet": "^6.1.7",
"@types/react-window-infinite-loader": "^1.0.6",
"@uniswap/analytics": "1.5.0",
"@uniswap/analytics-events": "^2.24.0",
"@uniswap/analytics-events": "^2.28.0",
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "^1.0.1",
"@uniswap/permit2-sdk": "^1.2.0",
"@uniswap/redux-multicall": "^1.1.8",
"@uniswap/router-sdk": "^1.6.0",
"@uniswap/sdk-core": "^4.0.3",
"@uniswap/router-sdk": "^1.7.1",
"@uniswap/sdk-core": "4.0.7",
"@uniswap/smart-order-router": "^3.15.0",
"@uniswap/token-lists": "^1.0.0-beta.33",
"@uniswap/uniswapx-sdk": "^1.3.0",
"@uniswap/universal-router-sdk": "^1.5.6",
"@uniswap/uniswapx-sdk": "^1.4.1",
"@uniswap/universal-router-sdk": "^1.5.8",
"@uniswap/v2-core": "^1.0.1",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v2-sdk": "^3.2.0",

99
public/app-sitemap.xml Normal file

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>https://app.uniswap.org/</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://app.uniswap.org/tokens</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://app.uniswap.org/send</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://app.uniswap.org/swap</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://app.uniswap.org/pool/v2/find</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://app.uniswap.org/pool/v2</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://app.uniswap.org/pool</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://app.uniswap.org/pools/v2/find</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://app.uniswap.org/pools/v2</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://app.uniswap.org/pools</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/add/v2</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://app.uniswap.org/add</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://app.uniswap.org/migrate/v2</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/profile</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://app.uniswap.org/create-proposal</loc>
<lastmod>2023-10-11T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
</urlset>

Binary file not shown.

Binary file not shown.

@ -40,8 +40,9 @@
<link rel="preconnect" href="https://mainnet.infura.io/" crossorigin/>
<link rel="preload" href="%PUBLIC_URL%/fonts/Basel-Book.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="%PUBLIC_URL%/fonts/Basel-Book.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="%PUBLIC_URL%/fonts/Basel-Medium.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="%PUBLIC_URL%/fonts/Basel-Medium.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@ -57,9 +58,9 @@
font-weight: 535;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(%PUBLIC_URL%/fonts/Basel-Medium.woff) format('woff');
url('%PUBLIC_URL%/fonts/Basel-Medium.woff2') format('woff2'),
url('%PUBLIC_URL%/fonts/Basel-Medium.woff') format('woff');
}
@font-face {
@ -67,15 +68,14 @@
font-weight: 485;
font-style: normal;
font-display: block;
font-named-instance: 'Book';
src:
url(%PUBLIC_URL%/fonts/Basel-Book.woff) format('woff');
url('%PUBLIC_URL%/fonts/Basel-Book.woff') format('woff2'),
url('%PUBLIC_URL%/fonts/Basel-Book.woff') format('woff');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Basel', sans-serif;
}
}

518
public/nfts-sitemap.xml Normal file

@ -0,0 +1,518 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>https://app.uniswap.org/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x60e4d786628fea6478f785a6d7e704777c86a7c6</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x34d85c9cdeb23fa97cb08333b511ac86e1c4e258</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x99a9b7c1116f9ceeb1652de04d5969cce509b069</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xb7f7f6c52f2e2fdb1963eab30438024864c313f6</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x23581767a106ae21c074b2276d25e5c3e136a68b</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x8a90cab2b38dba80c64b7734e58ee1db38b8992e</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xba30e5f9bb24caa003e9f2f0497ad287fdf95623</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xbd3531da5cf5857e7cfaa92426877b022e612cf8</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x306b1ea3ecdf94ab739f1910bbda052ed4a9f949</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x1a92f7381b9f03921564a437210bb9396471050c</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x5cc5b05a8a13e3fbdb0bb9fccd98d38e50f90c38</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x5af0d9827e0c53e4799bb226655a1de152a425a5</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x3bf2922f4520a8ba0c2efc3d2a1539678dad5e9d</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xe785e82358879f061bc3dcac6f0444462d4b5330</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x76be3b62873462d2142405439777e971754e8e77</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xfd43af6d3fe1b916c026f6ac35b3ede068d1ca01</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x1cb1a5e65610aeff2551a50f76a87a7d3fb649c6</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xff9c1b15b16263c61d017ee9f65c50e4ae0113d7</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x6339e5e072086621540d0362c4e3cea0d643e114</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xb932a70a57673d89f4acffbe830e8ed7f75fb9e0</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x79fcdef22feed20eddacbb2587640e45491b757f</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xa3aee8bce55beea1951ef834b99f3ac60d1abeeb</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x769272677fab02575e84945f03eca517acc544cc</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x4db1f25d3d98600140dfc18deb7515be5bd293af</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x34eebee6942d8def3c125458d1a86e0a897fd6f9</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x59468516a8259058bad1ca5f8f4bff190d30e066</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x394e3d3044fc89fcdd966d3cb35ac0b32b0cda91</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x60bb1e2aa1c9acafb4d34f71585d7e959f387769</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x28472a58a490c5e09a238847f66a68a47cc76f0f</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x341a1c534248966c4b6afad165b98daed4b964ef</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x82c7a8f707110f5fbb16184a5933e9f78a34c6ab</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xccc441ac31f02cd96c153db6fd5fe0a2f4e6a68d</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x764aeebcf425d56800ef2c84f2578689415a2daa</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x160c404b2b49cbc3240055ceaee026df1e8497a0</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xd2f668a8461d6761115daf8aeb3cdf5f40c532c6</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x39ee2c7b3cb80254225884ca001f57118c8f21b6</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xd774557b647330c91bf44cfeab205095f7e6c367</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x1792a96e5668ad7c167ab804a100ce42395ce54d</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x04afa589e2b933f9463c5639f412b183ec062505</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xe75512aa3bec8f00434bbd6ad8b0a3fbff100ad6</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x348fc118bcc65a92dc033a951af153d14d945312</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x892848074ddea461a15f337250da3ce55580ca85</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x5946aeaab44e65eb370ffaa6a7ef2218cff9b47d</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x282bdd42f4eb70e7a9d9f40c8fea0825b7f68c5d</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x4b15a9c28034dc83db40cd810001427d3bd7163d</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x7ea3cca10668b8346aec0bf1844a49e995527c8b</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xb852c6b5892256c264cc2c888ea462189154d8d7</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x9378368ba6b85c1fba5b131b530f5f5bedf21a18</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x2acab3dea77832c09420663b0e1cb386031ba17b</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x0c2e57efddba8c768147d1fdf9176a0a6ebd5d83</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x08d7c0242953446436f34b4c78fe9da38c73668d</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x8943c7bac1914c9a7aba750bf2b6b09fd21037e0</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x364c828ee171616a39897688a831c2499ad972ec</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x7f36182dee28c45de6072a34d29855bae76dbe2f</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xf61f24c2d93bf2de187546b14425bf631f28d6dc</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x797a48c46be32aafcedcfd3d8992493d8a1f256b</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x123b30e25973fecd8354dd5f41cc45a3065ef88c</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x6632a9d63e142f17a668064d41a21193b49b41a0</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xf4ee95274741437636e748ddac70818b4ed7d043</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x57a204aa1042f6e66dd7730813f4024114d74f37</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xd1258db6ac08eb0e625b75b371c023da478e94a9</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x75e95ba5997eb235f40ecf8347cdb11f18ff640b</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xd532b88607b1877fe20c181cba2550e3bbd6b31c</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xa1d4657e0e6507d5a94d06da93e94dc7c8c44b51</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xedb61f74b0d09b2558f1eeb79b247c1f363ae452</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x7d8820fa92eb1584636f4f5b8515b5476b75171a</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x231d3559aa848bf10366fb9868590f01d34bf240</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xad9fd7cb4fc7a0fbce08d64068f60cbde22ed34c</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x0e9d6552b85be180d941f1ca73ae3e318d2d4f1f</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xb716600ed99b4710152582a124c697a7fe78adbf</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xaadc2d4261199ce24a4b0a57370c4fcf43bb60aa</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x4e1f41613c9084fdb9e34e11fae9412427480e56</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x79986af15539de2db9a5086382daeda917a9cf0c</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xc99c679c50033bbc5321eb88752e89a93e9e83c5</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xc36cf0cfcb5d905b8b513860db0cfe63f6cf9f5c</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x3110ef5f612208724ca51f5761a69081809f03b7</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x036721e5a769cc48b3189efbb9cce4471e8a48b1</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x524cab2ec69124574082676e6f654a18df49a048</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x7ab2352b1d2e185560494d5e577f9d3c238b78c5</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x32973908faee0bf825a343000fe412ebe56f802a</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x7daec605e9e2a1717326eedfd660601e2753a057</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xc1caf0c19a8ac28c41fe59ba6c754e4b9bd54de9</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x33fd426905f149f8376e227d0c9d3340aad17af1</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x466cfcd0525189b573e794f554b8a751279213ac</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x6be69b2a9b153737887cfcdca7781ed1511c7e36</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x80336ad7a747236ef41f47ed2c7641828a480baa</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x9401518f4ebba857baa879d9f76e1cc8b31ed197</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x4b61413d4392c806e6d0ff5ee91e6073c21d6430</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xc3f733ca98e0dad0386979eb96fb1722a1a05e69</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x09233d553058c2f42ba751c87816a8e9fae7ef10</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x960b7a6bcd451c9968473f7bbfd9be826efd549a</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x36d30b3b85255473d27dd0f7fd8f35e36a9d6f06</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x698fbaaca64944376e2cdc4cad86eaa91362cf54</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x497a9a79e82e6fc0ff10a16f6f75e6fcd5ae65a8</loc>
<lastmod>2023-10-11T23:24:32.127Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x41a322b28d0ff354040e2cbc676f0320d8c8850d</loc>
<lastmod>2023-10-16T18:42:53.632Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0xa9c0a07a7cb84ad1f2ffab06de3e55aab7d523e8</loc>
<lastmod>2023-10-16T18:42:53.632Z</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/nfts/collection/0x942bc2d3e7a589fe5bd4a5c6ef9727dfd82f5c8a</loc>
<lastmod>2023-10-16T18:42:53.632Z</lastmod>
<priority>0.7</priority>
</url>
</urlset>

@ -1,10 +1,6 @@
# *
User-agent: *
Disallow: /static/js/
Allow: /
# Host
Host: https://app.uniswap.org
Disallow:
# Sitemaps
Sitemap: https://app.uniswap.org/sitemap.xml

File diff suppressed because it is too large Load Diff

1663
public/tokens-sitemap.xml Normal file

File diff suppressed because it is too large Load Diff

@ -9,19 +9,13 @@ const thegraphConfig = require('../graphql.thegraph.config')
const exec = promisify(child_process.exec)
function fetchSchema(url, outputFile) {
exec(`yarn --silent get-graphql-schema --h Origin=https://app.uniswap.org ${url}`)
.then(({ stderr, stdout }) => {
if (stderr) {
throw new Error(stderr)
} else {
fs.writeFile(outputFile, stdout)
}
})
.catch((err) => {
console.error(err)
console.error(`Failed to fetch schema from ${url}`)
})
async function fetchSchema(url, outputFile) {
try {
const { stdout } = await exec(`yarn --silent get-graphql-schema --h Origin=https://app.uniswap.org ${url}`);
await fs.writeFile(outputFile, stdout);
} catch(err){
console.error(`Failed to fetch schema from ${url}`)
}
}
fetchSchema(process.env.THE_GRAPH_SCHEMA_ENDPOINT, thegraphConfig.schema)

@ -29,8 +29,8 @@ const nftTopCollectionsQuery = `
}
`
fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
const sitemapURLs = {}
fs.readFile('./public/tokens-sitemap.xml', 'utf8', async (err, data) => {
const tokenURLs = {}
try {
const sitemap = await parseStringPromise(data)
if (sitemap.urlset.url) {
@ -39,7 +39,7 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
if (lastMod < Date.now() - weekMs) {
url.lastmod = nowISO
}
sitemapURLs[url.loc] = true
tokenURLs[url.loc] = true
})
}
@ -57,7 +57,7 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
tokenAddresses.forEach((address) => {
const tokenURL = `https://app.uniswap.org/tokens/${chainName.toLowerCase()}/${address}`
if (!(tokenURL in sitemapURLs)) {
if (!(tokenURL in tokenURLs)) {
sitemap.urlset.url.push({
loc: [tokenURL],
lastmod: [nowISO],
@ -67,6 +67,39 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
})
}
const builder = new Builder()
const xml = builder.buildObject(sitemap)
const path = './public/tokens-sitemap.xml'
fs.writeFile(path, xml, (error) => {
if (error) throw error
const stats = fs.statSync(path)
const fileSizeBytes = stats.size
const fileSizeMegabytes = fileSizeBytes / (1024 * 1024)
if (fileSizeMegabytes > 50) {
throw new Error('Generated tokens-sitemap.xml file size exceeds 50MB')
}
console.log('Tokens sitemap updated')
})
} catch (e) {
console.error(e)
}
})
fs.readFile('./public/nfts-sitemap.xml', 'utf8', async (err, data) => {
const collectionURLs = {}
try {
const sitemap = await parseStringPromise(data)
if (sitemap.urlset.url) {
sitemap.urlset.url.forEach((url) => {
const lastMod = new Date(url.lastmod).getTime()
if (lastMod < Date.now() - weekMs) {
url.lastmod = nowISO
}
collectionURLs[url.loc] = true
})
}
const nftResponse = await fetch('https://api.uniswap.org/v1/graphql', {
method: 'POST',
headers: {
@ -79,7 +112,7 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
const collectionAddresses = nftJSON.data.topCollections.edges.map((edge) => edge.node.nftContracts[0].address)
collectionAddresses.forEach((address) => {
const collectionURL = `https://app.uniswap.org/nfts/collection/${address}`
if (!(collectionURL in sitemapURLs)) {
if (!(collectionURL in collectionURLs)) {
sitemap.urlset.url.push({
loc: [collectionURL],
lastmod: [nowISO],
@ -90,7 +123,7 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
const builder = new Builder()
const xml = builder.buildObject(sitemap)
const path = './public/sitemap.xml'
const path = './public/nfts-sitemap.xml'
fs.writeFile(path, xml, (error) => {
if (error) throw error
const stats = fs.statSync(path)
@ -98,9 +131,9 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
const fileSizeMegabytes = fileSizeBytes / (1024 * 1024)
if (fileSizeMegabytes > 50) {
throw new Error('Generated sitemap file size exceeds 50MB')
throw new Error('Generated nfts-sitemap.xml file size exceeds 50MB')
}
console.log('Sitemap updated')
console.log('NFT collections sitemap updated')
})
} catch (e) {
console.error(e)

Binary file not shown.

After

(image error) Size: 11 KiB

Binary file not shown.

After

(image error) Size: 9.8 KiB

Binary file not shown.

After

(image error) Size: 1.2 KiB

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 22.773 22.773" style="enable-background:new 0 0 22.773 22.773;" xml:space="preserve">
<g>
<g>
<path d="M15.769,0c0.053,0,0.106,0,0.162,0c0.13,1.606-0.483,2.806-1.228,3.675c-0.731,0.863-1.732,1.7-3.351,1.573
c-0.108-1.583,0.506-2.694,1.25-3.561C13.292,0.879,14.557,0.16,15.769,0z"/>
<path d="M20.67,16.716c0,0.016,0,0.03,0,0.045c-0.455,1.378-1.104,2.559-1.896,3.655c-0.723,0.995-1.609,2.334-3.191,2.334
c-1.367,0-2.275-0.879-3.676-0.903c-1.482-0.024-2.297,0.735-3.652,0.926c-0.155,0-0.31,0-0.462,0
c-0.995-0.144-1.798-0.932-2.383-1.642c-1.725-2.098-3.058-4.808-3.306-8.276c0-0.34,0-0.679,0-1.019
c0.105-2.482,1.311-4.5,2.914-5.478c0.846-0.52,2.009-0.963,3.304-0.765c0.555,0.086,1.122,0.276,1.619,0.464
c0.471,0.181,1.06,0.502,1.618,0.485c0.378-0.011,0.754-0.208,1.135-0.347c1.116-0.403,2.21-0.865,3.652-0.648
c1.733,0.262,2.963,1.032,3.723,2.22c-1.466,0.933-2.625,2.339-2.427,4.74C17.818,14.688,19.086,15.964,20.67,16.716z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

(image error) Size: 1.6 KiB

@ -0,0 +1,85 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2450_35919)">
<rect width="20" height="20" rx="4.60595" fill="#FFFBFF"/>
<rect width="20" height="20" rx="4.60595" fill="url(#paint0_linear_2450_35919)"/>
<g filter="url(#filter0_f_2450_35919)">
<ellipse cx="9.99967" cy="12.0001" rx="9.66667" ry="7.66667" fill="url(#paint1_radial_2450_35919)" fill-opacity="0.8"/>
</g>
<g filter="url(#filter1_i_2450_35919)">
<path d="M14.4974 4.34199C14.5183 3.97475 14.5683 3.73252 14.6689 3.51131C14.7087 3.42374 14.746 3.35208 14.7518 3.35208C14.7575 3.35208 14.7402 3.41671 14.7133 3.49569C14.6402 3.71038 14.6282 4.00402 14.6786 4.34566C14.7425 4.77915 14.7788 4.84169 15.239 5.30995C15.4549 5.52958 15.706 5.80659 15.797 5.92551L15.9625 6.14176L15.797 5.98722C15.5946 5.79823 15.1292 5.42966 15.0263 5.37698C14.9574 5.34164 14.9472 5.34225 14.9046 5.38439C14.8655 5.42322 14.8572 5.48156 14.8518 5.75737C14.8433 6.18723 14.7844 6.46314 14.6424 6.73903C14.5656 6.88824 14.5535 6.8564 14.623 6.68797C14.6749 6.56222 14.6802 6.50694 14.6798 6.0908C14.679 5.25469 14.5793 5.05368 13.9946 4.70934C13.8465 4.62211 13.6024 4.4963 13.4522 4.42976C13.3021 4.36322 13.1828 4.30526 13.1871 4.30093C13.2037 4.28452 13.7739 4.45022 14.0034 4.53811C14.3448 4.66886 14.4012 4.6858 14.4426 4.67003C14.4704 4.65946 14.4839 4.57887 14.4974 4.34199Z" fill="url(#paint2_linear_2450_35919)"/>
<path d="M7.28422 3.12561C7.0817 3.09439 7.07316 3.09073 7.16846 3.07618C7.3511 3.04826 7.78237 3.0863 8.07956 3.15653C8.77336 3.32041 9.40468 3.74022 10.0786 4.48582L10.2576 4.68389L10.5137 4.64298C11.5927 4.47068 12.6903 4.60761 13.6084 5.02906C13.8609 5.14501 14.2592 5.3758 14.3089 5.43511C14.3248 5.45402 14.3539 5.57569 14.3736 5.70552C14.4418 6.1547 14.4076 6.499 14.2694 6.75616C14.1941 6.8961 14.19 6.94045 14.2405 7.06021C14.2809 7.15577 14.3935 7.22651 14.5049 7.22636C14.7331 7.22605 14.9787 6.85956 15.0924 6.34963L15.1376 6.14707L15.2272 6.24785C15.7183 6.80076 16.104 7.5548 16.1703 8.09153L16.1875 8.23147L16.105 8.10426C15.9629 7.88534 15.8202 7.73632 15.6374 7.61612C15.3079 7.39947 14.9595 7.32574 14.0368 7.27742C13.2034 7.23379 12.7318 7.16305 12.2641 7.01151C11.4684 6.75372 11.0673 6.41038 10.1221 5.17811C9.70233 4.63077 9.44285 4.32794 9.18474 4.08407C8.59823 3.52993 8.02193 3.23932 7.28422 3.12561Z" fill="url(#paint3_linear_2450_35919)"/>
<path d="M7.67762 5.77618C7.26647 5.2137 7.01209 4.3513 7.06716 3.70661L7.08419 3.50711L7.17778 3.52408C7.35352 3.55594 7.65655 3.66801 7.79844 3.75366C8.18783 3.98865 8.3564 4.29805 8.5279 5.09252C8.57814 5.32522 8.64404 5.58856 8.67437 5.67772C8.7232 5.82122 8.90769 6.15641 9.05769 6.3741C9.16573 6.53088 9.09396 6.60518 8.85514 6.58376C8.49039 6.55104 7.99634 6.21221 7.67762 5.77618Z" fill="url(#paint4_linear_2450_35919)"/>
<path d="M13.9982 9.96242C12.0768 9.19367 11.4001 8.52639 11.4001 7.40049C11.4001 7.2348 11.4058 7.09924 11.4128 7.09924C11.4198 7.09924 11.4941 7.15392 11.578 7.22076C11.9676 7.5313 12.4039 7.66393 13.6118 7.83903C14.3225 7.94208 14.7225 8.0253 15.0915 8.1469C16.2642 8.53338 16.9898 9.3177 17.1628 10.386C17.2131 10.6964 17.1836 11.2785 17.1021 11.5853C17.0377 11.8276 16.8413 12.2644 16.7892 12.2812C16.7748 12.2858 16.7606 12.2308 16.7569 12.156C16.7372 11.7549 16.5332 11.3643 16.1906 11.0718C15.8011 10.7392 15.2777 10.4744 13.9982 9.96242Z" fill="url(#paint5_linear_2450_35919)"/>
<path d="M12.6493 10.2818C12.6252 10.1394 12.5835 9.95763 12.5565 9.87779L12.5075 9.73264L12.5985 9.83421C12.7246 9.97475 12.8241 10.1546 12.9085 10.3942C12.973 10.577 12.9802 10.6314 12.9797 10.9285C12.9792 11.2202 12.9712 11.2813 12.9117 11.4459C12.8179 11.7054 12.7015 11.8894 12.5062 12.0869C12.1552 12.4419 11.704 12.6384 11.0528 12.7199C10.9396 12.734 10.6097 12.7579 10.3197 12.7729C9.58873 12.8107 9.10767 12.8887 8.6754 13.0395C8.61325 13.0612 8.55776 13.0744 8.55213 13.0688C8.53464 13.0515 8.82895 12.8772 9.07204 12.7608C9.41481 12.5968 9.75602 12.5072 10.5205 12.3807C10.8982 12.3182 11.2882 12.2424 11.3872 12.2122C12.3224 11.9273 12.8031 11.1919 12.6493 10.2818Z" fill="url(#paint6_linear_2450_35919)"/>
<path d="M13.5301 11.8362C13.2748 11.2909 13.2162 10.7643 13.356 10.2733C13.371 10.2208 13.3951 10.1779 13.4095 10.1779C13.424 10.1779 13.4842 10.2103 13.5434 10.2498C13.661 10.3285 13.897 10.461 14.5255 10.8016C15.3099 11.2265 15.7571 11.5556 16.0612 11.9316C16.3276 12.2608 16.4924 12.6358 16.5717 13.0931C16.6166 13.3521 16.5903 13.9753 16.5234 14.2361C16.3125 15.0585 15.8224 15.7045 15.1233 16.0814C15.0209 16.1366 14.9289 16.182 14.919 16.1822C14.909 16.1824 14.9464 16.0881 15.0019 15.9726C15.2371 15.4841 15.2639 15.0089 15.0861 14.48C14.9772 14.1562 14.7552 13.761 14.3071 13.0932C13.786 12.3167 13.6583 12.11 13.5301 11.8362Z" fill="url(#paint7_linear_2450_35919)"/>
<path d="M6.31312 14.7786C7.02613 14.1803 7.9133 13.7553 8.72142 13.6248C9.0697 13.5686 9.64988 13.5909 9.97238 13.673C10.4893 13.8044 10.9518 14.0989 11.1923 14.4497C11.4273 14.7926 11.5281 15.0914 11.6331 15.7561C11.6745 16.0184 11.7196 16.2817 11.7332 16.3413C11.8121 16.6859 11.9655 16.9613 12.1556 17.0996C12.4576 17.3192 12.9776 17.3329 13.4891 17.1346C13.5759 17.1009 13.6513 17.0777 13.6566 17.0829C13.6751 17.1012 13.4175 17.2726 13.2358 17.3628C12.9913 17.4841 12.7969 17.531 12.5386 17.531C12.0701 17.531 11.6811 17.2942 11.3566 16.8113C11.2927 16.7163 11.1491 16.4316 11.0376 16.1788C10.6949 15.4021 10.5257 15.1655 10.1278 14.9066C9.78156 14.6813 9.33501 14.6409 8.99908 14.8046C8.55782 15.0196 8.4347 15.58 8.75075 15.9351C8.87636 16.0762 9.1106 16.198 9.30214 16.2216C9.66047 16.2659 9.96842 15.995 9.96842 15.6355C9.96842 15.4021 9.87813 15.2689 9.65083 15.1669C9.34039 15.0277 9.0067 15.1905 9.0083 15.4803C9.00899 15.6038 9.06314 15.6814 9.1878 15.7375C9.26777 15.7734 9.26963 15.7763 9.20442 15.7628C8.91957 15.7041 8.85285 15.3629 9.08187 15.1364C9.35683 14.8644 9.92541 14.9844 10.1207 15.3556C10.2027 15.5116 10.2122 15.8221 10.1407 16.0096C9.98064 16.4293 9.51395 16.65 9.04053 16.5299C8.71821 16.4481 8.58696 16.3596 8.19835 15.9618C7.52307 15.2706 7.26092 15.1366 6.28739 14.9856L6.10084 14.9567L6.31312 14.7786Z" fill="url(#paint8_linear_2450_35919)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.14462 2.40696C5.39974 5.12681 6.95295 6.24897 7.12556 6.48605C7.26807 6.68181 7.21443 6.85781 6.97029 6.99574C6.83452 7.07242 6.55538 7.15013 6.41563 7.15013C6.25755 7.15013 6.20328 7.08953 6.20328 7.08953C6.11163 7.00315 6.06001 7.01826 5.58934 6.18804C4.9359 5.18048 4.38905 4.34467 4.37413 4.33067C4.33964 4.29831 4.34023 4.2994 5.52271 6.40187C5.71377 6.84008 5.56071 7.00093 5.56071 7.06334C5.56071 7.1903 5.52586 7.25704 5.36825 7.43173C5.1055 7.72301 4.98805 8.05029 4.90326 8.72762C4.80821 9.4869 4.54094 10.0233 3.80024 10.9412C3.36666 11.4785 3.29572 11.577 3.18631 11.7936C3.04851 12.0663 3.01062 12.2191 2.99526 12.5635C2.97903 12.9276 3.01064 13.1628 3.12259 13.511C3.2206 13.8158 3.32291 14.017 3.58444 14.4196C3.81014 14.767 3.9401 15.0251 3.9401 15.1261C3.9401 15.2064 3.95554 15.2065 4.30528 15.1281C5.14226 14.9402 5.82189 14.6099 6.20412 14.2051C6.44068 13.9545 6.49622 13.8162 6.49802 13.4728C6.4992 13.2482 6.49125 13.2012 6.43016 13.072C6.33071 12.8617 6.14967 12.6869 5.75064 12.4158C5.2278 12.0606 5.00448 11.7747 4.9428 11.3815C4.8922 11.0588 4.9509 10.8312 5.24014 10.2288C5.53952 9.60524 5.61371 9.33953 5.66389 8.71103C5.69631 8.30498 5.74119 8.14484 5.8586 8.0163C5.98104 7.88226 6.09127 7.83687 6.3943 7.79573C6.88833 7.72866 7.20291 7.60165 7.46149 7.36487C7.6858 7.15946 7.77966 6.96154 7.79407 6.6636L7.805 6.43776L7.67965 6.29253C7.2257 5.76656 2.84061 1.9935 2.81267 1.9935C2.8067 1.9935 2.95609 2.17957 3.14462 2.40696ZM4.19493 12.9755C4.29756 12.7949 4.24303 12.5627 4.07135 12.4492C3.90913 12.342 3.65714 12.3925 3.65714 12.5322C3.65714 12.5748 3.68086 12.6058 3.73431 12.6332C3.82432 12.6792 3.83085 12.731 3.76003 12.8367C3.68832 12.9439 3.6941 13.038 3.77637 13.102C3.90895 13.2052 4.09663 13.1485 4.19493 12.9755Z" fill="url(#paint9_linear_2450_35919)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.11678 7.91419C7.88485 7.98498 7.6594 8.22923 7.58962 8.48535C7.54704 8.6416 7.5712 8.91567 7.63497 9.00032C7.73799 9.13703 7.83762 9.17306 8.10739 9.17117C8.63556 9.16751 9.0947 8.94241 9.14808 8.66101C9.19184 8.43034 8.99019 8.11067 8.71242 7.97032C8.56908 7.89793 8.26425 7.86921 8.11678 7.91419ZM8.7342 8.39395C8.81565 8.27895 8.78002 8.15466 8.64149 8.07059C8.3777 7.91053 7.97877 8.04299 7.97877 8.29063C7.97877 8.4139 8.1868 8.5484 8.37749 8.5484C8.50441 8.5484 8.6781 8.47319 8.7342 8.39395Z" fill="url(#paint10_linear_2450_35919)"/>
</g>
</g>
<rect x="0.0205078" y="0.0205078" width="19.959" height="19.959" rx="4.58544" stroke="black" stroke-opacity="0.1" stroke-width="0.0410156"/>
<defs>
<filter id="filter0_f_2450_35919" x="-3.16699" y="0.833389" width="26.333" height="22.3333" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.75" result="effect1_foregroundBlur_2450_35919"/>
</filter>
<filter id="filter1_i_2450_35919" x="2.8125" y="1.9935" width="14.375" height="15.5376" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="0.47168"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.864916 0 0 0 0 0.401042 0 0 0 0 0.875 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_2450_35919"/>
</filter>
<linearGradient id="paint0_linear_2450_35919" x1="7.66667" y1="20" x2="7.66667" y2="0" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFF1FF"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint1_radial_2450_35919" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10.0123 11.9901) rotate(35.9783) scale(9.71138 9.26994)">
<stop stop-color="white"/>
<stop offset="1" stop-color="#FFE3FF"/>
</radialGradient>
<linearGradient id="paint2_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
<stop stop-color="#FC72FF"/>
<stop offset="1" stop-color="#FF41F4"/>
</linearGradient>
<linearGradient id="paint3_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
<stop stop-color="#FC72FF"/>
<stop offset="1" stop-color="#FF41F4"/>
</linearGradient>
<linearGradient id="paint4_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
<stop stop-color="#FC72FF"/>
<stop offset="1" stop-color="#FF41F4"/>
</linearGradient>
<linearGradient id="paint5_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
<stop stop-color="#FC72FF"/>
<stop offset="1" stop-color="#FF41F4"/>
</linearGradient>
<linearGradient id="paint6_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
<stop stop-color="#FC72FF"/>
<stop offset="1" stop-color="#FF41F4"/>
</linearGradient>
<linearGradient id="paint7_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
<stop stop-color="#FC72FF"/>
<stop offset="1" stop-color="#FF41F4"/>
</linearGradient>
<linearGradient id="paint8_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
<stop stop-color="#FC72FF"/>
<stop offset="1" stop-color="#FF41F4"/>
</linearGradient>
<linearGradient id="paint9_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
<stop stop-color="#FC72FF"/>
<stop offset="1" stop-color="#FF41F4"/>
</linearGradient>
<linearGradient id="paint10_linear_2450_35919" x1="16.4648" y1="15.099" x2="7.33333" y2="7.33334" gradientUnits="userSpaceOnUse">
<stop stop-color="#FC72FF"/>
<stop offset="1" stop-color="#FF41F4"/>
</linearGradient>
<clipPath id="clip0_2450_35919">
<rect width="20" height="20" rx="4.60595" fill="white"/>
</clipPath>
</defs>
</svg>

After

(image error) Size: 12 KiB

@ -14,7 +14,6 @@ import Tooltip from 'components/Tooltip'
import { getConnection } from 'connection'
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
import useENSName from 'hooks/useENSName'
import { useIsNotOriginCountry } from 'hooks/useIsNotOriginCountry'
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
import { ProfilePageStateType } from 'nft/types'
@ -26,6 +25,7 @@ import { updateSelectedWallet } from 'state/user/reducer'
import styled from 'styled-components'
import { CopyHelper, ExternalLink, ThemedText } from 'theme/components'
import { shortenAddress } from 'utils'
import { isPathBlocked } from 'utils/blockedPaths'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { useCloseModal, useFiatOnrampAvailability, useOpenModal, useToggleModal } from '../../state/application/hooks'
@ -160,8 +160,8 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
const resetSellAssets = useSellAsset((state) => state.reset)
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
const shouldShowBuyFiatButton = useIsNotOriginCountry('GB')
const { formatNumber, formatPercent } = useFormatter()
const shouldShowBuyFiatButton = !isPathBlocked('/buy')
const { formatNumber, formatDelta } = useFormatter()
const shouldDisableNFTRoutes = useDisableNFTRoutes()
@ -284,7 +284,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
{`${formatNumber({
input: Math.abs(absoluteChange as number),
type: NumberType.PortfolioBalance,
})} (${formatPercent(percentChange)})`}
})} (${formatDelta(percentChange)})`}
</ThemedText.BodySecondary>
</>
)}

@ -31,7 +31,7 @@ function BaseButton({ onClick, branded, children }: PropsWithChildren<{ onClick?
)
}
// Launches App Store if on an iOS device, else navigates to Uniswap Wallet microsite
// Launches App/Play Store if on an iOS/Android device, else navigates to Uniswap Wallet microsite
export function DownloadButton({
onClick,
text = 'Download',

@ -90,7 +90,7 @@ const DescriptionText = styled(ThemedText.LabelMicro)`
function useOrderAmounts(
orderDetails?: UniswapXOrderDetails
): Pick<InterfaceTrade, 'inputAmount' | 'postTaxOutputAmount'> | undefined {
): Pick<InterfaceTrade, 'inputAmount' | 'outputAmount'> | undefined {
const inputCurrency = useCurrency(orderDetails?.swapInfo?.inputCurrencyId, orderDetails?.chainId)
const outputCurrency = useCurrency(orderDetails?.swapInfo?.outputCurrencyId, orderDetails?.chainId)
@ -106,7 +106,7 @@ function useOrderAmounts(
if (swapInfo.tradeType === TradeType.EXACT_INPUT) {
return {
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.inputCurrencyAmountRaw),
postTaxOutputAmount: CurrencyAmount.fromRawAmount(
outputAmount: CurrencyAmount.fromRawAmount(
outputCurrency,
swapInfo.settledOutputCurrencyAmountRaw ?? swapInfo.expectedOutputCurrencyAmountRaw
),
@ -114,7 +114,7 @@ function useOrderAmounts(
} else {
return {
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.expectedInputCurrencyAmountRaw),
postTaxOutputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw),
outputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw),
}
}
}

@ -68,20 +68,24 @@ Object {
"currencies": Array [
Token {
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "DAI",
"sellFeeBps": undefined,
"symbol": "DAI",
},
Token {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "Wrapped Ether",
"sellFeeBps": undefined,
"symbol": "WETH",
},
],
@ -115,11 +119,13 @@ Object {
},
Token {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "Wrapped Ether",
"sellFeeBps": undefined,
"symbol": "WETH",
},
],
@ -143,11 +149,13 @@ Object {
"currencies": Array [
Token {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "Wrapped Ether",
"sellFeeBps": undefined,
"symbol": "WETH",
},
],
@ -186,11 +194,13 @@ Object {
"currencies": Array [
Token {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "Wrapped Ether",
"sellFeeBps": undefined,
"symbol": "WETH",
},
],
@ -214,20 +224,24 @@ Object {
"currencies": Array [
Token {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "Wrapped Ether",
"sellFeeBps": undefined,
"symbol": "WETH",
},
Token {
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "DAI",
"sellFeeBps": undefined,
"symbol": "DAI",
},
],
@ -251,11 +265,13 @@ Object {
"currencies": Array [
Token {
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "DAI",
"sellFeeBps": undefined,
"symbol": "DAI",
},
],
@ -279,20 +295,24 @@ Object {
"currencies": Array [
Token {
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "DAI",
"sellFeeBps": undefined,
"symbol": "DAI",
},
Token {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "Wrapped Ether",
"sellFeeBps": undefined,
"symbol": "WETH",
},
],
@ -315,20 +335,24 @@ Object {
"currencies": Array [
Token {
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "DAI",
"sellFeeBps": undefined,
"symbol": "DAI",
},
Token {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "Wrapped Ether",
"sellFeeBps": undefined,
"symbol": "WETH",
},
],
@ -352,11 +376,13 @@ Object {
"currencies": Array [
Token {
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"buyFeeBps": undefined,
"chainId": 1,
"decimals": 18,
"isNative": false,
"isToken": true,
"name": "DAI",
"sellFeeBps": undefined,
"symbol": "DAI",
},
],

@ -8,10 +8,10 @@ import { NftCard } from 'nft/components/card'
import { detailsHref } from 'nft/components/card/utils'
import { VerifiedIcon } from 'nft/components/icons'
import { WalletAsset } from 'nft/types'
import { floorFormatter } from 'nft/utils'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components'
import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
const FloorPrice = styled(Row)`
opacity: 0;
@ -83,6 +83,8 @@ export function NFT({
}
function NFTDetails({ asset }: { asset: WalletAsset }) {
const { formatNumberOrString } = useFormatter()
return (
<Box overflow="hidden" width="full" flexWrap="nowrap">
<Row gap="4px">
@ -91,7 +93,9 @@ function NFTDetails({ asset }: { asset: WalletAsset }) {
</Row>
<FloorPrice>
<ThemedText.BodySmall color="neutral2">
{asset.floorPrice ? `${floorFormatter(asset.floorPrice)} ETH` : ' '}
{asset.floorPrice
? `${formatNumberOrString({ input: asset.floorPrice, type: NumberType.NFTTokenFloorPrice })} ETH`
: ' '}
</ThemedText.BodySmall>
</FloorPrice>
</Box>

@ -110,7 +110,7 @@ const ActiveDot = styled.span<{ closed: boolean; outOfRange: boolean }>`
margin-top: 1px;
`
function calculcateLiquidityValue(price0: number | undefined, price1: number | undefined, position: Position) {
function calculateLiquidityValue(price0: number | undefined, price1: number | undefined, position: Position) {
if (!price0 || !price1) return undefined
const value0 = parseFloat(position.amount0.toExact()) * price0
@ -124,7 +124,7 @@ function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) {
const { chainId, position, pool, details, inRange, closed } = positionInfo
const { priceA, priceB, fees: feeValue } = useFeeValues(positionInfo)
const liquidityValue = calculcateLiquidityValue(priceA, priceB, position)
const liquidityValue = calculateLiquidityValue(priceA, priceB, position)
const navigate = useNavigate()
const toggleWalletDrawer = useToggleAccountDrawer()

@ -36,7 +36,9 @@ const DoubleLogoContainer = styled.div`
}
`
const StyledLogoParentContainer = styled.div`
const LogoContainer = styled.div`
display: flex;
align-items: center;
position: relative;
top: 0;
left: 0;
@ -57,9 +59,9 @@ const CircleLogoImage = styled.img<{ size: string }>`
const L2LogoContainer = styled.div`
border-radius: ${getDefaultBorderRadius(16)}px;
height: 16px;
left: 60%;
left: 70%;
position: absolute;
top: 60%;
top: 70%;
outline: 2px solid ${({ theme }) => theme.surface1};
width: 16px;
display: flex;
@ -155,10 +157,10 @@ function SquareL2Logo({ chainId }: { chainId: ChainId }) {
*/
export function PortfolioLogo(props: PortfolioLogoProps) {
return (
<StyledLogoParentContainer style={props.style}>
<LogoContainer style={props.style}>
{getLogo(props)}
<SquareL2Logo chainId={props.chainId} />
</StyledLogoParentContainer>
</LogoContainer>
)
}

@ -3,7 +3,9 @@ import { TraceEvent } from 'analytics'
import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper'
import Row from 'components/Row'
import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta'
import { TokenBalance } from 'graphql/data/__generated__/types-and-hooks'
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
import { PortfolioTokenBalancePartsFragment } from 'graphql/data/__generated__/types-and-hooks'
import { PortfolioToken } from 'graphql/data/portfolios'
import { getTokenDetailsURL, gqlToCurrency, logSentryErrorForUnsupportedChain } from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils'
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
@ -27,7 +29,7 @@ export default function Tokens({ account }: { account: string }) {
const { data } = useCachedPortfolioBalancesQuery({ account })
const tokenBalances = data?.portfolios?.[0].tokenBalances as TokenBalance[] | undefined
const tokenBalances = data?.portfolios?.[0].tokenBalances
const { visibleTokens, hiddenTokens } = useMemo(
() => splitHiddenTokens(tokenBalances ?? [], { hideSmallBalances }),
@ -68,18 +70,23 @@ const TokenNameText = styled(ThemedText.SubHeader)`
${EllipsisStyle}
`
type PortfolioToken = NonNullable<TokenBalance['token']>
function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: TokenBalance & { token: PortfolioToken }) {
const { formatPercent } = useFormatter()
function TokenRow({
token,
quantity,
denominatedValue,
tokenProjectMarket,
}: PortfolioTokenBalancePartsFragment & { token: PortfolioToken }) {
const { formatDelta } = useFormatter()
const percentChange = tokenProjectMarket?.pricePercentChange?.value ?? 0
const navigate = useNavigate()
const toggleWalletDrawer = useToggleAccountDrawer()
const isInfoExplorePageEnabled = useInfoExplorePageEnabled()
const navigateToTokenDetails = useCallback(async () => {
navigate(getTokenDetailsURL(token))
navigate(getTokenDetailsURL({ ...token, isInfoExplorePageEnabled }))
toggleWalletDrawer()
}, [navigate, token, toggleWalletDrawer])
}, [navigate, token, isInfoExplorePageEnabled, toggleWalletDrawer])
const { formatNumber } = useFormatter()
const currency = gqlToCurrency(token)
@ -121,7 +128,7 @@ function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: Tok
</ThemedText.SubHeader>
<Row justify="flex-end">
<DeltaArrow delta={percentChange} />
<ThemedText.BodySecondary>{formatPercent(percentChange)}</ThemedText.BodySecondary>
<ThemedText.BodySecondary>{formatDelta(percentChange)}</ThemedText.BodySecondary>
</Row>
</>
)

@ -32,6 +32,14 @@ exports[`PortfolioLogo renders with L2 icon 1`] = `
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
position: relative;
top: 0;
left: 0;
@ -46,9 +54,9 @@ exports[`PortfolioLogo renders with L2 icon 1`] = `
.c3 {
border-radius: 4px;
height: 16px;
left: 60%;
left: 70%;
position: absolute;
top: 60%;
top: 70%;
outline: 2px solid #FFFFFF;
width: 16px;
display: -webkit-box;
@ -150,6 +158,14 @@ exports[`PortfolioLogo renders without L2 icon 1`] = `
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
position: relative;
top: 0;
left: 0;

@ -1,5 +1,5 @@
import { Trans } from '@lingui/macro'
import { InterfaceElementName } from '@uniswap/analytics-events'
import { InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
import { WalletConnect as WalletConnectv2 } from '@web3-react/walletconnect-v2'
import { sendAnalyticsEvent } from 'analytics'
import Column, { AutoColumn } from 'components/Column'
@ -13,7 +13,7 @@ import { QRCodeSVG } from 'qrcode.react'
import { useEffect, useState } from 'react'
import styled, { useTheme } from 'styled-components'
import { CloseIcon, ThemedText } from 'theme/components'
import { isIOS } from 'utils/userAgent'
import { isAndroid, isIOS } from 'utils/userAgent'
import uniPng from '../../assets/images/uniwallet_modal_icon.png'
import { DownloadButton } from './DownloadButton'
@ -42,9 +42,10 @@ export default function UniwalletModal() {
const { activationState, cancelActivation } = useActivationState()
const [uri, setUri] = useState<string>()
// Displays the modal if not on iOS, a Uniswap Wallet Connection is pending, & qrcode URI is available
// Displays the modal if not on iOS/Android, a Uniswap Wallet Connection is pending, & qrcode URI is available
const onLaunchedMobilePlatform = isIOS || isAndroid
const open =
!isIOS &&
!onLaunchedMobilePlatform &&
activationState.status === ActivationStatus.PENDING &&
activationState.connection.type === ConnectionType.UNISWAP_WALLET_V2 &&
!!uri
@ -57,7 +58,7 @@ export default function UniwalletModal() {
}, [])
useEffect(() => {
if (open) sendAnalyticsEvent('Uniswap wallet modal opened')
if (open) sendAnalyticsEvent(InterfaceEventName.UNIWALLET_CONNECT_MODAL_OPENED)
}, [open])
const theme = useTheme()
@ -106,12 +107,10 @@ function InfoSection() {
<InfoSectionWrapper>
<AutoColumn gap="4px">
<ThemedText.SubHeaderSmall color="neutral1">
<Trans>Don&apos;t have Uniswap Wallet?</Trans>
<Trans>Don&apos;t have a Uniswap wallet?</Trans>
</ThemedText.SubHeaderSmall>
<ThemedText.BodySmall color="neutral2">
<Trans>
Download in the App Store to safely store your tokens and NFTs, swap tokens, and connect to crypto apps.
</Trans>
<Trans>Safely store and swap tokens with the Uniswap app. Available on iOS and Android.</Trans>
</ThemedText.BodySmall>
</AutoColumn>
<Column>

@ -0,0 +1,74 @@
import { Trans } from '@lingui/macro'
import { InterfaceElementName } from '@uniswap/analytics-events'
import { useScreenSize } from 'hooks/useScreenSize'
import { useLocation } from 'react-router-dom'
import { useHideAndroidAnnouncementBanner } from 'state/user/hooks'
import { ThemedText } from 'theme/components'
import { useIsDarkMode } from 'theme/components/ThemeToggle'
import { openDownloadApp } from 'utils/openDownloadApp'
import { isMobileSafari } from 'utils/userAgent'
import androidAnnouncementBannerQR from '../../../assets/images/androidAnnouncementBannerQR.png'
import darkAndroidThumbnail from '../../../assets/images/AndroidWallet-Thumbnail-Dark.png'
import lightAndroidThumbnail from '../../../assets/images/AndroidWallet-Thumbnail-Light.png'
import {
Container,
DownloadButton,
PopupContainer,
StyledQrCode,
StyledXButton,
TextContainer,
Thumbnail,
} from './styled'
export default function AndroidAnnouncementBanner() {
const [hideAndroidAnnouncementBanner, toggleHideAndroidAnnouncementBanner] = useHideAndroidAnnouncementBanner()
const location = useLocation()
const isLandingScreen = location.search === '?intro=true' || location.pathname === '/'
const screenSize = useScreenSize()
const shouldDisplay = Boolean(!hideAndroidAnnouncementBanner && !isLandingScreen)
const isDarkMode = useIsDarkMode()
const onClick = () =>
openDownloadApp({
element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON,
})
if (isMobileSafari) return null
return (
<PopupContainer show={shouldDisplay}>
<Container>
<Thumbnail src={isDarkMode ? darkAndroidThumbnail : lightAndroidThumbnail} alt="Android app thumbnail" />
<TextContainer onClick={!screenSize['xs'] ? onClick : undefined}>
<ThemedText.BodySmall lineHeight="20px">
<Trans>Uniswap on Android</Trans>
</ThemedText.BodySmall>
<ThemedText.LabelMicro>
<Trans>Available now - download from the Google Play Store today</Trans>
</ThemedText.LabelMicro>
<DownloadButton
onClick={(e) => {
e.stopPropagation()
onClick()
}}
>
<Trans>Download now</Trans>
</DownloadButton>
</TextContainer>
<StyledQrCode src={androidAnnouncementBannerQR} alt="App OneLink QR code" />
<StyledXButton
data-testid="uniswap-wallet-banner"
size={24}
onClick={(e) => {
// prevent click from bubbling to UI on the page underneath, i.e. clicking a token row
e.preventDefault()
e.stopPropagation()
toggleHideAndroidAnnouncementBanner()
}}
/>
</Container>
</PopupContainer>
)
}

@ -0,0 +1,92 @@
import { ButtonText } from 'components/Button'
import { OpacityHoverState } from 'components/Common'
import { X } from 'react-feather'
import styled from 'styled-components'
import { BREAKPOINTS } from 'theme'
import { Z_INDEX } from 'theme/zIndex'
export const PopupContainer = styled.div<{ show: boolean }>`
${({ show }) => !show && 'display: none'};
background-color: ${({ theme }) => theme.surface2};
color: ${({ theme }) => theme.neutral1};
position: fixed;
z-index: ${Z_INDEX.sticky};
user-select: none;
border-radius: 20px;
bottom: 40px;
right: 20px;
width: 360px;
height: 92px;
border: 1.3px solid ${({ theme }) => theme.surface3};
@media only screen and (max-width: ${BREAKPOINTS.md}px) {
bottom: 62px;
}
@media only screen and (max-width: ${BREAKPOINTS.xs}px) {
width: unset;
right: 10px;
left: 10px;
}
`
export const StyledXButton = styled(X)`
cursor: pointer;
position: absolute;
top: -30px;
right: 0px;
padding: 4px;
border-radius: 50%;
background-color: ${({ theme }) => theme.surface5};
color: ${({ theme }) => theme.neutral2};
${OpacityHoverState};
@media only screen and (max-width: ${BREAKPOINTS.xs}px) {
top: 8px;
right: 8px;
}
`
export const Container = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
height: 100%;
overflow: hidden;
border-radius: 20px;
gap: 16px;
`
export const Thumbnail = styled.img`
width: 82px;
`
export const TextContainer = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2px;
color: ${({ theme }) => theme.neutral2};
padding: 10px 0px 10px;
line-height: 16px;
@media only screen and (max-width: ${BREAKPOINTS.xs}px) {
width: 220px;
}
`
export const StyledQrCode = styled.img`
padding: 2px;
border-radius: 8px;
width: 64px;
height: 64px;
background-color: ${({ theme }) => theme.white};
margin-right: 16px;
@media only screen and (max-width: ${BREAKPOINTS.xs}px) {
display: none;
}
`
export const DownloadButton = styled(ButtonText)`
line-height: 16px;
font-size: 14px;
color: ${({ theme }) => theme.accent1};
`

@ -1,92 +0,0 @@
import { Trans } from '@lingui/macro'
import { InterfaceElementName } from '@uniswap/analytics-events'
import { ChainId } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { ReactComponent as AppleLogo } from 'assets/svg/apple_logo.svg'
import baseLogoUrl from 'assets/svg/base_background_icon.svg'
import { useScreenSize } from 'hooks/useScreenSize'
import { useLocation } from 'react-router-dom'
import { useHideBaseWalletBanner } from 'state/user/hooks'
import { ThemedText } from 'theme/components'
import { openDownloadApp, openWalletMicrosite } from 'utils/openDownloadApp'
import { isIOS, isMobileSafari } from 'utils/userAgent'
import { BannerButton, BaseBackgroundImage, ButtonRow, PopupContainer, StyledXButton } from './styled'
export default function BaseWalletBanner() {
const { chainId } = useWeb3React()
const [hideBaseWalletBanner, toggleHideBaseWalletBanner] = useHideBaseWalletBanner()
const location = useLocation()
const isLandingScreen = location.search === '?intro=true' || location.pathname === '/'
const shouldDisplay = Boolean(!hideBaseWalletBanner && !isLandingScreen && chainId === ChainId.BASE)
const screenSize = useScreenSize()
if (isMobileSafari) return null
return (
<PopupContainer show={shouldDisplay}>
<StyledXButton
data-testid="uniswap-wallet-banner"
size={20}
onClick={(e) => {
// prevent click from bubbling to UI on the page underneath, i.e. clicking a token row
e.preventDefault()
e.stopPropagation()
toggleHideBaseWalletBanner()
}}
/>
<BaseBackgroundImage src={baseLogoUrl} alt="transparent base background logo" />
<ThemedText.HeadlineMedium fontSize="24px" lineHeight="28px" color="white" maxWidth="224px">
<Trans>
Swap on{' '}
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M19.5689 10C19.5689 15.4038 15.1806 19.7845 9.76737 19.7845C4.63163 19.7845 0.418433 15.8414 0 10.8225H12.9554V9.17755H0C0.418433 4.15863 4.63163 0.215576 9.76737 0.215576C15.1806 0.215576 19.5689 4.59621 19.5689 10Z"
fill="white"
/>
</svg>{' '}
BASE in the Uniswap wallet
</Trans>
</ThemedText.HeadlineMedium>
<ButtonRow>
{isIOS ? (
<>
<BannerButton
backgroundColor="white"
onClick={() =>
openDownloadApp({
element: InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON,
appStoreParams: 'pt=123625782&ct=base-app-banner&mt=8',
})
}
>
<AppleLogo width={14} height={14} />
<ThemedText.LabelSmall color="black" marginLeft="5px">
{!screenSize['xs'] ? <Trans>Download</Trans> : <Trans>Download app</Trans>}
</ThemedText.LabelSmall>
</BannerButton>
<BannerButton backgroundColor="black" onClick={() => openWalletMicrosite()}>
<ThemedText.LabelSmall color="white">
<Trans>Learn more</Trans>
</ThemedText.LabelSmall>
</BannerButton>
</>
) : (
<BannerButton backgroundColor="white" width="125px" onClick={() => openWalletMicrosite()}>
<ThemedText.LabelSmall color="black">
<Trans>Learn more</Trans>
</ThemedText.LabelSmall>
</BannerButton>
)}
</ButtonRow>
</PopupContainer>
)
}

@ -1,84 +0,0 @@
import walletBannerPhoneImageSrc from 'assets/images/wallet_banner_phone_image.png'
import { BaseButton } from 'components/Button'
import { OpacityHoverState } from 'components/Common'
import Row from 'components/Row'
import { X } from 'react-feather'
import styled from 'styled-components'
import { Z_INDEX } from 'theme/zIndex'
export const PopupContainer = styled.div<{ show: boolean }>`
display: flex;
flex-direction: column;
justify-content: space-between;
${({ show }) => !show && 'display: none'};
background: url(${walletBannerPhoneImageSrc});
background-repeat: no-repeat;
background-position: top 18px right 15px;
background-size: 166px;
:hover {
background-size: 170px;
}
transition: background-size ${({ theme }) => theme.transition.duration.medium}
${({ theme }) => theme.transition.timing.inOut};
background-color: ${({ theme }) => theme.chain_84531};
color: ${({ theme }) => theme.neutral1};
position: fixed;
z-index: ${Z_INDEX.sticky};
padding: 24px 16px 16px;
border-radius: 20px;
bottom: 20px;
right: 20px;
width: 390px;
height: 164px;
border: 1px solid ${({ theme }) => theme.surface3};
box-shadow: ${({ theme }) => theme.deprecated_deepShadow};
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
bottom: 62px;
}
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
background-position: top 32px right -10px;
width: unset;
right: 10px;
left: 10px;
height: 144px;
}
user-select: none;
`
export const BaseBackgroundImage = styled.img`
position: absolute;
top: 0;
left: 0;
height: 138px;
width: 138px;
`
export const ButtonRow = styled(Row)`
gap: 16px;
`
export const StyledXButton = styled(X)`
cursor: pointer;
position: absolute;
top: 21px;
right: 17px;
color: ${({ theme }) => theme.white};
${OpacityHoverState};
`
export const BannerButton = styled(BaseButton)`
height: 40px;
border-radius: 16px;
padding: 10px;
${OpacityHoverState};
`

@ -52,11 +52,11 @@ interface ChartDeltaProps {
function ChartDelta({ startingPrice, endingPrice, noColor }: ChartDeltaProps) {
const delta = calculateDelta(startingPrice.value, endingPrice.value)
const { formatPercent } = useFormatter()
const { formatDelta } = useFormatter()
return (
<DeltaContainer>
{formatPercent(delta)}
{formatDelta(delta)}
<DeltaArrow delta={delta} noColor={noColor} />
</DeltaContainer>
)

@ -18,11 +18,13 @@ const FiatLoadingBubble = styled(LoadingBubble)`
export function FiatValue({
fiatValue,
priceImpact,
testId,
}: {
fiatValue: { data?: number; isLoading: boolean }
priceImpact?: Percent
testId?: string
}) {
const { formatNumber, formatPriceImpact } = useFormatter()
const { formatNumber, formatPercent } = useFormatter()
const priceImpactColor = useMemo(() => {
if (!priceImpact) return undefined
@ -39,7 +41,7 @@ export function FiatValue({
return (
<Row gap="sm">
<ThemedText.BodySmall color="neutral2">
<ThemedText.BodySmall color="neutral2" data-testid={testId}>
{fiatValue.data ? (
formatNumber({
input: fiatValue.data,
@ -54,7 +56,7 @@ export function FiatValue({
<MouseoverTooltip
text={<Trans>The estimated difference between the USD values of input and output amounts.</Trans>}
>
(<Trans>{formatPriceImpact(priceImpact)}</Trans>)
(<Trans>{formatPercent(priceImpact.multiply(-1))}</Trans>)
</MouseoverTooltip>
</ThemedText.BodySmall>
)}

@ -382,7 +382,9 @@ const SwapCurrencyInputPanel = forwardRef<HTMLInputElement, SwapCurrencyInputPan
<FiatRow>
<RowBetween>
<LoadingOpacityContainer $loading={loading}>
{fiatValue && <FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />}
{fiatValue && (
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} testId={`fiat-value-${id}`} />
)}
</LoadingOpacityContainer>
{account ? (
<RowFixed style={{ height: '16px' }}>

@ -12,7 +12,7 @@ import { ReactNode, useCallback, useState } from 'react'
import styled, { useTheme } from 'styled-components'
import { ThemedText } from 'theme/components'
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import { useCurrencyBalance } from '../../state/connection/hooks'
@ -212,6 +212,7 @@ export default function CurrencyInputPanel({
const { account, chainId } = useWeb3React()
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
const theme = useTheme()
const { formatCurrencyAmount } = useFormatter()
const handleDismissSearch = useCallback(() => {
setModalOpen(false)
@ -297,7 +298,13 @@ export default function CurrencyInputPanel({
>
{Boolean(!hideBalance && currency && selectedCurrencyBalance) &&
(renderBalance?.(selectedCurrencyBalance as CurrencyAmount<Currency>) || (
<Trans>Balance: {formatCurrencyAmount(selectedCurrencyBalance, 4)}</Trans>
<Trans>
Balance:{' '}
{formatCurrencyAmount({
amount: selectedCurrencyBalance,
type: NumberType.TokenNonTx,
})}
</Trans>
))}
</ThemedText.DeprecatedBody>
{Boolean(showMaxButton && selectedCurrencyBalance) && (

@ -5,19 +5,17 @@ import { DynamicConfigName } from 'featureFlags/dynamicConfig'
import { useQuickRouteChains } from 'featureFlags/dynamicConfig/quickRouteChains'
import { useCurrencyConversionFlag } from 'featureFlags/flags/currencyConversion'
import { useFallbackProviderEnabledFlag } from 'featureFlags/flags/fallbackProvider'
import { useFotAdjustmentsFlag } from 'featureFlags/flags/fotAdjustments'
import { useInfoExploreFlag } from 'featureFlags/flags/infoExplore'
import { useInfoLiveViewsFlag } from 'featureFlags/flags/infoLiveViews'
import { useInfoPoolPageFlag } from 'featureFlags/flags/infoPoolPage'
import { useInfoTDPFlag } from 'featureFlags/flags/infoTDP'
import { useLimitsEnabledFlag } from 'featureFlags/flags/limits'
import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx'
import { useProgressIndicatorV2Flag } from 'featureFlags/flags/progressIndicatorV2'
import { useQuickRouteMainnetFlag } from 'featureFlags/flags/quickRouteMainnet'
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
import { useUniswapXDefaultEnabledFlag } from 'featureFlags/flags/uniswapXDefault'
import { useUniswapXEthOutputFlag } from 'featureFlags/flags/uniswapXEthOutput'
import { useUniswapXExactOutputFlag } from 'featureFlags/flags/uniswapXExactOutput'
import { useUniswapXSyntheticQuoteFlag } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
import { useFeesEnabledFlag } from 'featureFlags/flags/useFees'
import { useUpdateAtom } from 'jotai/utils'
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
import { X } from 'react-feather'
@ -267,6 +265,18 @@ export default function FeatureFlagModal() {
<X size={24} />
</CloseButton>
</Header>
<FeatureFlagOption
variant={BaseVariant}
value={useFeesEnabledFlag()}
featureFlag={FeatureFlag.feesEnabled}
label="Enable Swap Fees"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useLimitsEnabledFlag()}
featureFlag={FeatureFlag.limitsEnabled}
label="Enable Limits"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useFallbackProviderEnabledFlag()}
@ -285,12 +295,6 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.multichainUX}
label="Updated Multichain UX"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useFotAdjustmentsFlag()}
featureFlag={FeatureFlag.fotAdjustedmentsEnabled}
label="Enable fee-on-transfer UI and slippage adjustments"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useProgressIndicatorV2Flag()}
@ -319,24 +323,6 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.uniswapXSyntheticQuote}
label="Force synthetic quotes for UniswapX"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useUniswapXEthOutputFlag()}
featureFlag={FeatureFlag.uniswapXEthOutputEnabled}
label="Enable eth output for UniswapX orders"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useUniswapXExactOutputFlag()}
featureFlag={FeatureFlag.uniswapXExactOutputEnabled}
label="Enable exact output for UniswapX orders"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useUniswapXDefaultEnabledFlag()}
featureFlag={FeatureFlag.uniswapXDefaultEnabled}
label="Enable UniswapX by default"
/>
</FeatureFlagGroup>
<FeatureFlagGroup name="Info Site Migration">
<FeatureFlagOption

@ -7,6 +7,7 @@ import { PoolState } from 'hooks/usePools'
import React from 'react'
import styled from 'styled-components'
import { ThemedText } from 'theme/components'
import { useFormatter } from 'utils/formatNumbers'
import { FeeTierPercentageBadge } from './FeeTierPercentageBadge'
import { FEE_AMOUNT_DETAIL } from './shared'
@ -30,12 +31,14 @@ interface FeeOptionProps {
}
export function FeeOption({ feeAmount, active, poolState, distributions, onClick }: FeeOptionProps) {
const { formatDelta } = useFormatter()
return (
<ButtonRadioChecked active={active} onClick={onClick}>
<AutoColumn gap="sm" justify="flex-start">
<AutoColumn justify="flex-start" gap="6px">
<ResponsiveText>
<Trans>{FEE_AMOUNT_DETAIL[feeAmount].label}%</Trans>
<Trans>{formatDelta(parseFloat(FEE_AMOUNT_DETAIL[feeAmount].label))}</Trans>
</ResponsiveText>
<ThemedText.DeprecatedMain fontWeight={485} fontSize="12px" textAlign="left">
{FEE_AMOUNT_DETAIL[feeAmount].description}

@ -16,6 +16,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Box } from 'rebass'
import styled, { keyframes } from 'styled-components'
import { ThemedText } from 'theme/components'
import { useFormatter } from 'utils/formatNumbers'
import { FeeOption } from './FeeOption'
import { FeeTierPercentageBadge } from './FeeTierPercentageBadge'
@ -62,6 +63,7 @@ export default function FeeSelector({
}) {
const { chainId } = useWeb3React()
const trace = useTrace()
const { formatDelta } = useFormatter()
const { isLoading, isError, largestUsageFeeTier, distributions } = useFeeTierDistribution(currencyA, currencyB)
@ -161,7 +163,7 @@ export default function FeeSelector({
) : (
<>
<ThemedText.DeprecatedLabel className="selected-fee-label">
<Trans>{FEE_AMOUNT_DETAIL[feeAmount].label}% fee tier</Trans>
<Trans>{formatDelta(parseFloat(FEE_AMOUNT_DETAIL[feeAmount].label))} fee tier</Trans>
</ThemedText.DeprecatedLabel>
<Box style={{ width: 'fit-content', marginTop: '8px' }} className="selected-fee-percentage">
{distributions && (

@ -1,6 +1,6 @@
import { ChainId, WETH9 } from '@uniswap/sdk-core'
import {
MATIC,
MATIC_MAINNET,
USDC_ARBITRUM,
USDC_MAINNET,
USDC_OPTIMISM,
@ -32,7 +32,7 @@ describe('getDefaultCurrencyCode', () => {
expect(getDefaultCurrencyCode('NATIVE', 'polygon')).toBe('matic_polygon')
})
it('MATIC/ethereum should return the correct currency code', () => {
expect(getDefaultCurrencyCode(MATIC.address, 'ethereum')).toBe('polygon')
expect(getDefaultCurrencyCode(MATIC_MAINNET.address, 'ethereum')).toBe('polygon')
})
it('USDC/arbitrum should return the correct currency code', () => {
expect(getDefaultCurrencyCode(USDC_ARBITRUM.address, 'arbitrum')).toBe('usdc_arbitrum')
@ -56,7 +56,7 @@ describe('getDefaultCurrencyCode', () => {
expect(getDefaultCurrencyCode(USDC_ARBITRUM.address, 'ethereum')).toBe('eth')
expect(getDefaultCurrencyCode(USDC_OPTIMISM.address, 'ethereum')).toBe('eth')
expect(getDefaultCurrencyCode(USDC_POLYGON.address, 'ethereum')).toBe('eth')
expect(getDefaultCurrencyCode(MATIC.address, 'arbitrum')).toBe('eth')
expect(getDefaultCurrencyCode(MATIC_MAINNET.address, 'arbitrum')).toBe('eth')
})
})

@ -1,6 +1,6 @@
import { ChainId, WETH9 } from '@uniswap/sdk-core'
import {
MATIC,
MATIC_MAINNET,
USDC_ARBITRUM,
USDC_MAINNET,
USDC_OPTIMISM,
@ -28,7 +28,7 @@ const CURRENCY_CODES: {
[USDC_MAINNET.address.toLowerCase()]: 'usdc',
[USDT.address.toLowerCase()]: 'usdt',
[WBTC.address.toLowerCase()]: 'wbtc',
[MATIC.address.toLowerCase()]: 'polygon',
[MATIC_MAINNET.address.toLowerCase()]: 'polygon',
native: 'eth',
},
[Chain.Arbitrum]: {

@ -0,0 +1,11 @@
import { ComponentProps } from 'react'
export const ExplorerIcon = (props: ComponentProps<'svg'>) => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
id="box-search_2"
d="M6.32064 7.43328C6.4873 7.50661 6.66065 7.55329 6.83398 7.59329V13.94C6.72732 13.9133 6.62067 13.8733 6.52067 13.8266L2.52067 12.0466C1.80067 11.7266 1.33398 11.0133 1.33398 10.2199V5.77996C1.33398 5.59996 1.36064 5.41995 1.40731 5.25329L6.32064 7.43328ZM12.754 4.37328C12.5807 4.19995 12.3806 4.05328 12.1473 3.95328L8.1473 2.17329C7.6273 1.93996 7.04067 1.93996 6.52067 2.17329L2.52067 3.95328C2.28734 4.05328 2.08731 4.19995 1.91398 4.37328L6.72062 6.51328C7.10729 6.68661 7.55401 6.68661 7.94735 6.51328L12.754 4.37328ZM11.1347 7.92862C11.8227 7.76662 12.4766 7.81863 13.0646 8.02129C13.1966 8.06663 13.334 7.97729 13.334 7.83729V5.77996C13.334 5.59996 13.3073 5.41995 13.2607 5.25329L8.34733 7.43328C8.18066 7.49995 8.00732 7.55329 7.83398 7.59329V13.94C7.84598 13.948 7.84599 13.9479 7.85799 13.9559L8.98665 13.452C9.10132 13.4006 9.13533 13.262 9.066 13.158C8.584 12.4373 8.37864 11.522 8.57397 10.5586C8.83464 9.27263 9.85802 8.22929 11.1347 7.92862ZM15.0207 14.3526C14.9233 14.4499 14.7953 14.4993 14.6673 14.4993C14.5393 14.4993 14.4113 14.4506 14.314 14.3526L13.2807 13.3193C12.896 13.5713 12.4386 13.7213 11.9453 13.7213C10.5973 13.7213 9.50065 12.6246 9.50065 11.2766C9.50065 9.92862 10.5973 8.83194 11.9453 8.83194C13.2926 8.83194 14.3893 9.92862 14.3893 11.2766C14.3893 11.77 14.24 12.228 13.988 12.6119L15.0213 13.6453C15.216 13.8406 15.216 14.1573 15.0207 14.3526ZM13.3893 11.2773C13.3893 10.4806 12.7413 9.83261 11.9453 9.83261C11.1486 9.83261 10.5007 10.4806 10.5007 11.2773C10.5007 12.0739 11.1486 12.722 11.9453 12.722C12.7413 12.7213 13.3893 12.0733 13.3893 11.2773Z"
fill="white"
/>
</svg>
)

@ -3,7 +3,6 @@ import { Currency, Price, Token } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import { AutoColumn, ColumnCenter } from 'components/Column'
import Loader from 'components/Icons/LoadingSpinner'
import { format } from 'd3'
import { useColor } from 'hooks/useColor'
import { saturate } from 'polished'
import { ReactNode, useCallback, useMemo } from 'react'
@ -12,6 +11,7 @@ import { batch } from 'react-redux'
import { Bound } from 'state/mint/v3/actions'
import styled, { useTheme } from 'styled-components'
import { ThemedText } from 'theme/components'
import { useFormatter } from 'utils/formatNumbers'
import { Chart } from './Chart'
import { useDensityChartData } from './hooks'
@ -142,6 +142,7 @@ export default function LiquidityChartRangeInput({
: undefined
}, [isSorted, priceLower, priceUpper])
const { formatDelta } = useFormatter()
const brushLabelValue = useCallback(
(d: 'w' | 'e', x: number) => {
if (!price) return ''
@ -151,9 +152,9 @@ export default function LiquidityChartRangeInput({
const percent = (x < price ? -1 : 1) * ((Math.max(x, price) - Math.min(x, price)) / price) * 100
return price ? `${format(Math.abs(percent) > 1 ? '.2~s' : '.2~f')(percent)}%` : ''
return price ? `${(Math.sign(percent) < 0 ? '-' : '') + formatDelta(percent)}` : ''
},
[isSorted, price, ticksAtLimit]
[formatDelta, isSorted, price, ticksAtLimit]
)
const isUninitialized = !currencyA || !currencyB || (formattedData === undefined && !isLoading)

@ -1,8 +0,0 @@
export const AppleLogo = (props: React.SVGProps<SVGSVGElement>) => (
<svg viewBox="0 0 814 1000" {...props}>
<path
fill="currentColor"
d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"
/>
</svg>
)

@ -79,6 +79,7 @@ export default function AssetLogo({
onLoad={() => void setImgLoaded(true)}
onError={nextSrc}
imgLoaded={imgLoaded}
loading="lazy"
/>
</LogoImageWrapper>
) : (

@ -1,16 +0,0 @@
import { style } from '@vanilla-extract/css'
import { lightGrayOverlayOnHover } from 'nft/css/common.css'
import { sprinkles } from '../../nft/css/sprinkles.css'
export const ChainSelector = style([
lightGrayOverlayOnHover,
sprinkles({
borderRadius: '20',
height: '36',
cursor: 'pointer',
border: 'none',
color: 'neutral1',
background: 'none',
}),
])

@ -2,6 +2,7 @@ import { t } from '@lingui/macro'
import { ChainId } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { showTestnetsAtom } from 'components/AccountDrawer/TestnetsToggle'
import { BaseButton } from 'components/Button'
import { ChainLogo } from 'components/Logo/ChainLogo'
import { MouseoverTooltip } from 'components/Tooltip'
import { getConnection } from 'connection'
@ -13,24 +14,38 @@ import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useSelectChain from 'hooks/useSelectChain'
import useSyncChainQuery from 'hooks/useSyncChainQuery'
import { useAtomValue } from 'jotai/utils'
import { Box } from 'nft/components/Box'
import { Portal } from 'nft/components/common/Portal'
import { Column, Row } from 'nft/components/Flex'
import { Column } from 'nft/components/Flex'
import { useIsMobile } from 'nft/hooks'
import { useCallback, useMemo, useRef, useState } from 'react'
import { AlertTriangle, ChevronDown, ChevronUp } from 'react-feather'
import { useTheme } from 'styled-components'
import styled, { useTheme } from 'styled-components'
import { getSupportedChainIdsFromWalletConnectSession } from 'utils/getSupportedChainIdsFromWalletConnectSession'
import * as styles from './ChainSelector.css'
import ChainSelectorRow from './ChainSelectorRow'
import { NavDropdown } from './NavDropdown'
const NETWORK_SELECTOR_CHAINS = [...L1_CHAIN_IDS, ...L2_CHAIN_IDS]
interface ChainSelectorProps {
leftAlign?: boolean
}
const ChainSelectorWrapper = styled.div`
position: relative;
`
const ChainSelectorButton = styled(BaseButton)<{ isOpen: boolean }>`
display: flex;
background: ${({ theme, isOpen }) => (isOpen ? theme.accent2 : 'none')};
padding: 10px 8px;
gap: 4px;
border-radius: 12px;
height: 40px;
color: ${({ theme }) => theme.neutral1};
transition: ${({ theme }) =>
`${theme.transition.duration.medium} ${theme.transition.timing.ease} background-color, ${theme.transition.duration.medium} ${theme.transition.timing.ease} margin`};
&:hover {
background-color: ${({ theme }) => theme.deprecated_stateOverlayHover};
}
`
function useWalletSupportedChains(): ChainId[] {
const { connector } = useWeb3React()
@ -45,7 +60,7 @@ function useWalletSupportedChains(): ChainId[] {
}
}
export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
export const ChainSelector = ({ leftAlign }: { leftAlign?: boolean }) => {
const { chainId } = useWeb3React()
const [isOpen, setIsOpen] = useState<boolean>(false)
const isMobile = useIsMobile()
@ -133,25 +148,18 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
}
return (
<Box position="relative" ref={ref}>
<ChainSelectorWrapper ref={ref}>
<MouseoverTooltip text={t`Your wallet's current network is unsupported.`} disabled={isSupported}>
<Row
data-testid="chain-selector"
as="button"
gap="8"
className={styles.ChainSelector}
background={isOpen ? 'accent2' : 'none'}
onClick={() => setIsOpen(!isOpen)}
>
<ChainSelectorButton data-testid="chain-selector" onClick={() => setIsOpen(!isOpen)} isOpen={isOpen}>
{!isSupported ? (
<AlertTriangle size={20} color={theme.neutral2} />
) : (
<ChainLogo chainId={chainId} size={24} testId="chain-selector-logo" />
<ChainLogo chainId={chainId} size={20} testId="chain-selector-logo" />
)}
{isOpen ? <ChevronUp {...chevronProps} /> : <ChevronDown {...chevronProps} />}
</Row>
</ChainSelectorButton>
</MouseoverTooltip>
{isOpen && (isMobile ? <Portal>{dropdown}</Portal> : <>{dropdown}</>)}
</Box>
</ChainSelectorWrapper>
)
}

@ -26,6 +26,7 @@ export const MenuRow = style([
{
lineHeight: '24px',
textDecoration: 'none',
alignItems: 'flex-start',
},
])

@ -20,10 +20,11 @@ import { ReactNode, useReducer, useRef } from 'react'
import { NavLink, NavLinkProps } from 'react-router-dom'
import { useToggleModal } from 'state/application/hooks'
import styled, { useTheme } from 'styled-components'
import { ThemedText } from 'theme/components'
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
import { openDownloadApp } from 'utils/openDownloadApp'
import { ReactComponent as AppleLogo } from '../../assets/svg/apple_logo.svg'
import { ReactComponent as UniswapAppLogo } from '../../assets/svg/uniswap_app_logo.svg'
import { ApplicationModal } from '../../state/application/reducer'
import * as styles from './MenuDropdown.css'
import { NavDropdown } from './NavDropdown'
@ -138,7 +139,7 @@ export const MenuDropdown = () => {
{isOpen && (
<NavDropdown top={{ sm: 'unset', lg: '56' }} bottom={{ sm: '50', lg: 'unset' }} right="0">
<Column gap="16">
<Column gap="8">
<Column paddingX="8" gap="4">
<Box display={{ sm: 'none', lg: 'flex', xxl: 'none' }}>
<PrimaryMenuRow to="/pool" close={toggleOpen}>
@ -150,22 +151,6 @@ export const MenuDropdown = () => {
</PrimaryMenuRow.Text>
</PrimaryMenuRow>
</Box>
<Box
onClick={() =>
openDownloadApp({
element: InterfaceElementName.UNISWAP_WALLET_MODAL_DOWNLOAD_BUTTON,
})
}
>
<PrimaryMenuRow close={toggleOpen}>
<Icon>
<AppleLogo width="24px" height="24px" fill={theme.neutral1} />
</Icon>
<PrimaryMenuRow.Text>
<Trans>Download Uniswap Wallet</Trans>
</PrimaryMenuRow.Text>
</PrimaryMenuRow>
</Box>
<PrimaryMenuRow to="/vote" close={toggleOpen}>
<Icon>
<GovernanceIcon width={24} height={24} color={theme.neutral1} />
@ -182,6 +167,29 @@ export const MenuDropdown = () => {
<Trans>View more analytics</Trans>
</PrimaryMenuRow.Text>
</PrimaryMenuRow>
<Box
onClick={() =>
openDownloadApp({
element: InterfaceElementName.UNISWAP_WALLET_NAVBAR_MENU_DOWNLOAD_BUTTON,
})
}
>
<PrimaryMenuRow close={toggleOpen}>
<>
<Icon>
<UniswapAppLogo width="24px" height="24px" />
</Icon>
<div>
<ThemedText.BodyPrimary>
<Trans>Download Uniswap</Trans>
</ThemedText.BodyPrimary>
<ThemedText.LabelSmall>
<Trans>Available on iOS and Android</Trans>
</ThemedText.LabelSmall>
</div>
</>
</PrimaryMenuRow>
</Box>
</Column>
<Separator />
<Box

@ -4,6 +4,7 @@ import clsx from 'clsx'
import QueryTokenLogo from 'components/Logo/QueryTokenLogo'
import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon'
import { checkSearchTokenWarning } from 'constants/tokenSafety'
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
import { Chain, TokenStandard } from 'graphql/data/__generated__/types-and-hooks'
import { SearchToken } from 'graphql/data/SearchTokens'
import { getTokenDetailsURL } from 'graphql/data/util'
@ -12,13 +13,11 @@ import { Column, Row } from 'nft/components/Flex'
import { VerifiedIcon } from 'nft/components/icons'
import { vars } from 'nft/css/sprinkles.css'
import { GenieCollection } from 'nft/types'
import { ethNumberStandardFormatter } from 'nft/utils/currency'
import { putCommas } from 'nft/utils/putCommas'
import { useCallback, useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import styled from 'styled-components'
import { ThemedText } from 'theme/components'
import { useFormatter } from 'utils/formatNumbers'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { DeltaArrow, DeltaText } from '../Tokens/TokenDetails/Delta'
import { useAddRecentlySearchedAsset } from './RecentlySearchedAssets'
@ -48,6 +47,8 @@ export const CollectionRow = ({
index,
eventProperties,
}: CollectionRowProps) => {
const { formatNumberOrString } = useFormatter()
const [brokenImage, setBrokenImage] = useState(false)
const [loaded, setLoaded] = useState(false)
@ -101,13 +102,17 @@ export const CollectionRow = ({
<Box className={styles.primaryText}>{collection.name}</Box>
{collection.isVerified && <VerifiedIcon className={styles.suggestionIcon} />}
</Row>
<Box className={styles.secondaryText}>{putCommas(collection?.stats?.total_supply ?? 0)} items</Box>
<Box className={styles.secondaryText}>
{formatNumberOrString({ input: collection?.stats?.total_supply, type: NumberType.WholeNumber })} items
</Box>
</Column>
</Row>
{collection.stats?.floor_price ? (
<Column className={styles.suggestionSecondaryContainer}>
<Row gap="4">
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.stats?.floor_price)} ETH</Box>
<Box className={styles.primaryText}>
{formatNumberOrString({ input: collection.stats?.floor_price, type: NumberType.NFTToken })} ETH
</Box>
</Row>
<Box className={styles.secondaryText}>Floor</Box>
</Column>
@ -128,7 +133,7 @@ interface TokenRowProps {
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, eventProperties }: TokenRowProps) => {
const addRecentlySearchedAsset = useAddRecentlySearchedAsset()
const navigate = useNavigate()
const { formatFiatPrice, formatPercent } = useFormatter()
const { formatFiatPrice, formatDelta } = useFormatter()
const handleClick = useCallback(() => {
const address = !token.address && token.standard === TokenStandard.Native ? 'NATIVE' : token.address
@ -138,7 +143,9 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
sendAnalyticsEvent(InterfaceEventName.NAVBAR_RESULT_SELECTED, { ...eventProperties })
}, [addRecentlySearchedAsset, token, toggleOpen, eventProperties])
const tokenDetailsPath = getTokenDetailsURL(token)
const isInfoExplorePageEnabled = useInfoExplorePageEnabled()
const tokenDetailsPath = getTokenDetailsURL({ ...token, isInfoExplorePageEnabled })
// Close the modal on escape
useEffect(() => {
const keyDownHandler = (event: KeyboardEvent) => {
@ -191,7 +198,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
<DeltaArrow delta={token.market?.pricePercentChange?.value} />
<ThemedText.BodySmall>
<DeltaText delta={token.market?.pricePercentChange?.value}>
{formatPercent(Math.abs(token.market?.pricePercentChange?.value ?? 0))}
{formatDelta(Math.abs(token.market?.pricePercentChange?.value ?? 0))}
</DeltaText>
</ThemedText.BodySmall>
</PriceChangeContainer>

@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { useAccountDrawer } from 'components/AccountDrawer'
import Web3Status from 'components/Web3Status'
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
import { chainIdToBackendName } from 'graphql/data/util'
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
import { useIsNftPage } from 'hooks/useIsNftPage'
@ -61,15 +62,22 @@ export const PageTabs = () => {
const isNftPage = useIsNftPage()
const shouldDisableNFTRoutes = useDisableNFTRoutes()
const infoExplorePageEnabled = useInfoExplorePageEnabled()
return (
<>
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
<Trans>Swap</Trans>
</MenuItem>
<MenuItem href={`/tokens/${chainName.toLowerCase()}`} isActive={pathname.startsWith('/tokens')}>
<Trans>Tokens</Trans>
</MenuItem>
{infoExplorePageEnabled ? (
<MenuItem href={`/explore/tokens/${chainName.toLowerCase()}`} isActive={pathname.startsWith('/explore')}>
<Trans>Explore</Trans>
</MenuItem>
) : (
<MenuItem href={`/tokens/${chainName.toLowerCase()}`} isActive={pathname.startsWith('/tokens')}>
<Trans>Tokens</Trans>
</MenuItem>
)}
{!shouldDisableNFTRoutes && (
<MenuItem dataTestId="nft-nav" href="/nfts" isActive={isNftPage}>
<Trans>NFTs</Trans>

@ -1,9 +1,36 @@
import userEvent from '@testing-library/user-event'
import store from 'state'
import { addSerializedToken } from 'state/user/reducer'
import { act, render, screen } from 'test-utils/render'
import { PoolDetailsHeader } from './PoolDetailsHeader'
describe('PoolDetailsHeader', () => {
beforeEach(() => {
store.dispatch(
addSerializedToken({
serializedToken: {
chainId: 1,
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
symbol: 'USDC',
name: 'USD Coin',
decimals: 6,
},
})
)
store.dispatch(
addSerializedToken({
serializedToken: {
chainId: 1,
address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
symbol: 'WETH',
name: 'Wrapped Ether',
decimals: 18,
},
})
)
})
const mockProps = {
chainId: 1,
poolAddress: '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640',
@ -13,6 +40,13 @@ describe('PoolDetailsHeader', () => {
toggleReversed: jest.fn(),
}
it('loading skeleton is shown', () => {
const { asFragment } = render(<PoolDetailsHeader {...mockProps} loading={true} />)
expect(asFragment()).toMatchSnapshot()
expect(screen.getByTestId('pdp-header-loading-skeleton')).toBeInTheDocument()
})
it('renders header text correctly', () => {
const { asFragment } = render(<PoolDetailsHeader {...mockProps} />)
expect(asFragment()).toMatchSnapshot()

@ -4,6 +4,7 @@ import blankTokenUrl from 'assets/svg/blank_token.svg'
import Column from 'components/Column'
import { ChainLogo } from 'components/Logo/ChainLogo'
import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading'
import { BIPS_BASE } from 'constants/misc'
import { chainIdToBackendName } from 'graphql/data/util'
import { useCurrency } from 'hooks/Tokens'
@ -15,6 +16,7 @@ import { ClickableStyle, ThemedText } from 'theme/components'
import { shortenAddress } from 'utils'
import { ReversedArrowsIcon } from './icons'
import { DetailBubble } from './shared'
const HeaderColumn = styled(Column)`
gap: 36px;
@ -35,6 +37,12 @@ const ToggleReverseArrows = styled(ReversedArrowsIcon)`
${ClickableStyle}
`
const IconBubble = styled(LoadingBubble)`
width: 32px;
height: 32px;
border-radius: 50%;
`
interface Token {
id: string
symbol: string
@ -47,6 +55,7 @@ interface PoolDetailsHeaderProps {
token1?: Token
feeTier?: number
toggleReversed: React.DispatchWithoutAction
loading?: boolean
}
export function PoolDetailsHeader({
@ -56,10 +65,25 @@ export function PoolDetailsHeader({
token1,
feeTier,
toggleReversed,
loading,
}: PoolDetailsHeaderProps) {
const currencies = [useCurrency(token0?.id, chainId) ?? undefined, useCurrency(token1?.id, chainId) ?? undefined]
const chainName = chainIdToBackendName(chainId)
const origin = `/tokens/${chainName}`
if (loading)
return (
<HeaderColumn data-testid="pdp-header-loading-skeleton">
<DetailBubble $width={300} />
<Column gap="sm">
<Row gap="8px">
<IconBubble />
<DetailBubble $width={137} />
</Row>
</Column>
</HeaderColumn>
)
return (
<HeaderColumn>
<Row>
@ -101,7 +125,6 @@ const StyledLogoParentContainer = styled.div`
top: 0;
left: 0;
`
function DoubleCurrencyAndChainLogo({
chainId,
currencies,
@ -141,37 +164,45 @@ function SquareL2Logo({ chainId }: { chainId: ChainId }) {
)
}
function DoubleCurrencyLogo({ chainId, currencies }: { chainId: number; currencies: Array<Currency | undefined> }) {
export function DoubleCurrencyLogo({
chainId,
currencies,
small,
}: {
chainId: number
currencies: Array<Currency | undefined>
small?: boolean
}) {
const [src, nextSrc] = useTokenLogoSource(currencies?.[0]?.wrapped.address, chainId, currencies?.[0]?.isNative)
const [src2, nextSrc2] = useTokenLogoSource(currencies?.[1]?.wrapped.address, chainId, currencies?.[1]?.isNative)
return <DoubleLogo logo1={src} onError1={nextSrc} logo2={src2} onError2={nextSrc2} />
return <DoubleLogo logo1={src} onError1={nextSrc} logo2={src2} onError2={nextSrc2} small={small} />
}
const DoubleLogoContainer = styled.div`
const DoubleLogoContainer = styled.div<{ small?: boolean }>`
display: flex;
gap: 2px;
position: relative;
top: 0;
left: 0;
img {
width: 16px;
height: 32px;
width: ${({ small }) => (small ? '10px' : '16px')};
height: ${({ small }) => (small ? '20px' : '32px')};
object-fit: cover;
}
img:first-child {
border-radius: 16px 0 0 16px;
border-radius: ${({ small }) => (small ? '10px 0 0 10px' : '16px 0 0 16px')};
object-position: 0 0;
}
img:last-child {
border-radius: 0 16px 16px 0;
border-radius: ${({ small }) => (small ? '0 10px 10px 0' : '0 16px 16px 0')};
object-position: 100% 0;
}
`
const CircleLogoImage = styled.img`
width: 32px;
height: 32px;
const CircleLogoImage = styled.img<{ small?: boolean }>`
width: ${({ small }) => (small ? '10px' : '16px')};
height: ${({ small }) => (small ? '20px' : '32px')};
border-radius: 50%;
`
@ -180,13 +211,14 @@ interface DoubleLogoProps {
logo2?: string
onError1?: () => void
onError2?: () => void
small?: boolean
}
function DoubleLogo({ logo1, onError1, logo2, onError2 }: DoubleLogoProps) {
function DoubleLogo({ logo1, onError1, logo2, onError2, small }: DoubleLogoProps) {
return (
<DoubleLogoContainer>
<CircleLogoImage src={logo1 ?? blankTokenUrl} onError={onError1} />
<CircleLogoImage src={logo2 ?? blankTokenUrl} onError={onError2} />
<DoubleLogoContainer small={small}>
<CircleLogoImage src={logo1 ?? blankTokenUrl} onError={onError1} small={small} />
<CircleLogoImage src={logo2 ?? blankTokenUrl} onError={onError2} small={small} />
</DoubleLogoContainer>
)
}

@ -0,0 +1,63 @@
import { ChainId } from '@uniswap/sdk-core'
import { USDC_MAINNET } from 'constants/tokens'
import store from 'state'
import { addSerializedToken } from 'state/user/reducer'
import { usdcWethPoolAddress, validPoolToken0, validPoolToken1 } from 'test-utils/pools/fixtures'
import { render, screen } from 'test-utils/render'
import { PoolDetailsLink } from './PoolDetailsLink'
describe('PoolDetailsHeader', () => {
beforeEach(() => {
store.dispatch(
addSerializedToken({
serializedToken: {
chainId: 1,
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
symbol: 'USDC',
name: 'USD Coin',
decimals: 6,
},
})
)
store.dispatch(
addSerializedToken({
serializedToken: {
chainId: 1,
address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
symbol: 'WETH',
name: 'Wrapped Ether',
decimals: 18,
},
})
)
})
it('renders link for pool address', async () => {
const { asFragment } = render(
<PoolDetailsLink
address={usdcWethPoolAddress}
chainId={ChainId.MAINNET}
tokens={[validPoolToken0, validPoolToken1]}
/>
)
expect(asFragment()).toMatchSnapshot()
expect(screen.getByText('USDC / WETH')).toBeInTheDocument()
expect(screen.getByTestId('pdp-pool-logo-USDC-WETH')).toBeInTheDocument()
expect(screen.getByTestId(`copy-address-${usdcWethPoolAddress}`)).toBeInTheDocument()
expect(screen.getByTestId(`explorer-url-https://etherscan.io/address/${usdcWethPoolAddress}`)).toBeInTheDocument()
})
it('renders link for token address', async () => {
const { asFragment } = render(
<PoolDetailsLink address={USDC_MAINNET.address} chainId={ChainId.MAINNET} tokens={[validPoolToken0]} />
)
expect(asFragment()).toMatchSnapshot()
expect(screen.getByText('USDC')).toBeInTheDocument()
expect(screen.getByTestId('pdp-token-logo-USDC')).toBeInTheDocument()
expect(screen.getByTestId(`copy-address-${USDC_MAINNET.address}`)).toBeInTheDocument()
expect(screen.getByTestId(`explorer-url-https://etherscan.io/token/${USDC_MAINNET.address}`)).toBeInTheDocument()
})
})

@ -0,0 +1,165 @@
import { Trans } from '@lingui/macro'
import { ChainId } from '@uniswap/sdk-core'
import { EtherscanLogo } from 'components/Icons/Etherscan'
import { ExplorerIcon } from 'components/Icons/ExplorerIcon'
import CurrencyLogo from 'components/Logo/CurrencyLogo'
import Row from 'components/Row'
import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore'
import { chainIdToBackendName, getTokenDetailsURL } from 'graphql/data/util'
import { Token } from 'graphql/thegraph/__generated__/types-and-hooks'
import { useCurrency } from 'hooks/Tokens'
import useCopyClipboard from 'hooks/useCopyClipboard'
import { useCallback } from 'react'
import { ChevronRight, Copy } from 'react-feather'
import { useNavigate } from 'react-router-dom'
import styled, { useTheme } from 'styled-components'
import { BREAKPOINTS } from 'theme'
import { ClickableStyle, EllipsisStyle, ExternalLink, ThemedText } from 'theme/components'
import { shortenAddress } from 'utils'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
import { DoubleCurrencyLogo } from './PoolDetailsHeader'
import { DetailBubble, SmallDetailBubble } from './shared'
const TokenName = styled(ThemedText.BodyPrimary)`
display: none;
@media (max-width: ${BREAKPOINTS.lg - 1}px) and (min-width: ${BREAKPOINTS.xs - 1}px) {
display: block;
}
${EllipsisStyle}
`
const TokenTextWrapper = styled(Row)<{ isClickable?: boolean }>`
gap: 8px;
margin-right: 12px;
${({ isClickable }) => isClickable && ClickableStyle}
`
const SymbolText = styled(ThemedText.BodyPrimary)`
flex-shrink: 0;
@media (max-width: ${BREAKPOINTS.lg - 1}px) and (min-width: ${BREAKPOINTS.xs - 1}px) {
color: ${({ theme }) => theme.neutral2};
}
${EllipsisStyle}
`
const CopyAddress = styled(Row)`
gap: 8px;
padding: 8px 12px;
border-radius: 20px;
background-color: ${({ theme }) => theme.surface3};
font-size: 14px;
font-weight: 535;
line-height: 16px;
width: max-content;
flex-shrink: 0;
${ClickableStyle}
`
const StyledCopyIcon = styled(Copy)`
width: 16px;
height: 16px;
color: ${({ theme }) => theme.neutral2};
flex-shrink: 0;
`
const ExplorerWrapper = styled.div`
padding: 8px;
border-radius: 20px;
background-color: ${({ theme }) => theme.surface3};
display: flex;
${ClickableStyle}
`
const ButtonsRow = styled(Row)`
gap: 8px;
flex-shrink: 0;
width: max-content;
`
interface PoolDetailsLinkProps {
address?: string
chainId?: number
tokens: (Token | undefined)[]
loading?: boolean
}
export function PoolDetailsLink({ address, chainId, tokens, loading }: PoolDetailsLinkProps) {
const theme = useTheme()
const currencies = [
useCurrency(tokens[0]?.id, chainId) ?? undefined,
useCurrency(tokens[1]?.id, chainId) ?? undefined,
]
const [, setCopied] = useCopyClipboard()
const copy = useCallback(() => {
address && setCopied(address)
}, [address, setCopied])
const isPool = tokens.length === 2
const explorerUrl =
address && chainId && getExplorerLink(chainId, address, isPool ? ExplorerDataType.ADDRESS : ExplorerDataType.TOKEN)
const navigate = useNavigate()
const isInfoExplorePageEnabled = useInfoExplorePageEnabled()
const chainName = chainIdToBackendName(chainId)
const handleTokenTextClick = useCallback(() => {
if (!isPool) {
navigate(getTokenDetailsURL({ address: tokens[0]?.id, chain: chainName, isInfoExplorePageEnabled }))
}
}, [navigate, tokens, isPool, chainName, isInfoExplorePageEnabled])
if (loading || !address || !chainId) {
return (
<Row gap="8px" padding="6px 0px">
<SmallDetailBubble />
<DetailBubble $width={117} />
</Row>
)
}
return (
<Row align="space-between">
<TokenTextWrapper
data-testid={
isPool ? `pdp-pool-logo-${tokens[0]?.symbol}-${tokens[1]?.symbol}` : `pdp-token-logo-${tokens[0]?.symbol}`
}
isClickable={!isPool}
onClick={handleTokenTextClick}
>
{isPool ? (
<DoubleCurrencyLogo chainId={chainId} currencies={currencies} small />
) : (
<CurrencyLogo currency={currencies[0]} size="20px" />
)}
<TokenName>{isPool ? <Trans>Pool</Trans> : tokens[0]?.name}</TokenName>
<SymbolText>
{isPool ? (
`${tokens[0]?.symbol} / ${tokens[1]?.symbol}`
) : (
<Row gap="4px">
{tokens[0]?.symbol} <ChevronRight size={16} color={theme.neutral2} />
</Row>
)}
</SymbolText>
</TokenTextWrapper>
<ButtonsRow>
<CopyAddress data-testid={`copy-address-${address}`} onClick={copy}>
{shortenAddress(address)}
<StyledCopyIcon />
</CopyAddress>
{explorerUrl && (
<ExternalLink href={explorerUrl} data-testid={`explorer-url-${explorerUrl}`}>
<ExplorerWrapper>
{chainId === ChainId.MAINNET ? (
<EtherscanLogo width="16px" height="16px" fill={theme.neutral2} />
) : (
<ExplorerIcon width="16px" height="16px" stroke={theme.darkMode ? 'none' : theme.neutral2} />
)}
</ExplorerWrapper>
</ExternalLink>
)}
</ButtonsRow>
</Row>
)
}

@ -1,4 +1,6 @@
import { enableNetConnect } from 'nock'
import store from 'state'
import { addSerializedToken } from 'state/user/reducer'
import { validPoolDataResponse } from 'test-utils/pools/fixtures'
import { act, render, screen } from 'test-utils/render'
import { BREAKPOINTS } from 'theme'
@ -15,6 +17,28 @@ describe('PoolDetailsStats', () => {
beforeEach(() => {
// Enable network connections for retrieving token logos
enableNetConnect()
store.dispatch(
addSerializedToken({
serializedToken: {
chainId: 1,
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
symbol: 'USDC',
name: 'USD Coin',
decimals: 6,
},
})
)
store.dispatch(
addSerializedToken({
serializedToken: {
chainId: 1,
address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
symbol: 'WETH',
name: 'Wrapped Ether',
decimals: 18,
},
})
)
})
it('renders stats text correctly', async () => {

@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
import Column from 'components/Column'
import CurrencyLogo from 'components/Logo/CurrencyLogo'
import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading'
import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta'
import { PoolData } from 'graphql/thegraph/PoolData'
import { useCurrency } from 'hooks/Tokens'
@ -15,6 +16,8 @@ import { colors } from 'theme/colors'
import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { DetailBubble } from './shared'
const HeaderText = styled(Text)`
font-weight: 485;
font-size: 24px;
@ -90,13 +93,25 @@ const BalanceChartSide = styled.div<{ percent: number; $color: string; isLeft: b
${({ isLeft }) => (isLeft ? leftBarChartStyles : rightBarChartStyles)}
`
const StatSectionBubble = styled(LoadingBubble)`
width: 180px;
height: 40px;
`
const StatHeaderBubble = styled(LoadingBubble)`
width: 116px;
height: 24px;
border-radius: 8px;
`
interface PoolDetailsStatsProps {
poolData: PoolData
isReversed: boolean
poolData?: PoolData
isReversed?: boolean
chainId?: number
loading?: boolean
}
export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsStatsProps) {
export function PoolDetailsStats({ poolData, isReversed, chainId, loading }: PoolDetailsStatsProps) {
const isScreenSize = useScreenSize()
const screenIsNotLarge = isScreenSize['lg']
const { formatNumber } = useFormatter()
@ -112,26 +127,46 @@ export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsS
}
const [token0, token1] = useMemo(() => {
const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1
const token0FullData = {
...poolData?.token0,
price: poolData?.token0Price,
tvl: poolData?.tvlToken0,
color: color0,
percent: poolData?.tvlToken0 / poolData?.token0Price / fullWidth,
currency: currency0,
if (poolData) {
const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1
const token0FullData = {
...poolData?.token0,
price: poolData?.token0Price,
tvl: poolData?.tvlToken0,
color: color0,
percent: poolData?.tvlToken0 / poolData?.token0Price / fullWidth,
currency: currency0,
}
const token1FullData = {
...poolData?.token1,
price: poolData?.token1Price,
tvl: poolData?.tvlToken1,
color: color1,
percent: poolData?.tvlToken1 / fullWidth,
currency: currency1,
}
return isReversed ? [token1FullData, token0FullData] : [token0FullData, token1FullData]
} else {
return [undefined, undefined]
}
const token1FullData = {
...poolData?.token1,
price: poolData?.token1Price,
tvl: poolData?.tvlToken1,
color: color1,
percent: poolData?.tvlToken1 / fullWidth,
currency: currency1,
}
return isReversed ? [token1FullData, token0FullData] : [token0FullData, token1FullData]
}, [color0, color1, currency0, currency1, isReversed, poolData])
if (loading || !token0 || !token1 || !poolData) {
return (
<StatsWrapper>
<HeaderText>
<StatHeaderBubble />
</HeaderText>
{Array.from({ length: 4 }).map((_, i) => (
<Column gap="md" key={`loading-info-row-${i}`}>
<DetailBubble />
<StatSectionBubble />
</Column>
))}
</StatsWrapper>
)
}
return (
<StatsWrapper>
<HeaderText>
@ -204,7 +239,7 @@ const StatItemText = styled(Text)`
`
function StatItem({ title, value, delta }: { title: ReactNode; value: number; delta?: number }) {
const { formatNumber, formatPercent } = useFormatter()
const { formatNumber, formatDelta } = useFormatter()
return (
<StatItemColumn>
@ -219,7 +254,7 @@ function StatItem({ title, value, delta }: { title: ReactNode; value: number; de
{!!delta && (
<Row width="max-content" padding="4px 0px">
<DeltaArrow delta={delta} />
<ThemedText.BodySecondary>{formatPercent(delta)}</ThemedText.BodySecondary>
<ThemedText.BodySecondary>{formatDelta(delta)}</ThemedText.BodySecondary>
</Row>
)}
</StatsTextContainer>

@ -1,5 +1,7 @@
import userEvent from '@testing-library/user-event'
import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions'
import store from 'state'
import { addSerializedToken } from 'state/user/reducer'
import { mocked } from 'test-utils/mocked'
import { useMultiChainPositionsReturnValue, validPoolToken0, validPoolToken1 } from 'test-utils/pools/fixtures'
import { act, render, screen } from 'test-utils/render'
@ -24,6 +26,35 @@ describe('PoolDetailsStatsButton', () => {
beforeEach(() => {
mocked(useMultiChainPositions).mockReturnValue(useMultiChainPositionsReturnValue)
store.dispatch(
addSerializedToken({
serializedToken: {
chainId: 1,
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
symbol: 'USDC',
name: 'USD Coin',
decimals: 6,
},
})
)
store.dispatch(
addSerializedToken({
serializedToken: {
chainId: 1,
address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
symbol: 'WETH',
name: 'Wrapped Ether',
decimals: 18,
},
})
)
})
it('loading skeleton shown correctly', () => {
const { asFragment } = render(<PoolDetailsStatsButtons {...mockProps} loading={true} />)
expect(asFragment()).toMatchSnapshot()
expect(screen.getByTestId('pdp-buttons-loading-skeleton')).toBeVisible()
})
it('renders both buttons correctly', () => {
@ -57,7 +88,7 @@ describe('PoolDetailsStatsButton', () => {
await act(() => userEvent.click(screen.getByTestId('pool-details-add-liquidity-button')))
expect(global.window.location.href).toContain(
'/increase/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/500'
'/add/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/500'
)
})
})

@ -4,6 +4,7 @@ import { PositionInfo } from 'components/AccountDrawer/MiniPortfolio/Pools/cache
import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions'
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading'
import { Token } from 'graphql/thegraph/__generated__/types-and-hooks'
import { useCurrency } from 'hooks/Tokens'
import { useSwitchChain } from 'hooks/useSwitchChain'
@ -26,11 +27,18 @@ const PoolButton = styled(ThemeButton)`
width: 50%;
`
const ButtonBubble = styled(LoadingBubble)`
height: 44px;
width: 175px;
border-radius: 900px;
`
interface PoolDetailsStatsButtonsProps {
chainId?: number
token0?: Token
token1?: Token
feeTier?: number
loading?: boolean
}
function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1?: Token, feeTier?: number) {
@ -45,7 +53,7 @@ function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1?
)
}
export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: PoolDetailsStatsButtonsProps) {
export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier, loading }: PoolDetailsStatsButtonsProps) {
const { chainId: walletChainId, connector, account } = useWeb3React()
const { positions: userOwnedPositions } = useMultiChainPositions(account ?? '', chainId ? [chainId] : undefined)
const position = userOwnedPositions && findMatchingPosition(userOwnedPositions, token0, token1, feeTier)
@ -60,11 +68,19 @@ export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: Po
navigate(
toSwap
? `/swap?inputCurrency=${currencyId(currency0)}&outputCurrency=${currencyId(currency1)}`
: `/increase/${currencyId(currency0)}/${currencyId(currency1)}/${feeTier}${tokenId ? `/${tokenId}` : ''}`
: `/add/${currencyId(currency0)}/${currencyId(currency1)}/${feeTier}${tokenId ? `/${tokenId}` : ''}`
)
}
}
if (!currency0 || !currency1) return null
if (loading || !currency0 || !currency1)
return (
<PoolDetailsStatsButtonsRow data-testid="pdp-buttons-loading-skeleton">
<ButtonBubble />
<ButtonBubble />
</PoolDetailsStatsButtonsRow>
)
return (
<PoolDetailsStatsButtonsRow>
<PoolButton
@ -75,7 +91,6 @@ export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: Po
>
<Trans>Add liquidity</Trans>
</PoolButton>
<PoolButton
size={ButtonSize.medium}
emphasis={ButtonEmphasis.highSoft}

@ -0,0 +1,98 @@
import { Trans } from '@lingui/macro'
import Column from 'components/Column'
import { ScrollBarStyles } from 'components/Common'
import Row from 'components/Row'
import { ArrowDown } from 'react-feather'
import styled from 'styled-components'
import { ThemedText } from 'theme/components'
import { DetailBubble, SmallDetailBubble } from './shared'
const Table = styled(Column)`
gap: 24px;
border-radius: 20px;
border: 1px solid ${({ theme }) => theme.surface3};
padding-bottom: 12px;
overflow-y: hidden;
${ScrollBarStyles}
`
const TableRow = styled(Row)<{ $borderBottom?: boolean }>`
justify-content: space-between;
border-bottom: ${({ $borderBottom, theme }) => ($borderBottom ? `1px solid ${theme.surface3}` : 'none')}};
padding: 12px;
min-width: max-content;
`
const TableElement = styled(ThemedText.BodySecondary)<{
alignRight?: boolean
small?: boolean
large?: boolean
}>`
display: flex;
padding: 0px 8px;
flex: ${({ small }) => (small ? 'unset' : '1')};
width: ${({ small }) => (small ? '44px' : 'auto')};
min-width: ${({ large, small }) => (large ? '136px' : small ? 'unset' : '121px')} !important;
justify-content: ${({ alignRight }) => (alignRight ? 'flex-end' : 'flex-start')};
`
{
/* TODO(WEB-2735): When making real datatable, merge in this code and deprecate this skeleton file */
}
export function PoolDetailsTableSkeleton() {
return (
<Table $isHorizontalScroll>
<TableRow $borderBottom>
<TableElement large>
<Row>
<ArrowDown size={16} />
<Trans>Time</Trans>
</Row>
</TableElement>
<TableElement>
<Trans>Type</Trans>
</TableElement>
<TableElement alignRight>
<Trans>USD</Trans>
</TableElement>
<TableElement alignRight>
<DetailBubble />
</TableElement>
<TableElement alignRight>
<DetailBubble />
</TableElement>
<TableElement alignRight>
<Trans>Maker</Trans>
</TableElement>
<TableElement alignRight small>
<Trans>Txn</Trans>
</TableElement>
</TableRow>
{Array.from({ length: 10 }).map((_, i) => (
<TableRow key={`loading-table-row-${i}`}>
<TableElement large>
<DetailBubble />
</TableElement>
<TableElement>
<DetailBubble />
</TableElement>
<TableElement alignRight>
<DetailBubble />
</TableElement>
<TableElement alignRight>
<DetailBubble />
</TableElement>
<TableElement alignRight>
<DetailBubble />
</TableElement>
<TableElement alignRight>
<DetailBubble />
</TableElement>
<TableElement alignRight small>
<SmallDetailBubble />
</TableElement>
</TableRow>
))}
</Table>
)
}

Some files were not shown because too many files have changed in this diff Show More