Compare commits

...

187 Commits

Author SHA1 Message Date
Charles Bachmeier
ecd208176b fix: handle both UNIWALLET and UNISWAP_WALLET in LocalStorage (#6767)
fix uniwallet bug

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-06-14 14:21:07 -07:00
eddie
ac93d42113 fix: remove sentry logging for swap failures (#6766) 2023-06-14 12:00:30 -07:00
UL Service Account
fca93af230 ci: add global CODEOWNERS 2023-06-09 19:00:49 +00:00
Zach Pomerantz
4235b57cd8 build: make codecov reports consistent/not confusing (#6734)
* build: codecov config

* build: better codecov

* build: omit default

* build: sensical codecov

* build: upload-artifact

* build: customize codecov comment

* build: try again

* build: try again

* build: turn off status except flags

* build: flag comments

* build: changes on

* build: simplify

* build: after_n for comment
2023-06-09 11:00:27 -07:00
Charles Bachmeier
cb362f1b2c fix: Handle scientific notation for NFT Details pricing (#6707)
* fix: Handle scientific notation for NFT Details pricing

* add test case

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-06-09 09:10:40 -07:00
Jack Short
a4d61d8eaa fix: insufficient liquidity state flashes (#6733) 2023-06-09 12:10:13 -04:00
Jack Short
310623b948 fix: language updating in search bar (#6720) 2023-06-09 11:56:20 -04:00
Shubham Rasal
b7303fb9c0 style: navbar ui updated (#6627)
* ui: navbar ui updated

* added margin right

* moved style to higher component
2023-06-09 08:52:52 -07:00
Jack Short
4fbb8e9117 chore: removing error state for rejecting chain switch (#6713)
* chore: removing error state for rejecting chain switch

* removing connection error state
2023-06-09 11:45:35 -04:00
Zach Pomerantz
f0502bfc33 build: rm old release workflow (#6731) 2023-06-08 14:52:24 -07:00
eddie
98f4af55c9 fix: stop resetting modal when we interrupt quote polling (#6695)
* test(e2e): disable video compression

* refactor: improve popupList impl/test

* test(e2e): log JSON-RPC calls

* fix: retry backoff logic

* test(e2e): wait for hardhat/popup assertions

* fix: remove transactions after expired

* test(e2e): re-enable other swap tests

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

* chore: rm console.log

* fix: expire txs after 6h

* refactor: dry trade info

* test(e2e): use default deadline

* test(e2e): use aliases for permit2 test

* test(e2e): automine/off in beforeEach

* test(e2e): rm intermediate screen with no pause

* fix: stop resetting modal when we interrupt quote polling'

* feat: add e2e test for window losing focus

* fix: move test

* fix: combine tests

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2023-06-08 13:44:47 -07:00
Jordan Frankfurt
08b8bdd769 chore: update @web3-react/walletconnect-v2 (#6724) 2023-06-08 14:20:14 -05:00
Jordan Frankfurt
1283199d0d fix: remove mainnet from optional chains list (#6725)
* fix: remove mainnet from optional chains list

* simplify imports
2023-06-08 14:09:41 -05:00
Jack Short
c1fff5ea49 fix: time interval axis overlap (#6717) 2023-06-08 13:26:25 -04:00
cartcrom
94adc449a1 refactor: convert getConnection to non-hook (#6714)
* refactor: convert getConnection to non-hook

* fix: remove getConnection from hook dependecies

* fix: unecessary optional
2023-06-08 13:06:46 -04:00
eddie
4b24e5f754 fix: remove step indicators in sucess state (#6716) 2023-06-08 09:14:39 -07:00
eddie
e0a531e538 fix: type error in cypress infra (#6700) 2023-06-07 18:16:57 -07:00
eddie
8cef1ca0f7 fix: bottom padding on swap modal (#6715)
<!-- 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:_ https://linear.app/uniswap/issue/WEB-2151/permit2-fix-dialog-window-height


<!-- Delete this section if your change does not affect UI. -->
## Screen capture

### Before
|    Mobile    |   Desktop    |
| ------------ | ------------ |
| paste_before | <img width="516" alt="image" src="https://github.com/Uniswap/interface/assets/66155195/4b7320c3-9bd3-4c5c-8d28-341a5ae7fa4f"> |


### After
|    Mobile    |   Desktop   |
| ------------ | ----------- |
| paste_after  | ![image](https://github.com/Uniswap/interface/assets/66155195/8b112a05-39d5-4a01-b412-47a7d74d040f) |
2023-06-07 18:09:20 -07:00
Zach Pomerantz
088f1d9ae4 test(e2e): use aliases for permit2 test (#6656)
* test(e2e): disable video compression

* refactor: improve popupList impl/test

* test(e2e): log JSON-RPC calls

* fix: retry backoff logic

* test(e2e): wait for hardhat/popup assertions

* fix: remove transactions after expired

* test(e2e): re-enable other swap tests

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

* chore: rm console.log

* fix: expire txs after 6h

* refactor: dry trade info

* test(e2e): use default deadline

* test(e2e): use aliases for permit2 test

* test(e2e): automine/off in beforeEach

* test(e2e): rm intermediate screen with no pause

* fix merge

* fix only

---------

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2023-06-07 17:55:27 -07:00
Nate Wienert
89a7d98b41 ci: fix wallet disconnect test regression (#6723) 2023-06-07 13:17:58 -10:00
Zach Pomerantz
0076fdc65b test(e2e): skip outdated disconnect-wallet test (#6722) 2023-06-07 16:57:37 -04:00
Nate Wienert
48b4a533c3 feat: disconnect button has confirmation step with animated width transition to show confirm text (#6668)
* feat: changes disconnect button to a two-step animated button with confirmation

* fix: use ConfirmSwapModal in expert mode (#6673)

Fixes the swap flow for users who are still in expert mode  and need permit2 approvals for a token

---------

Co-authored-by: eddie <66155195+just-toby@users.noreply.github.com>
2023-06-07 09:37:32 -10:00
Zach Pomerantz
0b66fde26c fix: only remove expired transactions from updater (#6625)
* test(e2e): disable video compression

* refactor: improve popupList impl/test

* test(e2e): log JSON-RPC calls

* fix: retry backoff logic

* test(e2e): wait for hardhat/popup assertions

* fix: remove transactions after expired

* test(e2e): re-enable other swap tests

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

* chore: rm console.log

* fix: expire txs after 6h

* refactor: dry trade info

* test(e2e): use default deadline

---------

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2023-06-07 09:29:23 -07:00
Jordan Frankfurt
5788385951 feat(wallet-connect): add support for v2 (#6582)
* feat(wallet-connect): add support for v2

* use theme button to fix opacity issue

* fix lint

* add new web3-react v2 package

* add mainnet to chains list

* fix test

* yarn dedupe

* add new @walletconnect/ethereum-provider

* fix safari padding

* fix second-click flash on popover toggle

* add walletconnect theme

* add goerli to non-prod chain selector

* remove: debug

* remove vertical line

* WEB-2107 Fix modal close behavior

* remove logging

* clean up accountDrawerOpenAtom usage

* remove irrelevant comments

* remove unintentional whitespace diff

* yarn yarn-deduplicate --strategy=highest

* add conditional chain selector

* update wc package version

* goerli -> sepolia

* goerli -> sepolia

* yarn yarn-deduplicate --strategy=highest

* UNIWALLET -> UNISWAP_WALLET

* useWalletSupportsChain -> useWalletSupportedChains

* use TOGGLE_SIZE

* remove inline styles

* remove inline styles and use better alt text

* update Option.test

* use a named function for forwardRef arg

* fix types

---------

Co-authored-by: Jordan Frankfurt <jordan@CORN-Jordan-949.frankfurt>
2023-06-07 11:11:08 -05:00
Brendan Wong
0891e67528 fix: token bridge pointer bug (#6670)
* fix: token bridge pointer bug

* fix: use zIndex enum instead of hardcoded
2023-06-07 10:18:48 -04:00
Brendan Wong
b319acd9c4 fix: info labels for extended list tokens (#6678)
* fix: info labels for extended list tokens

* test: update warning tests for new changes

* fix: function for label and padding

* fix: use warning check function

* fix: update displayWarningLabel
2023-06-07 10:15:36 -04:00
Brendan Wong
05977f950b fix: updated celo logo (#6676)
* fix: updated celo logo

* fix: use celo logo from local fs

* fix: removed outdated comments

* fix: combine if statements in celo check

* fix: fix lint
2023-06-07 10:15:06 -04:00
Nate Wienert
5ac36d4156 feat: add link to download the uniswap wallet to the homepage (#6690)
* feat: add link to download the uniswap wallet to the homepage
2023-06-06 14:17:42 -10:00
clrdo
fb998706c2 fix: swaps with native currency destination in BNB Chain (#6705)
fix: swaps with native currency destination in BNB Chain

Co-authored-by: clrdo <clrdo@github.com>
2023-06-06 16:48:36 -05:00
eddie
c45492c890 feat: log swap errors to sentry (#6698)
* feat: log swap errors to sentry

* fix: dont stringify
2023-06-06 13:31:46 -07:00
eddie
41219b435f feat: update content in permit2 flow (#6699) 2023-06-06 13:30:06 -07:00
eddie
e1321843de fix: disable settings button when contextual chainId !== connectedChainId (#6696)
* fix: disable settings button when contextual chainId !== connectedChainId

* fix: re-enable TDP tests (#6708)
2023-06-06 13:28:21 -07:00
Charles Bachmeier
0baa8a1fff feat: upgrade to node 18 (#6606)
* feat: upgrade to node 18

* use 18.x

* try removing npx from fetch schema fn

* use yarn

* setup github build on 18

* no yarn

* use yarn and sanitize output

* yarn silent

* update workflows

* fully remove unused step

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-06-06 11:59:03 -07:00
Zach Pomerantz
f2a3b66357 test(e2e): simplify and re-enable token explore filter test (#6660) 2023-06-06 09:05:16 -07:00
Zach Pomerantz
f2af46037e test(e2e): wallet connection (#6661) 2023-06-06 08:57:04 -07:00
Zach Pomerantz
20a06c9b5a test: skip SwapDetailsDropdown tests (#6652)
* test: skip SwapDetailsDropdown tests

Tests depend on network requests, which may cause flakiness.
These tests should have the network requests mocked, then be re-enabled.

* add TODO

---------

Co-authored-by: cartcrom <cartergcromer@gmail.com>
2023-06-05 22:03:05 -07:00
Zach Pomerantz
8ef54d41b6 docs(e2e): cypress README (#6523)
* docs(e2e): best practices

* docs: rewording running

* wip: thenable

* docs(e2e): writing tests
2023-06-05 19:38:25 -07:00
Jordan Frankfurt
1cdddd1321 chore: add better documentation to a complicated hook (#6689)
* chore: add better documentation to a complicated hook

* input from eddie

* Update src/hooks/useUnmountingAnimation.ts

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

* remove jsdoc types

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-06-05 20:34:34 -05:00
Brendan Wong
f834af69fe fix: Trust wallet loading state (#6694) 2023-06-05 16:25:30 -04:00
Jordan Frankfurt
08cd4bec41 fix: add '-' instead of '$0.00' when the api does not return a price for a tokens page entry (#6691)
* fix: show '-' instead of .00 when we don't have price data on a token

* explicitly handle the 0 case

* add tests to token row component
2023-06-05 14:56:49 -05:00
cartcrom
63ac64f470 fix: use coned function for chart prices (#6693) 2023-06-05 15:40:03 -04:00
Nate Wienert
72686f1e32 ci: temporarily disable coverage tests as they are too flakey (#6684) 2023-06-05 06:42:59 -10:00
Jordan Frankfurt
c07359362f feat: dismiss the landing page when the account drawer is opened (#6633)
* feat: dismiss the landing page when the account drawer is opened

* remove the gradient before the page transition

* pr feedback

* clean up darkmode handling

* pr feedback from design

* update link test

---------

Co-authored-by: Jordan Frankfurt <jordan@CORN-Jordan-949.frankfurt>
2023-06-05 11:37:18 -05:00
cartcrom
ed58c39bdc feat: parse moonpay purchases (#6677)
* feat: parse moonpay purchases

* fix: update comment

* fix: use new coned function

* refactor: simplify nested if statements
2023-06-02 16:46:57 -04:00
Nate Wienert
5d2254be27 fix: searchbar showing incorrect spacing at medium size (#6637)
* fix: searchbar showing incorrect spacing at medium size

* refactor: remove media query navSearchInputVisible into hook only useNavSearchInputVisible

* fix: disable slight blur showing incorrectly when 1100px to lg breakpoint width

* build: use repository slack secret (#6639)

* feat: add animation to Settings menu (#6617)

* feat: add price impact back

* chore: update tes tname

* chore: update snapshot for price impact

* fix

* fix

* update snapshot after rebase

* update snapshot

* chore: finish

* chore: remove snapshot

* feat: add test matcher

* cleanup

* chore: add animation test

* add comment

* update comment

* fix: no-undefined-or in object field types (#6640)

* feat: show affordance in swap UI when we can't fetch usd quote (#6622)

* initial commit:

* add todo to linear

* fix: increase useBestTrade debounce time (#6631)

* fix: increase useBestTrade debounce time

* reduce

* increase

* 350

* fix

* build: caching i18n extractor (#6619)

* fix: do not attempt to cache i18n:extract

* fix: i18n extraction

* docs: improve comments

* fix: remove app advert on mobile safari (#6630)

Co-authored-by: Jordan Frankfurt <jordan@CORN-Jordan-949.frankfurt>

* fix: include sw metric with web vitals (#6646)

* build: add vscode settings for default formatter (#6644)

build: add vscode settings for default formatter to be dbaeumer.vscode-eslint

* test(e2e): improve hardhat configuration (#6650)

build: cache hardhat cache

* feat: permit2 flow updates (#6538)

* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* fix: correct modal state when moving between steps

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* feat: add comments explaining async state

* fix: nits

* fix: address comments

* feat: permit2 e2e tests (#6541)

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* chore: merge

* fix: update tests for new modal

* fix: testid fix

* fix: test updates

* fix: reduce nesting

* test: remove line from test for debugging

* fix: update tests

* fix: more nesting in test

* fix: update test

* fix: reorganize test code

* test: permit2 flow component tests (#6551)

* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* fix: correct modal state when moving between steps

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: testid fix

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: update tests

* fix: more nesting in test

* fix: update test

* fix: reorganize test code

* feat: add l2 chain logo to modal (#6575)

* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* feat: add l2 chain logo to modal

* feat: add unit test

* fix: correct modal state when moving between steps

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: testid fix

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* fix: headerContent prop

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: address  comments

* fix: update tests

* fix: more nesting in test

* fix: update test

* fix: reorganize test code

* feat: swap rejection error handling (#6576)

* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* feat: add l2 chain logo to modal

* feat: add unit test

* fix: correct modal state when moving between steps

* fix: correct modal state when moving between steps

* fix: proper error handling of user rejection of swap

* feat: update e2e test

* fix: typecheck

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: custom error type

* fix: testid fix

* fix: add comment

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* fix: headerContent prop

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: address  comments

* fix: update tests

* fix: more nesting in test

* fix: update test

* fix: update e2e test

* fix: update error test

* fix: reorganize test code

* feat: update content in Swap Submission Modal (#6577)

* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* feat: add l2 chain logo to modal

* feat: add unit test

* fix: correct modal state when moving between steps

* fix: correct modal state when moving between steps

* fix: proper error handling of user rejection of swap

* feat: update e2e test

* fix: typecheck

* feat: design updates, state updates

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: custom error type

* fix: testid fix

* fix: text colors

* fix: add comment

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* fix: headerContent prop

* fix: change tooltip to external link

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: address  comments

* fix: comments

* fix: update tests

* fix: update tests

* fix: more nesting in test

* fix: update test

* fix: update e2e test

* fix: update error test

* fix: update content in test

* fix: reorganize test code

* feat: permit2 animations WEB-2036 (#6590)

* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* feat: add l2 chain logo to modal

* feat: add unit test

* fix: correct modal state when moving between steps

* fix: correct modal state when moving between steps

* fix: proper error handling of user rejection of swap

* feat: update e2e test

* fix: typecheck

* feat: design updates, state updates

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: custom error type

* fix: testid fix

* fix: text colors

* fix: add comment

* wip: permit2 modal animations

* fix: entrance animations

* feat: step indicator transitions

* feat: icon aniamtions

* feat: fix spinner icon

* fix: re-organize new code

* fix: svg import

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* fix: headerContent prop

* fix: change tooltip to external link

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: design nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: address  comments

* fix: comments

* fix: update tests

* fix: update tests

* fix: more nesting in test

* fix: update test

* fix: update e2e test

* fix: update error test

* fix: dont show loader unless onchain processing is happening

* fix: update designs and add comments

* fix: update content in test

* fix: update tests more

* fix: reorganize test code

* fix: mainnet loading indicator on last step

* fix: re-use opacity css code

* fix: testid issue with test

* fix: lint

* fix: modal height and css improvements

* fix: empty

* feat: fix help center link (#6621)

* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* feat: add l2 chain logo to modal

* feat: add unit test

* fix: correct modal state when moving between steps

* fix: correct modal state when moving between steps

* fix: proper error handling of user rejection of swap

* feat: update e2e test

* fix: typecheck

* feat: design updates, state updates

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: custom error type

* fix: testid fix

* fix: text colors

* fix: add comment

* wip: permit2 modal animations

* fix: entrance animations

* feat: step indicator transitions

* feat: icon aniamtions

* feat: fix spinner icon

* fix: re-organize new code

* fix: svg import

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* fix: headerContent prop

* fix: change tooltip to external link

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: design nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: address  comments

* fix: comments

* fix: update tests

* fix: update tests

* fix: more nesting in test

* feat: correct help center article

* fix: update test

* fix: update e2e test

* fix: update error test

* fix: dont show loader unless onchain processing is happening

* fix: update designs and add comments

* fix: update content in test

* fix: update tests more

* fix: reorganize test code

* fix: mainnet loading indicator on last step

* fix: re-use opacity css code

* fix: testid issue with test

* fix: lint

* fix: modal height and css improvements

* fix: empty

* fix: design nits on summary view (#6623)

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* feat: add l2 chain logo to modal

* feat: add unit test

* fix: correct modal state when moving between steps

* fix: correct modal state when moving between steps

* fix: proper error handling of user rejection of swap

* feat: update e2e test

* fix: typecheck

* feat: design updates, state updates

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: custom error type

* fix: testid fix

* fix: text colors

* fix: add comment

* wip: permit2 modal animations

* fix: entrance animations

* feat: step indicator transitions

* feat: icon aniamtions

* feat: fix spinner icon

* fix: re-organize new code

* fix: svg import

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* fix: headerContent prop

* fix: change tooltip to external link

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: design nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: address  comments

* fix: comments

* fix: update tests

* fix: update tests

* fix: more nesting in test

* feat: correct help center article

* fix: design nits on summary view

* fix: update test

* fix: update snapshots

* fix: update e2e test

* fix: etherscan link

* fix: update error test

* fix: dont show loader unless onchain processing is happening

* fix: update designs and add comments

* fix: update content in test

* fix: update tests more

* fix: test

* fix: reorganize test code

* fix: sentence case in one more test

* fix: mainnet loading indicator on last step

* fix: re-use opacity css code

* fix: testid issue with test

* fix: update copy

* fix: update strings in test

* fix: lint

* fix: modal height and css improvements

* fix: empty

* fix: padding on l2 badge

* fix: lint

* feat: log swap button click (#6654)

* build: disable scheduled releases (#6651)

* feat: [DetailsV2] Add Activity Chart Time Period Switcher (#6653)

* add endButton and new TimePeriodSwitcher component

* add snapshot testing

* add test file

* remove switcher from TabbedComponent

* update snapshots

* update describe

* update padding

* extra return

* as const notation

* update snapshots

* no more abs positioning

* cleanup tests and add dropdown test

* add divider line to tabbed component

* update design to match new specs

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>

* fix: reduce ETH amount on mainnet useUSDPrice request to 50 (#6642)

* feat: page wallet connect txn completed (#6655)

* feat: add page to wallet_connect_txn_completed event

* feat: unit test

* fix: test

* chore: cleaning up buy button states (#6618)

* fix: typecheck error in bagfooter (#6669)

* feat: increased debounce swap quote rate (#6666)

* feat: increased debounce swap quote time

* fix

* add basic test for initial useScreenSize

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
Co-authored-by: Mike Grabowski <grabbou@gmail.com>
Co-authored-by: eddie <66155195+just-toby@users.noreply.github.com>
Co-authored-by: Vignesh Mohankumar <me@vig.xyz>
Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
Co-authored-by: Jordan Frankfurt <jordan@CORN-Jordan-949.frankfurt>
Co-authored-by: Charles Bachmeier <charles@bachmeier.io>
Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
Co-authored-by: Jack Short <john.short.tj@gmail.com>
2023-06-02 09:40:04 -10:00
Tina
83f4b53f55 feat: Add Circle native Arbitrum USDC to common base tokens in token selector (#6680)
* add feature flag for usdc arbitrum

* add to feature flag modal
2023-06-02 14:05:41 -04:00
Brendan Wong
4b5e2f7f16 fix: polygon bridge link leading to error page (#6664)
* fix: polygon bridge link leading to error page

* fix: fix polygon bridge to send directly to POS bridge
2023-06-02 10:03:50 -07:00
eddie
0c5d915638 feat: upgrade conedison (#6688)
<!-- 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" -->
upgrades the coned package to get this change: https://github.com/Uniswap/conedison/pull/19


## Test plan

<!-- Delete this section if your change is not a bug fix. -->
### Reproducing the error

<!-- Include steps to reproduce the bug. -->
1. Try to sign a permit2 signature with trustwallet ios on optimism or arbitrum - it will fail without this change.

### QA (ie manual testing)

<!-- Include steps to test the change, ensuring no regression. -->
- [x] manually moved this change into `interface` and tested it , it works as a fallback
2023-06-02 09:32:56 -07:00
Tina
a03231d356 fix: Bug with flipping ura feature flag on/off in dev mode (#6686)
fix bug
2023-06-02 12:12:06 -04:00
Jack Short
72936322b3 feat: adding price impact modal (#6681) 2023-06-02 11:01:28 -04:00
Charles Bachmeier
1bc6eb9a23 feat: Add support for Sepolia (#6610)
* initial sepolia const setup

* uni sepolia

* add more consts for sepolia

* better comment

* upgrade ur sdk

* need to request BE add Ethereum sepolia to supported chains

* update weth address

* remove sepolia from defaults

* add tick lens addr

* add multicall && quoter

* use weth9 checksum

* successful wrap

* use supportedChainId over magic num

* upgrade default-token-list && deduplicate

* cleanup

* use checksum for usdc

* upgrade SOR

* placeholder setProperty

* comment cleanup

* add to anonymizeLink

* bump core-sdk

* deduplicate

* update infura fork block

* add comment

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-06-01 17:57:03 -07:00
Tina
8954aa792a feat: Use routing-api v2 behind feature flag (#6594)
* add unified-routing-api slice

* rename legacy -> legacyAPI

* deduplicate client params and ura params

* move shared functions into utils and rename comments for unified routing API

* use feature flag

* remove eslint ignore since the function is now being used

* add typing to args

* rename ura -> routing-api v2

* update trace name and comment

* rename variables

* lint
2023-06-01 16:52:03 -04:00
eddie
379437b720 fix: remove all references to expert mode (#6675) 2023-06-01 12:04:16 -07:00
Nate Wienert
02c0dee089 feat: hide logo on swap currency selector modal (and other areas that use CurrencyLogo) for unsupported currencies (#6659) 2023-06-01 08:00:02 -10:00
eddie
c55e1af101 fix: reset confirm trade modal state when chain switches (#6667)
<!-- 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" -->

Resets the swap component's local state when the chain changes, to fix Confirmation Modal behavior


<!-- Delete inapplicable lines: -->
_Linear ticket:_ https://linear.app/uniswap/issue/WEB-2161/wonky-swap-modal-behavior-after-switching-chains
_Slack thread:_ https://uniswapteam.slack.com/archives/C047U65H422/p1685467094162069?thread_ts=1685459249.400839&cid=C047U65H422


<!-- Delete this section if your change does not affect UI. -->
## Screen capture

### Before

https://github.com/Uniswap/interface/assets/66155195/115e243a-858b-490d-be9e-269174cc7561


### After

https://github.com/Uniswap/interface/assets/66155195/a47b3606-08ac-490f-abc8-01acf2423efb 



## Test plan

<!-- Delete this section if your change is not a bug fix. -->
### Reproducing the error

<!-- Include steps to reproduce the bug. -->
1. swap on a l2
2. via the metamask extension (or in whatever wallet), trigger a chain-change event (e.g. from optimism to mainnet)
    a. this does not repro if you change chains from within the app selector.
4. fill in the input details for a new trade (ideally with different tokens so you can notice the bug easily)
5. see that the Confirm Swap modal opens automatically when it shouldn't, and it has the wrong trade details

### QA (ie manual testing)

<!-- Include steps to test the change, ensuring no regression. -->
- [x] same steps as above, ensuring the modal behaves correctly 


#### 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
2023-06-01 10:02:37 -07:00
eddie
87cbd1ab38 fix: use ConfirmSwapModal in expert mode (#6673)
Fixes the swap flow for users who are still in expert mode  and need permit2 approvals for a token

### test plan

added e2e test for full permit2 flow with expert mode enabled. 

failing test without the change:
<img width="1394" alt="Screenshot 2023-05-31 at 2 24 12 PM" src="https://github.com/Uniswap/interface/assets/66155195/6a39e039-31b5-4bce-91d2-5e3ebc777378">


passing test with change in the CI of this PR
2023-05-31 14:52:04 -07:00
eddie
1090e97bb5 fix: update snapshots (#6672) 2023-05-31 16:37:45 -05:00
Nate Wienert
921c6b105f fix: prevent searchbar from overflowing the bottom of the window viewport (WEB-2099) (#6645)
fix: prevent searchbar from overflowing the bottom of the window viewport, add scrolling support for inner contents
2023-05-31 10:03:02 -10:00
Vignesh Mohankumar
bc08e9263d feat: increased debounce swap quote rate (#6666)
* feat: increased debounce swap quote time

* fix
2023-05-31 15:46:59 -04:00
Jack Short
8c8300a5de fix: typecheck error in bagfooter (#6669) 2023-05-31 13:50:22 -04:00
Jack Short
3f169adcf2 chore: cleaning up buy button states (#6618) 2023-05-31 11:34:49 -04:00
eddie
774368f325 feat: page wallet connect txn completed (#6655)
* feat: add page to wallet_connect_txn_completed event

* feat: unit test

* fix: test
2023-05-30 11:10:16 -07:00
Vignesh Mohankumar
f83f15d37a fix: reduce ETH amount on mainnet useUSDPrice request to 50 (#6642) 2023-05-30 12:30:16 -04:00
Charles Bachmeier
d9f1402576 feat: [DetailsV2] Add Activity Chart Time Period Switcher (#6653)
* add endButton and new TimePeriodSwitcher component

* add snapshot testing

* add test file

* remove switcher from TabbedComponent

* update snapshots

* update describe

* update padding

* extra return

* as const notation

* update snapshots

* no more abs positioning

* cleanup tests and add dropdown test

* add divider line to tabbed component

* update design to match new specs

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-05-26 16:06:36 -07:00
Zach Pomerantz
d81cb28010 build: disable scheduled releases (#6651) 2023-05-26 14:10:40 -07:00
eddie
b57a5d7ddb feat: log swap button click (#6654) 2023-05-26 10:13:28 -07:00
eddie
e2dd78fd0e fix: design nits on summary view (#6623)
* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* feat: add l2 chain logo to modal

* feat: add unit test

* fix: correct modal state when moving between steps

* fix: correct modal state when moving between steps

* fix: proper error handling of user rejection of swap

* feat: update e2e test

* fix: typecheck

* feat: design updates, state updates

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: custom error type

* fix: testid fix

* fix: text colors

* fix: add comment

* wip: permit2 modal animations

* fix: entrance animations

* feat: step indicator transitions

* feat: icon aniamtions

* feat: fix spinner icon

* fix: re-organize new code

* fix: svg import

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* fix: headerContent prop

* fix: change tooltip to external link

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: design nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: address  comments

* fix: comments

* fix: update tests

* fix: update tests

* fix: more nesting in test

* feat: correct help center article

* fix: design nits on summary view

* fix: update test

* fix: update snapshots

* fix: update e2e test

* fix: etherscan link

* fix: update error test

* fix: dont show loader unless onchain processing is happening

* fix: update designs and add comments

* fix: update content in test

* fix: update tests more

* fix: test

* fix: reorganize test code

* fix: sentence case in one more test

* fix: mainnet loading indicator on last step

* fix: re-use opacity css code

* fix: testid issue with test

* fix: update copy

* fix: update strings in test

* fix: lint

* fix: modal height and css improvements

* fix: empty

* fix: padding on l2 badge

* fix: lint
2023-05-25 15:57:13 -07:00
eddie
96d6e00ed6 feat: fix help center link (#6621)
* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* feat: add l2 chain logo to modal

* feat: add unit test

* fix: correct modal state when moving between steps

* fix: correct modal state when moving between steps

* fix: proper error handling of user rejection of swap

* feat: update e2e test

* fix: typecheck

* feat: design updates, state updates

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: custom error type

* fix: testid fix

* fix: text colors

* fix: add comment

* wip: permit2 modal animations

* fix: entrance animations

* feat: step indicator transitions

* feat: icon aniamtions

* feat: fix spinner icon

* fix: re-organize new code

* fix: svg import

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* fix: headerContent prop

* fix: change tooltip to external link

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: design nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: address  comments

* fix: comments

* fix: update tests

* fix: update tests

* fix: more nesting in test

* feat: correct help center article

* fix: update test

* fix: update e2e test

* fix: update error test

* fix: dont show loader unless onchain processing is happening

* fix: update designs and add comments

* fix: update content in test

* fix: update tests more

* fix: reorganize test code

* fix: mainnet loading indicator on last step

* fix: re-use opacity css code

* fix: testid issue with test

* fix: lint

* fix: modal height and css improvements

* fix: empty
2023-05-25 15:40:42 -07:00
eddie
dd29c59238 feat: permit2 animations WEB-2036 (#6590)
* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* feat: add l2 chain logo to modal

* feat: add unit test

* fix: correct modal state when moving between steps

* fix: correct modal state when moving between steps

* fix: proper error handling of user rejection of swap

* feat: update e2e test

* fix: typecheck

* feat: design updates, state updates

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: custom error type

* fix: testid fix

* fix: text colors

* fix: add comment

* wip: permit2 modal animations

* fix: entrance animations

* feat: step indicator transitions

* feat: icon aniamtions

* feat: fix spinner icon

* fix: re-organize new code

* fix: svg import

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* fix: headerContent prop

* fix: change tooltip to external link

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: design nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: address  comments

* fix: comments

* fix: update tests

* fix: update tests

* fix: more nesting in test

* fix: update test

* fix: update e2e test

* fix: update error test

* fix: dont show loader unless onchain processing is happening

* fix: update designs and add comments

* fix: update content in test

* fix: update tests more

* fix: reorganize test code

* fix: mainnet loading indicator on last step

* fix: re-use opacity css code

* fix: testid issue with test

* fix: lint

* fix: modal height and css improvements

* fix: empty
2023-05-25 15:25:07 -07:00
eddie
8c2a0f1905 feat: update content in Swap Submission Modal (#6577)
* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* feat: add l2 chain logo to modal

* feat: add unit test

* fix: correct modal state when moving between steps

* fix: correct modal state when moving between steps

* fix: proper error handling of user rejection of swap

* feat: update e2e test

* fix: typecheck

* feat: design updates, state updates

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: custom error type

* fix: testid fix

* fix: text colors

* fix: add comment

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* fix: headerContent prop

* fix: change tooltip to external link

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: address  comments

* fix: comments

* fix: update tests

* fix: update tests

* fix: more nesting in test

* fix: update test

* fix: update e2e test

* fix: update error test

* fix: update content in test

* fix: reorganize test code
2023-05-25 14:57:04 -07:00
eddie
682fba219d feat: swap rejection error handling (#6576)
* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* feat: add l2 chain logo to modal

* feat: add unit test

* fix: correct modal state when moving between steps

* fix: correct modal state when moving between steps

* fix: proper error handling of user rejection of swap

* feat: update e2e test

* fix: typecheck

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: custom error type

* fix: testid fix

* fix: add comment

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* fix: headerContent prop

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: address  comments

* fix: update tests

* fix: more nesting in test

* fix: update test

* fix: update e2e test

* fix: update error test

* fix: reorganize test code
2023-05-25 14:56:37 -07:00
eddie
0f5e871054 feat: add l2 chain logo to modal (#6575)
* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* feat: add l2 chain logo to modal

* feat: add unit test

* fix: correct modal state when moving between steps

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: testid fix

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* fix: headerContent prop

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: address  comments

* fix: update tests

* fix: more nesting in test

* fix: update test

* fix: reorganize test code
2023-05-25 14:14:35 -07:00
eddie
07527bab26 test: permit2 flow component tests (#6551)
* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* wip: move approval to summary modal

* wip: not working

* feat: PendingModalContent tests

* feat: useMaxAmountIn

* fix: bad merge

* fix: naming

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* chore: merge

* fix: update tests for new modal

* fix: correct modal state when moving between steps

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: testid fix

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* feat: add comments explaining async state

* fix: test updates

* fix: nits

* fix: reduce nesting

* fix: address comments

* test: remove line from test for debugging

* fix: update tests

* fix: update tests

* fix: more nesting in test

* fix: update test

* fix: reorganize test code
2023-05-25 14:13:55 -07:00
eddie
7934777fa2 feat: permit2 flow updates (#6538)
* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* fix: modal flicker when refetching trade

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: remove step indicators when only one step

* feat: move content into PendingModalContent component

* fix: lint

* fix: correct modal state when moving between steps

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen

* feat: refactor approval flow into a hook

* fix: tradeMeaningfullyDiffers improvement and prepareFlow fix

* fix: address  comments

* feat: add comments explaining async state

* fix: nits

* fix: address comments

* feat: permit2 e2e tests (#6541)

* wip: move approval to summary modal

* wip: not working

* feat: working

* fix: fix flow

* feat: simplify states and build new modal UI

* feat: todos and differs fix

* feat: update tx status modal

* feat: split up approve and permit

* feat: error state

* feat: update success and error states

* feat: undo changes to TxConfirmationModal

* feat: re-order functions

* wip: move approval to summary modal

* wip: not working

* feat: update permit2 e2e tests

* feat: tests passing

* fix: swap test

* fix: bad merge

* chore: merge

* fix: update tests for new modal

* fix: testid fix

* fix: test updates

* fix: reduce nesting

* test: remove line from test for debugging

* fix: update tests

* fix: more nesting in test

* fix: update test

* fix: reorganize test code
2023-05-25 13:49:27 -07:00
Zach Pomerantz
2415a1e3cd test(e2e): improve hardhat configuration (#6650)
build: cache hardhat cache
2023-05-25 11:53:39 -07:00
Nate Wienert
1ba796a895 build: add vscode settings for default formatter (#6644)
build: add vscode settings for default formatter to be dbaeumer.vscode-eslint
2023-05-25 08:22:51 -10:00
Zach Pomerantz
4446eb9b84 fix: include sw metric with web vitals (#6646) 2023-05-25 10:12:20 -07:00
Jordan Frankfurt
d23b6e5da6 fix: remove app advert on mobile safari (#6630)
Co-authored-by: Jordan Frankfurt <jordan@CORN-Jordan-949.frankfurt>
2023-05-25 10:36:57 -05:00
Zach Pomerantz
44c355c7f0 build: caching i18n extractor (#6619)
* fix: do not attempt to cache i18n:extract

* fix: i18n extraction

* docs: improve comments
2023-05-24 14:23:40 -07:00
Vignesh Mohankumar
e4a9764a12 fix: increase useBestTrade debounce time (#6631)
* fix: increase useBestTrade debounce time

* reduce

* increase

* 350

* fix
2023-05-24 14:44:18 -04:00
Mike Grabowski
303fa15240 feat: show affordance in swap UI when we can't fetch usd quote (#6622)
* initial commit:

* add todo to linear
2023-05-24 22:32:53 +04:00
eddie
d180aef306 fix: no-undefined-or in object field types (#6640) 2023-05-24 11:31:40 -07:00
Mike Grabowski
c07c401189 feat: add animation to Settings menu (#6617)
* feat: add price impact back

* chore: update tes tname

* chore: update snapshot for price impact

* fix

* fix

* update snapshot after rebase

* update snapshot

* chore: finish

* chore: remove snapshot

* feat: add test matcher

* cleanup

* chore: add animation test

* add comment

* update comment
2023-05-24 22:02:59 +04:00
Zach Pomerantz
65d91eb363 build: use repository slack secret (#6639) 2023-05-24 13:15:28 -04:00
Jordan Frankfurt
bd4042aa16 chore: update pr template (#6634)
Co-authored-by: Jordan Frankfurt <jordan@CORN-Jordan-949.frankfurt>
2023-05-23 14:53:07 -07:00
github-actions[bot]
1dcafd2f2d chore(i18n): new Crowdin translations (#6215)
* chore(i18n): synchronize translations from crowdin [skip ci]

* chore: trigger actions

---------

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-05-23 14:39:58 -07:00
Jordan Frankfurt
66fcdb4465 feat: improve yarn prepare scripts (#6609)
* feat: improve yarn prepare scripts

* reset yarn.lock to main

* pr feedback

* Update scripts/prepare.js

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

* Update scripts/prepare.js

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

* Update scripts/prepare.js

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

* Update scripts/prepare.js

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

* Update scripts/prepare.js

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

* Update scripts/prepare.js

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

* Update scripts/prepare.js

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

* pr feedback

* switch to using concurrently

* yarn dedupe

---------

Co-authored-by: Jordan Frankfurt <jordan@CORN-Jordan-949.frankfurt>
Co-authored-by: Jordan Frankfurt <jordan@corn-jordan-949.lan>
Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-05-23 16:07:05 -05:00
Jack Short
e398e8b950 fix: allow unsupported chain in pwat (#6629)
chore: allow unsupported chain in pwat
2023-05-23 13:36:28 -04:00
Tina
6424fdfbcd fix: Simplify event logging for SWAP_QUOTE_RECEIVED (#6628)
* simplify event logging

* remove unused function parameter
2023-05-23 11:06:18 -04:00
eddie
95814e3271 fix: prevent race condition for swap state (#6624) 2023-05-22 15:57:17 -07:00
Jack Short
caa2524e27 feat: [DetailsV2] instant buy (#6599)
* initial impl

* removing isopen change

* stopping refetching

* shared button

* pending animiation

* updating shared

* updating snapshots

* adding disabled state

* isLoading in hook

* pulling out ternary

* removing fragment

* separate file for offer button

* fixing price diff check

* remove unnecessary export

* changing name to useBuyAssetCallback
2023-05-22 18:28:48 -04:00
Zach Pomerantz
d28a4b34cd fix: do not attempt to cache i18n:extract (#6616) 2023-05-22 14:03:11 -07:00
cartcrom
f3a80c6272 feat: special case arb search (#6584)
* feat: special case arb search

* fix: check both current and existing token
2023-05-22 12:40:46 -04:00
Zach Pomerantz
b89ee36448 test(e2e): attempt to de-flake (#6611)
* test(e2e): improve memory mgmt

* test(e2e): record flakes

* test(e2e): simplify tests in attempt to de-flake

* test(e2e): more simplification

* test(e2e): disable transaction popup checks

* test(e2e): better wrap assertions

* test(e2e): always assert both inputs
2023-05-22 09:02:54 -07:00
Vignesh Mohankumar
fbc55db937 chore: remove chunkResponseStatus tag (#6586)
* chore: remove chunkResponseStatus tag

* lint
2023-05-21 17:25:58 -04:00
Jordan Frankfurt
835c62acfa fix: use ephemeral props for styled component (#6607)
* fix: use ephemeral props for styled component

* add
2023-05-20 16:55:37 -05:00
Zach Pomerantz
8fe7c7a0a7 build: notify from notify/test (#6597)
* build: notify from notify/test

* debug

* debug2

* revert debugs
2023-05-19 12:24:11 -07:00
Tina
41113e6e41 fix: Use client side router only for price fetching (#6604)
use client side router only for price fetching
2023-05-19 13:35:29 -04:00
Mike Grabowski
58b25d29a9 feat: expand settings by default when custom values are set (#6603)
feat: expand by default
2023-05-19 21:35:02 +04:00
Zach Pomerantz
a2db3e2719 test(e2e): configure Cypress to post PR status comments (#6591) 2023-05-19 06:32:42 -07:00
Mike Grabowski
b62f9066a7 fix: add price impact back (#6581)
* feat: add price impact back

* chore: update tes tname

* chore: update snapshot for price impact

* fix

* fix

* update snapshot after rebase

* update snapshot
2023-05-19 09:24:06 +04:00
Zach Pomerantz
258f22e037 build: continue-on-error for slack notifications (#6600) 2023-05-18 15:30:43 -07:00
Zach Pomerantz
38b306a80f build: pin github-tag-action (#6598) 2023-05-18 15:30:24 -07:00
Zach Pomerantz
9050f09bfe build: notify from notify/releases (#6596) 2023-05-18 15:30:03 -07:00
Zach Pomerantz
77d46c361a test(e2e): de-flake wrap (#6589)
* test(e2e): mv swap to dir

* test(e2e): split swap/wrap/errors

* test(e2e): de-flake wrap
2023-05-18 13:47:15 -07:00
Charles Bachmeier
4fb48bdd1f fix: only request 1 listing on NFTDetails page (#6602)
only request 1 listing

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-05-18 13:35:01 -07:00
cartcrom
cf2b6bf568 fix: portfolio balances after switching accounts (#6527)
* fix: handle portfolio staleness upon account change

* fix: only consider current account for tx updates

* test: add e2e test for account switching

* fix: remove unnused data-testid

* fix: added todo comment

* refactor: move test into existing folder

* fix: add account to dependency array

* todo: tx reducer

* test: update cypress config to pass in env variables

* fix: undo unintended change

* fix: use process.env

* fix: use regex instead of env
2023-05-18 16:32:35 -04:00
eddie
03095f4e48 feat: add feature flag for URA (#6593) 2023-05-18 10:14:18 -07:00
lavalamp
b2966f8d29 ci: Fix YAML spacing (#6592)
Fix YAML spacing
2023-05-18 09:43:02 -07:00
Charles Bachmeier
ef6d1f20ed feat: [DetailsV2] Show data page header when nft scrolled out of view (#6585)
* show data page header when nft scrolled out of view

* add new snapshot test

* useRef for observer

* add comment

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-05-18 09:41:04 -07:00
lavalamp
10b156ff2b ci: Final CI fixes pass (#6556)
* Final CI fixes pass

* Change cut to awk

* Remove workflow_dispatch

* Start, success, failure messges

* Deploy from main and add comment

* Update .github/workflows/2-deploy-to-staging.yml

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

* Update jobs to have correct comments, simplification in prod

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-05-17 14:29:06 -06:00
Zach Pomerantz
146c5f29cf feat: only pre-cache the document (#6580)
* test(e2e): de-flake service-worker

* feat: rm stale cache storage

* fix: put not del

* fix: staging and test

* test: include staging

* fix: log

* test: rm console.log

* fix: unregister before

* test: deflake by restoring state afterwards
2023-05-17 12:10:28 -07:00
Zach Pomerantz
66a3475bf6 test(e2e): split swap tests (#6587)
* test(e2e): mv swap to dir

* test(e2e): split swap/wrap/errors
2023-05-17 09:43:52 -07:00
Zach Pomerantz
f6c393b016 test(e2e): de-flake activity-history (#6583) 2023-05-17 09:43:26 -07:00
cartcrom
15f8d34320 fix: update nonce deduplication logic (#6588)
* fix: update nonce-deduplication logic
* lint
2023-05-16 21:28:19 -04:00
eddie
504e09d3dc feat: new review design (#6451)
* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* fix: modal flicker when refetching trade

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen
2023-05-16 15:15:30 -07:00
Vignesh Mohankumar
1f755e8b0d feat: add retry logic for dynamic imports (#6512)
* feat: add retry logic for lazy import

* try again

* add tests

* refactor: moves retry helper to subfolder

* missing-files

* fix

* doc comment

* tsdoc

* fake timers

* fix

* add eslint rule

* try again?

* try again?

* only dynamic

* try again

* try again

* IT WORKS

* add retry

* fix

* add test

* warn -> error

* lint

* lint

* lint

* add back cache

* rm test

* try again

* real timers but really short intervals

* try returning the promise?

* try returning the promise?

* try this package

* retry

* Update src/utils/retry.ts

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

* Update rules/enforce-retry-on-import.js

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

* Update rules/enforce-retry-on-import.js

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

* eslint_rules

* test fixes

* name

* fix

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-05-16 16:53:22 -04:00
Vignesh Mohankumar
f45a7f921b fix: handle switchChain failure in swap (#6507)
* fix: handle switchChain failure in swap

* comment

* fix
2023-05-16 16:47:13 -04:00
Vignesh Mohankumar
29db61ff90 fix: filter error caused by missing meta tags (#6546)
* fix: filter error caused by missing meta tags

* fix
2023-05-16 16:40:25 -04:00
Tina
8431ad9161 chore: Refactor swap request flow (#6499)
* Refactor swap quote flow with widget logic

* remove console logging

* add ignore path for serialization check and pass in native currencies for client side routing

* apply stashed changes

* revert node version change

* remove TODO comment because maybe no longer relevant

* update unit tests

* wip: add snapshot test

* add snapshot test for gas estimate badge

* address PR comments: rename variables, fix client side router initialization

* update Trade type

* add TODO comment about isExactInput util

* change | undefined convention to ?

* PR comments

* update type

* remove client side initialization logic and replace with TODO

* use routing-api for price fetching trades too

* remove QuoteType.Initialized
2023-05-16 16:33:46 -04:00
Vignesh Mohankumar
fd1aded517 fix: remove trailing slash from request url (#6542)
* fix: remove trailing slash from request url

* moves cast

* {}
2023-05-16 12:36:24 -04:00
Vignesh Mohankumar
27ad7cbd41 test: move all tests to beforeSend (#6513)
* beforeSend tests

* fix

* refactor: filterKnownErrors -> shouldRejectError (#6547)

* refactor: filterKnownErrors -> shouldRejectError

* no unknown

* comments
2023-05-16 12:36:17 -04:00
Joshua DeCristi
01e5de436a fix: "Minimum output" should be "Maximum output" when trade is Exact Output (#6565)
* fix: minimum output should be showing maximum input when trade type is exact output

* fix: test for minimum output should be showing maximum input when trade type is exact output

---------

Co-authored-by: Josh DeCristi <joshdecristi@Joshs-MacBook-Pro.local>
2023-05-16 12:07:39 -04:00
Charles Bachmeier
fd5aa1b51e fix: use padding component (#6579)
* fix: rm unused const

* use component

* update snapshot

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-05-16 08:56:51 -07:00
Mike Grabowski
a6e1a7e6d9 feat: add slippage warning to MenuButton (#6548)
* feat: initial commit

* chore: add unit tests

* chore: move menubutton to sep. component

* chore: simplify styles and add real focused state

* chore: fix tests + some other tweaks

* chore: rename

* test: add snapshot tests

* tweaks
2023-05-16 11:41:14 +04:00
Jack Short
629fe2c144 feat: [DetailsV2] trait bubbles (#6552) 2023-05-15 19:17:45 -04:00
Vignesh Mohankumar
d73763ce75 refactor: imports shared polyfills in setupTests (#6571) 2023-05-15 17:11:28 -04:00
Zach Pomerantz
fe6df38997 build: upgrade to webpack5 with polyfilled Buffer (#6568)
* fix: Revert "fix: Revert "build: upgrade to webpack 5 (#6459)" (#6566)"

This reverts commit 5e591455b3.

* build: polyfill Buffer

* docs: fix comment negation
2023-05-15 14:07:05 -07:00
eddie
719ee0f5b5 fix: loosen permit2 expiration tolerance in e2e tests (#6573) 2023-05-15 10:58:05 -07:00
Charles Bachmeier
75bdf9a8d4 feat: [DetailsV2] Add left padding to trait rows and headers (#6534)
* feat: [DetailsV2] Add left padding to trait rows

* update snapshot

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-05-15 10:40:38 -07:00
Tina
efbe3994bb fix: catch RouterPreference.AUTO case for routing-api usage (#6572)
catch AUTO case for routing-api
2023-05-15 13:38:59 -04:00
Vignesh Mohankumar
93fe8e4349 fix: polyfill ResizeObserver (#6553)
* polyfill

* lint

* polyfill test

* dedupe
2023-05-15 12:39:31 -04:00
Vignesh Mohankumar
6062f615a0 build: change automated release to 16:00 UTC (#6567) 2023-05-15 12:25:49 -04:00
Charles Bachmeier
42e3af7b5c feat: [DetailsV2] Offer and Listing Tables (#6515)
* added home icon, basic content container with scroll behaviour

* add more struct

* add timeUntil util, add main structure of generic component, basic mock data

* propagate asset

* actual fake data

* working scroll

* proper alignment

* 1155 quantity

* small window sizes

* more action buttons

* cleanup

* update snapshot

* add tests

* add new test files

* add outline and hide usd price for certain screen sizes

* use sell order data

* update tests

* fetch multiple listings

* better price width on select screens

* mobile icon for approve

* bottom padding on mobile

* update snapshot

* use test objs in tests

* update query

* add border between rows

* update page padding

* breakpoint overlap

* simplified sellOrder check

* external link

* upstream button and better mobile padding

* add file and update tests

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-05-15 09:14:09 -07:00
Vignesh Mohankumar
57274a800d fix: don't console.error for WalletConnect modal close (#6559) 2023-05-15 12:13:34 -04:00
Tina
5e591455b3 fix: Revert "build: upgrade to webpack 5 (#6459)" (#6566)
Revert "build: upgrade to webpack 5 (#6459)"

This reverts commit ec547ab100.
2023-05-15 09:48:27 -04:00
Zach Pomerantz
ec547ab100 build: upgrade to webpack 5 (#6459)
* fix: import default from json

* fix: fallback to path-browserify

* build: upgrade to webpack5

* test: update size-tests to reflect single entry

* test: increase service-worker timeout

* docs: improve comments

* build: rm MiniCssExtract workaround

* docs: even better comments

* test: back out longer test

* build: vendor chunk

* test: increase sw timeout

* fix: justified splitChunks config

* better explanation

* fix: longer timeout

* fix: caching

* merge and rm duplication

* build
2023-05-12 12:55:01 -07:00
Charles Bachmeier
9de76c69ae feat: [DetailsV2] Data Page Header (#6549)
* hide header on mobile

* add buy and offer buttons, thumbnail, text

* handle no sell orders and add tests

* rehide on mobile

* breakpoint optimizations

* design feedback

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-05-12 14:48:26 -04:00
Charles Bachmeier
85d1b90197 feat: [DetailsV2] Mobile support for traits (#6535)
Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-05-12 13:39:31 -04:00
lavalamp
38af86e1bb ci: More CI pipeline fixes (#6550)
Move git config
2023-05-12 10:03:45 -07:00
Zach Pomerantz
11a8df2a3e build: report test failures via Slack (#6539)
* build: report test failures
2023-05-12 09:19:20 -07:00
Vignesh Mohankumar
3726b6bb47 fix: remove top-level Fragment (#6540) 2023-05-12 11:19:52 -04:00
Vignesh Mohankumar
bfde34c774 refactor: moves retry helper to subfolder (#6531) 2023-05-12 11:08:14 -04:00
Tina
bd8113d018 chore: Goodbye widget :( (#6543)
* goodbye widget :(

* remove unused function

* modify trace tests

* fix lint

* is github down?
2023-05-12 11:05:22 -04:00
lavalamp
14e3ef044e ci: CI pipeline fixes for merge issues (#6529)
* CI fixes

* update text content

* Change PR to force push

* releases environment for prod deploy

* add runs-on

* Rename third step

* Update .github/workflows/1-main-to-staging.yml

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

* Update .github/workflows/1-main-to-staging.yml

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

* nits

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-05-12 08:30:30 -04:00
Vignesh Mohankumar
4fc4bdcd55 fix: propagates user rejection errors (#6533) 2023-05-11 19:40:02 -04:00
Zach Pomerantz
3733570a89 build: update workflow owners (#6537) 2023-05-11 14:24:52 -07:00
Tina
7a042a5199 chore: Replace widget skeleton with internal swap skeleton (#6524)
* replace widget skeleton with internal version

* remove unuecessary div wrappers

* add snapshot test

* forgot to commit this file earlier

* remove exports

* revert(e2e): waitForAnimations

* convert values to px, refactor a bit to use flex gap instead of padding

* remove unnecessary props

* update snapshot test

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-05-11 16:58:35 -04:00
Jordan Frankfurt
6d5e17a6e7 fix: deduplicate remote and local tx lists (#6438)
* fix: deduplicate remote and local tx lists

* add nonce to local transactions

* removes local transactions that have nonces that are duplicates of remote transactions

* add e2e test

* lint fix

* fix types in test

* use supported chain id for reducer tests

* use getReceipt to remove outdated transactions via existing polling setup

* pr nits from cmcewen

* fix lint

* fix test
2023-05-11 13:16:18 -05:00
Jack Short
8301c5892c chore: removing token type check from routing (#6536) 2023-05-11 13:37:28 -04:00
Vignesh Mohankumar
59b757dda0 fix: disables CMC list temporarily (#6532)
* fix: disables CMC list temporarily

* -
2023-05-10 20:32:43 -04:00
Jack Short
92a6ec67b3 feat: [DetailsV2] different media types on landing page (#6492)
* initial layout assuming media type exists

* fixing embeds

* media type function

* updated snapshot test

* initial shadow impl

* better shadows

* audio player controls

* updating tests

* content not available handlers

* errors on all media types

* removing fullscreen from iframe

* adding snapshot tests

* responding to comments

* text align center

* updating tests
2023-05-10 13:10:25 -04:00
lavalamp
1d6a1e90d7 ci: GH action for deploying to production (#6482)
* GH action for deploying to production

* Remove dependency on tests

* Update .github/workflows/4-deploy-to-prod.yml

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

* Update .github/workflows/4-deploy-to-prod.yml

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

* Add tag as action step rather than separate target

* Comments

* Update .github/workflows/4-deploy-to-prod.yml

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

* Specify branches

* Update run name

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-05-10 12:59:59 -04:00
Jack Short
01aa3291b3 chore: bubbling up pwat spender from route query (#6481)
* chore: bubbling up pwat spender from route query

* refactoring

* adding loading state

* moving to loading allowance

* responding to comments

* linting
2023-05-10 12:45:58 -04:00
Zach Pomerantz
5539ebedf7 build: update stage 2 name (#6526) 2023-05-10 09:38:31 -07:00
Vignesh Mohankumar
e6adddbf55 refactor: return onDemandEntries instead of mediaURLs (#6514)
* refactor: return onDemandEntries instead of mediaURLs

* lint

* fix test
2023-05-10 11:46:28 -04:00
Mike Grabowski
0050b1e165 feat: new routing diagram (#6510)
* chore: initial commit

* chore: add todo to refactor and simplify if conditional in the future

* chore: update layout

* chore: ui tweaks

* chore: add todo

* chore: change todo

* chore: update UI

* chore: tmp

* feat: rename router preference

* chore: update type

* fix error

* fix one more issue

* finish UI work

* chore: remove unecessary components

* chore: update non-snapshot unit tests

* chore: fix lint

* chore: fix ts

* chore: one more time

* fix

* chore: update snapshots

* chore: add jira tickets

* chore: fix mobile popovers

* chore: add analytics event

* chore: fix padding and send event

* chore: fix loading state

* oops

* chore: address review

* chore: comment

* chore
2023-05-10 19:06:12 +04:00
Vignesh Mohankumar
5bf33ab004 revert(e2e): waitForAnimations (#6525)
Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-05-10 10:53:53 -04:00
lavalamp
a4cfeecd8c ci: Workflow for pushing to releases/staging (#6319)
* Initial draft of releases/staging force push

* Rename

* Update PAT, test on PR target

* Remove pull request target, add environment

* Update .github/workflows/push-staging.yml

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

* Update .github/workflows/push-staging.yml

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

* Update .github/workflows/push-staging.yml

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

* comments

* Update step name

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-05-10 08:29:31 -04:00
lavalamp
76cbfdd0b9 ci: GH action for promoting staging -> prod (#6366)
* Add action for promoting to prod

* Update .github/workflows/pr-staging-to-prod.yml

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

* Update .github/workflows/pr-staging-to-prod.yml

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

* Update .github/workflows/pr-staging-to-prod.yml

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

* Update .github/workflows/pr-staging-to-prod.yml

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

* Update .github/workflows/pr-staging-to-prod.yml

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

* Comments

* Comments

* Remove commit message

* Fix service account reference, add CODEOWNERS

* Update .github/workflows/pr-staging-to-prod.yml

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

* Nits

* Fix newline

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-05-10 08:29:20 -04:00
lavalamp
0db9e51e41 ci: Staging deploy GH action definition (#6455)
* Staging deploy GH action definition

* Remove workflow_dispatch

* Update src/utils/env.ts

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

* Restrict sentry reporting to staging environment

* Rename and change environment

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-05-10 08:28:59 -04:00
Vignesh Mohankumar
82e7925a17 fix: properly filter AbortErrors (#6519)
* fix: properly filter AbortErrors

* lol
2023-05-09 19:04:41 -04:00
Zach Pomerantz
2150347ba2 build: cache generated files across builds (#6495)
* build: cache generated files across builds

* docs: lingui pkg-up comment

* docs: explain clean extraction
2023-05-09 15:49:53 -07:00
Vignesh Mohankumar
2f80646ddd fix: add chunkResponseStatus tag (#6509)
* fix: add chunkResponseStatus tag

* add-tests

* fix tests

* tests

* description

* comments

* comment

* move

* comment

* lint
2023-05-09 17:44:58 -04:00
matteen
55eea6a724 chore(deps): bump @uniswap/token-lists (#6520)
* chore(deps): bump @uniswap/token-lists

* run yarn deduplicate
2023-05-09 17:34:05 -04:00
matteen
709a70652f feat: add tokenlist validation (#6504)
* feat: add tokenlist validation

* use alternative for spread operator

* maintain tokenlists version and use original ajv version

* bump ajv

* Revert "bump ajv"

This reverts commit b9d2dd61c6.

* rename vars in validator

* update gitignore

* nit fixes

* test

* add ^ back

* remove ^

* removed and readded ajv

* try require.resolve

* Revert "try require.resolve"

This reverts commit 62f58bcb7f.

* bump eslint-config

* yarn lock merge conflict

* bring back spread operator

* remove redundant lint ignore
2023-05-09 16:34:15 -04:00
Jack Short
5a7a041f12 feat: [DetailsV2] background for nft details (#6496)
* feat: background for nft details

* fixing top spacing

* adding new states for zindex
2023-05-09 16:08:28 -04:00
cartcrom
b60d98fc17 fix: loading spinner console bug (#6518)
fix: update svg path
2023-05-09 14:59:26 -04:00
Zach Pomerantz
38d9ab67eb test(e2e): token details and test cleanup (#6516)
* test(e2e): split e2e commands/setup

* test(e2e): consolidate tests

* test(e2e): simplify

* test(e2e): mv token details tests to appropriate spec

* test(e2e): rm obsolete constant

* test(e2e): comments

* test(e2e): do not wait for animations
2023-05-09 10:33:55 -07:00
Jordan Frankfurt
5e6ef1575b test: skip flakey e2e test (#6517) 2023-05-09 11:07:13 -05:00
Vignesh Mohankumar
4a015e9d0d fix: ensure event.request is defined in trace (#6498)
* fix: ensure event.request is defined in trace

* add check for empty event
2023-05-09 11:44:07 -04:00
Mike Grabowski
c383a0a0a2 feat: new Settings menu for Swap (#6480)
* feat: initial commit

* remove extra check

* chore: divider fix

* feat: switch from boolean to routerPreference enum

* chore: align name

* chore: fix two errors

* chore: clean up

* chore: entire radio button clickable

* chore: remove unused toggle component

* Revert "chore: remove unused toggle component"

This reverts commit 42858a02b5.

* feat: rewrite slippage

* feat: Slippage

* chore: tbd tomorrow

* Update src/state/user/reducer.ts

Co-authored-by: Tina <59578595+tinaszheng@users.noreply.github.com>

* feat: replace auto with Slippage enum

* chore: add todo for deadline

* chore: cleanup

* feat: improve autoslippage

* chore: replace price with auto

* chore: fix lint

* test: add coverage for Expand

* chore: fix tests

* chore: review feedback part 1

* chore: rework warning

* chore: add jira tickets

* feat: add tests for useUserSlippageTolerance

* chore: add some more

* chore: one more

* add tests for slippage

* chore: add unit tests for transactionsettings

* remove

* revet changes to improve coverage

* chore: update to figma caption

* chore

* chore

* chore: update wording

* Update src/components/Expand/index.tsx

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

* fix: issue with new value, update confusing migration comment

* chore: remove opacity animation temporarily

* chore: update snapshot test

* chore: fix e2e + update comment

* chore: fix tests

* chore: fix tests

---------

Co-authored-by: Tina <59578595+tinaszheng@users.noreply.github.com>
Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-05-09 10:33:25 -04:00
Vignesh Mohankumar
d6e92804ad fix: filter AbortError from Sentry (#6497) 2023-05-08 18:25:00 -04:00
Zach Pomerantz
f0d8f8b23b test(e2e): mock uniswap token lists from ipfs (#6511)
* test(e2e): mock uniswap token lists from ipfs

* test(e2e): correctly intercept
2023-05-08 15:04:02 -07:00
Charles Bachmeier
ff080aa957 fix: update OS listing fee (#6505)
* update OS listing fee

* remove redundancy

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-05-08 10:28:01 -07:00
cartcrom
04d9ff7d71 feat: update useAllTokensMultichain usage (#6493)
* feat: updates useAllTokensMultichain to return userAddedTokens

* fix: use correct types in tests

* chore: add documentation for future removal of TokenAddressMap

* fix: use doc comments

* test: add unit test for useAllTokensMultichain

* fix: check userAddedTokens for undefined
2023-05-05 18:15:27 -04:00
cartcrom
406893d99a test(e2e): permit2 approval (#6488)
* build: upgrade cypress-hardhat

* test: reset hardhat between tests

* test: enable auto-mining

* build: upgrade cypress-hardhat again

* test: permit2 e2e tests

* test: user approval rejection

* refactor: reuse common permit util vars to reduce # of lines

* fix: var casing

* refactor: polish

* refactor: clean up comments and function names

* refactor: remove approval util and update scoping

* fix: further nits
2023-05-05 17:52:22 -04:00
Vignesh Mohankumar
4630720956 fix: handle addEventListener being unsupported on Safari <14 (#6426)
* fix: handle addEventListener being unsupported on Safari

* conditionally call

* Revert "conditionally call"

This reverts commit b6b7d2f017.

* comment

* fix

* callback

* add tests

* lint

* casting

* good pr comments

* fix
2023-05-05 13:36:20 -04:00
Jordan Frankfurt
e73e1540ad fix: errors are responsible for their own grammar (#6490) 2023-05-05 11:38:00 -05:00
Vignesh Mohankumar
3a82642fe6 fix: update service worker to precache css/js files (#6486)
* fix: update service worker to precache css/js files

* add test

* move to utils

* working tests

* fixes

* lint

* change names

* lint

* change return type

* fix

* fix

* changes

* lint

* rename cache

* lint

* fix comments and remove string[] case

* string |

* Update src/serviceWorker/utils.ts

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

---------
2023-05-04 14:29:13 -04:00
Zach Pomerantz
2a50d6a17e test(e2e): reset hardhat between tests (#6487) 2023-05-04 08:31:53 -07:00
eddie
c8a8149127 fix: add amplitude page context to swap flow events (#6489) 2023-05-04 11:20:12 -04:00
Vignesh Mohankumar
38cde648cf fix: add a catch on collect static call (#6491)
* fix: add a catch on collect static call

* comment
2023-05-04 11:07:01 -04:00
Charles Bachmeier
33c099119d chore: refactor Bag to use scroll hook (#6483)
* chore: refactor Bag to use scroll hook

* remove outdated comment

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-05-03 14:19:27 -07:00
Jordan Frankfurt
40247ff7e0 test(e2e): slippage failure (#6464)
* initial draft

* remove logs

* assertions improvement

* add more comments

* add explicit call to disable automine

* test out a very long timeout

* Revert "test out a very long timeout"

This reverts commit 0fc2666d6f.

* improve test reliability

* remove mock list response

* remove hardhat reset and clean up tests

* simplify assertion

* add zzmp's nits
2023-05-03 16:03:43 -05:00
eddie
30d1de8e84 fix: moonpay modal height (#6439) 2023-05-03 14:05:50 -04:00
Jack Short
0e5328bee9 feat: [DetailsV2] initial details landing page view (#6453)
* feat: initial view of details landing page

* gap

* dynamic font sizes

* added test

* responding to comments

* updating snapshot

* linting
2023-05-03 11:08:48 -04:00
472 changed files with 59120 additions and 31333 deletions

1
.env
View File

@@ -11,3 +11,4 @@ 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_WALLET_CONNECT_PROJECT_ID="c6c9bacd35afa3eb9e6cccf6d8464395"

View File

@@ -2,13 +2,19 @@
require('@uniswap/eslint-config/load')
const rulesDirPlugin = require('eslint-plugin-rulesdir')
rulesDirPlugin.RULES_DIR = 'eslint_rules'
module.exports = {
extends: '@uniswap/eslint-config/react',
extends: ['@uniswap/eslint-config/react'],
plugins: ['rulesdir'],
overrides: [
{
files: ['**/*'],
rules: {
'multiline-comment-style': ['error', 'separate-lines'],
'rulesdir/enforce-retry-on-import': 'error',
'rulesdir/no-undefined-or': 'error',
},
},
{

2
.github/CODEOWNERS vendored
View File

@@ -1 +1 @@
@uniswap/web-reviewers
@uniswap/web-admins

48
.github/actions/report/action.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
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

View File

@@ -8,13 +8,14 @@ runs:
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 18
registry-url: https://registry.npmjs.org
cache: 'yarn'
# node_modules/.cache is intentionally omitted, as this is used for build tool caches.
- 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
@@ -22,3 +23,53 @@ runs:
- if: steps.install-cache.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile --ignore-scripts
shell: bash
# Validators compile quickly, so caching can be omitted.
- run: yarn ajv
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
# GraphQL is generated from schema. The schema is always fetched, but if unchanged, graphql does not need to be re-generated.
- run: yarn graphql:fetch
shell: bash
- uses: actions/cache@v3
id: graphql-cache
with:
path: src/graphql/**/__generated__
key: ${{ runner.os }}-graphql-${{ hashFiles('src/graphql/**/schema.graphql') }}
- if: steps.graphql-cache.outputs.cache-hit != 'true'
run: yarn graphql:generate
shell: bash
# Messages are extracted from source.
# A record of source file content hashes and catalogs is maintained in node_modules/.cache/lingui.
# Messages are always extracted, but extraction may short-circuit from the custom extractor's cache.
- uses: actions/cache@v3
id: i18n-extract-cache
with:
path: node_modules/.cache
key: ${{ runner.os }}-i18n-extract-${{ github.run_id }}
restore-keys: ${{ runner.os }}-i18n-extract-
- run: yarn i18n:extract
shell: bash
# Translations are compiled from messages. If messages haven't changed, the translations do not need to be re-compiled.
- uses: actions/cache@v3
id: i18n-compile-cache
with:
path: src/locales/*.js
key: ${{ runner.os }}-i18n-compile-${{ hashFiles('src/locales/*.po') }}
- if: steps.i18n-compile-cache.outputs.cache-hit !='true'
run: yarn i18n:compile
shell: bash

View File

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

View File

@@ -6,7 +6,7 @@
<!-- Delete inapplicable lines: -->
_JIRA ticket:_
_Linear ticket:_
_Slack thread:_
_Relevant docs:_
@@ -14,9 +14,16 @@ _Relevant docs:_
<!-- Delete this section if your change does not affect UI. -->
## Screen capture
| Before | After (Desktop) | After (Mobile) |
| ------------ |---------------- | -------------- |
| paste_before | past_after | paste_after |
### Before
| Mobile | Desktop |
| ------------ | ------------ |
| paste_before | paste_before |
### After
| Mobile | Desktop |
| ------------ | ----------- |
| paste_after | paste_after |
## Test plan

32
.github/workflows/1-main-to-staging.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
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:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
token: ${{ secrets.RELEASE_SERVICE_ACCESS_TOKEN }}
ref: main
- name: Git config
run: |
git config user.name "UL Service Account"
git config user.email "hello-happy-puppy@users.noreply.github.com"
- name: Add CODEOWNERS file
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

View File

@@ -0,0 +1,63 @@
name: 2 | Deploy staging
on:
push:
branches:
- 'releases/staging'
jobs:
deploy-to-staging:
runs-on: ubuntu-latest
environment:
name: deploy/staging
steps:
- name: Send Slack message that deploy is starting
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
with:
payload: |
{
"text": "Staging deploy started for branch: ${{ 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 prepare
- 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
- name: Send Slack message about deployment outcome
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
if: always()
with:
payload: |
{
"text": "Staging 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'

27
.github/workflows/3-staging-to-prod.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
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:
- 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

View File

@@ -1,72 +1,47 @@
name: Release
name: 4 | Deploy prod
on:
schedule:
- cron: '0 12 * * 1-4' # every day 12:00 UTC Monday-Thursday
# manual trigger
workflow_dispatch:
push:
branches:
- 'releases/prod'
jobs:
wait-on-tests:
runs-on: ubuntu-latest
steps:
- id: unit-tests
uses: fountainhead/action-wait-for-check@v1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
checkName: unit-tests
- id: cypress-tests
uses: fountainhead/action-wait-for-check@v1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
checkName: cypress-tests
- if: steps.unit-tests.outputs.conclusion != 'success' || steps.cypress-tests.outputs.conclusion != 'success'
run: exit 1
tag:
needs: wait-on-tests
runs-on: ubuntu-latest
outputs:
new_tag: ${{ steps.github-tag-action.outputs.new_tag }}
changelog: ${{ steps.github-tag-action.outputs.changelog }}
steps:
- uses: actions/checkout@v3
- name: Bump and tag
id: github-tag-action
uses: mathieudutour/github-tag-action@v6.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
release_branches: .*
default_bump: patch
release:
needs: tag
if: ${{ needs.tag.outputs.new_tag != null }}
deploy-to-prod:
runs-on: ubuntu-latest
environment:
name: release
name: deploy/prod
steps:
- name: Send Slack message that build is starting
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
with:
payload: |
{
"text": "Production deploy started for branch: ${{ 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 prepare
- 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 ${{ needs.tag.outputs.new_tag }}
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: Pin to Crust
uses: crustio/ipfs-crust-action@v2.0.3
continue-on-error: true
timeout-minutes: 2
with:
cid: ${{ steps.pinata.outputs.hash }}
seeds: ${{ secrets.CRUST_SEEDS }}
- name: Convert CIDv0 to CIDv1
id: convert-cidv0
uses: uniswap/convert-cidv0-cidv1@v1.0.0
@@ -78,18 +53,18 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ needs.tag.outputs.new_tag }}
release_name: Release ${{ needs.tag.outputs.new_tag }}
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 accessible via our alias to the Cloudflare IPFS gateway at [app.uniswap.org](https://app.uniswap.org).
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
You can also access the Uniswap Interface directly from an IPFS gateway.
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 alias to the latest release at [app.uniswap.org](https://app.uniswap.org).
**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:
@@ -97,22 +72,32 @@ jobs:
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
- [ipfs://${{ steps.pinata.outputs.hash }}/](ipfs://${{ steps.pinata.outputs.hash }}/)
${{ needs.tag.outputs.changelog }}
- name: Setup node@16 (required by Cloudflare Pages)
uses: actions/setup-node@v3
with:
node-version: 16
${{ 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
- name: Send Slack message about deployment outcome
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
if: always()
with:
payload: |
{
"text": "Production 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

View File

@@ -11,8 +11,10 @@ name: Slack notifications for releases/* merges
# | 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}' \
# Finally, we need to deal with some escaping issues with newlines so that we don't break the Slack message format
# 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
@@ -25,6 +27,8 @@ jobs:
notify-slack:
name: 'Emit Slack notification(s)'
runs-on: ubuntu-latest
environment:
name: notify/releases
steps:
- name: Parse event to slug
id: parse-slug
@@ -38,6 +42,7 @@ jobs:
| 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"
- name: Send custom JSON data to Slack workflow

View File

@@ -2,6 +2,7 @@ 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 cache to node_modules/.cache, so this is 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:
@@ -25,6 +26,11 @@ jobs:
key: ${{ runner.os }}-eslint-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-eslint-${{ hashFiles('**/yarn.lock') }}-
- 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
@@ -37,8 +43,12 @@ jobs:
path: node_modules/.cache
key: ${{ runner.os }}-tsc-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-tsc-${{ hashFiles('**/yarn.lock') }}-
- run: yarn prepare
- 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
@@ -46,6 +56,11 @@ jobs:
- 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
@@ -58,14 +73,17 @@ jobs:
path: node_modules/.cache
key: ${{ runner.os }}-jest-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-jest-${{ hashFiles('**/yarn.lock') }}-
- run: yarn prepare
- run: yarn test --coverage --maxWorkers=100%
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
verbose: true
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-e2e:
runs-on: ubuntu-latest
@@ -78,11 +96,10 @@ jobs:
path: node_modules/.cache
key: ${{ runner.os }}-build-e2e-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-build-e2e-${{ hashFiles('**/yarn.lock') }}-
- run: yarn prepare
- run: yarn build:e2e
env:
NODE_OPTIONS: "--max_old_space_size=4096"
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: build-e2e
path: build
@@ -109,7 +126,7 @@ jobs:
id: cypress-cache
with:
path: /root/.cache/Cypress
key: ${{ runner.os }}-cypress
key: ${{ runner.os }}-cypress-${{ hashFiles('**/node_modules/cypress/package.json') }}
- run: |
yarn cypress install
yarn cypress info
@@ -119,28 +136,49 @@ jobs:
name: build-e2e
path: build
- uses: actions/cache@v3
id: hardhat-cache
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: chrome
record: true
parallel: true
group: e2e
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 }}
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
verbose: true
flags: e2e-tests
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cypress tests
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
# Included as a single job to check for cypress-test-matrix success, as a matrix cannot be checked.
cypress-tests:
if: ${{ always() }}
if: always()
needs: [cypress-test-matrix]
runs-on: ubuntu-latest
steps:

6
.gitignore vendored
View File

@@ -8,8 +8,10 @@
/src/locales/**/en-US.po
/src/locales/**/pseudo.po
# generated graphql types
/src/graphql/**/__generated__
# generated files
/src/**/__generated__
# schema
schema.graphql
# dependencies

2
.nvmrc
View File

@@ -1 +1 @@
14.20.0
v18.16.0

11
.vscode/settings.json vendored
View File

@@ -12,5 +12,14 @@
},
"files.eol": "\n",
"eslint.enable": true,
"eslint.debug": true
"eslint.debug": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
}

1
CODEOWNERS Normal file
View File

@@ -0,0 +1 @@
@uniswap/web-admins

View File

@@ -9,17 +9,38 @@ ignore:
- "**/constants/**/*"
- "constants/**/*"
coverage:
status:
# Omit merging unit/e2e reports into the defaults, as it is nonsensical.
project: off
patch: off
flag_management:
default_rules:
statuses:
- type: project
target: auto
threshold: 1%
# Adjust the base when removing code to avoid penalizing tech debt payback / dead code removal.
removed_code_behavior: adjust_base
if_ci_failed: error
- type: patch
target: 80%
individual_flags:
- name: unit-tests
- name: e2e-tests
# Wait until all machines have reported coverage - e2e tests run across 4 machines.
after_n_builds: 4
statuses:
- type: patch
target: 0%
comment:
layout: flags
# Wait until all machines have reported coverage - e2e tests run across 4 machines + unit tests across 1.
after_n_builds: 5
hide_comment_details: false
github_checks:
# Turn off GitHub Check annotations, as they make it more difficult to review code.
annotations: false

View File

@@ -1,9 +1,11 @@
/* eslint-env node */
const { VanillaExtractPlugin } = require('@vanilla-extract/webpack-plugin')
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
const { execSync } = require('child_process')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
const { DefinePlugin, IgnorePlugin } = require('webpack')
const path = require('path')
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
const { DefinePlugin, IgnorePlugin, ProvidePlugin } = require('webpack')
const commitHash = execSync('git rev-parse HEAD').toString().trim()
const isProduction = process.env.NODE_ENV === 'production'
@@ -12,6 +14,11 @@ const isProduction = process.env.NODE_ENV === 'production'
// Omit them from production builds, as they slow down the feedback loop.
const shouldLintOrTypeCheck = !isProduction
function getCacheDirectory(cacheName) {
// Include the trailing slash to denote that this is a directory.
return `${path.join(__dirname, 'node_modules/.cache/', cacheName)}/`
}
module.exports = {
babel: {
plugins: [
@@ -44,8 +51,13 @@ module.exports = {
pluginOptions(eslintConfig) {
return Object.assign(eslintConfig, {
cache: true,
cacheLocation: 'node_modules/.cache/eslint/',
cacheLocation: getCacheDirectory('eslint'),
ignorePath: '.gitignore',
// Use our own eslint/plugins/config, as overrides interfere with caching.
// This ensures that `yarn start` and `yarn lint` share one cache.
eslintPath: require.resolve('eslint'),
resolvePluginsRelativeTo: null,
baseConfig: null,
})
},
},
@@ -55,11 +67,13 @@ module.exports = {
jest: {
configure(jestConfig) {
return Object.assign(jestConfig, {
transform: {
'\\.css\\.ts$': './vanilla.transform.cjs',
...jestConfig.transform,
},
cacheDirectory: 'node_modules/.cache/jest',
cacheDirectory: getCacheDirectory('jest'),
transform: Object.assign(jestConfig.transform, {
// Transform vanilla-extract using its own transformer.
// See https://sandroroth.com/blog/vanilla-extract-cra#jest-transform.
'\\.css\\.ts$': '@vanilla-extract/jest-transform',
}),
// Use @uniswap/conedison's build directly, as jest does not support its exports.
transformIgnorePatterns: ['@uniswap/conedison/format', '@uniswap/conedison/provider'],
moduleNameMapper: {
'@uniswap/conedison/format': '@uniswap/conedison/dist/format',
@@ -69,8 +83,19 @@ module.exports = {
},
},
webpack: {
plugins: [new VanillaExtractPlugin({ identifiers: 'short' })],
plugins: [
// Webpack 5 does not polyfill node globals, so we do so for those necessary:
new ProvidePlugin({
// - react-markdown requires process.cwd
process: 'process/browser',
}),
// vanilla-extract has poor performance on M1 machines with 'debug' identifiers, so we use 'short' instead.
// See https://vanilla-extract.style/documentation/integrations/webpack/#identifiers for docs.
// See https://github.com/vanilla-extract-css/vanilla-extract/issues/771#issuecomment-1249524366.
new VanillaExtractPlugin({ identifiers: 'short' }),
],
configure: (webpackConfig) => {
// Configure webpack plugins:
webpackConfig.plugins = webpackConfig.plugins
.map((plugin) => {
// Extend process.env with dynamic values (eg commit hash).
@@ -87,10 +112,16 @@ module.exports = {
plugin.options.ignoreOrder = true
}
// Disable TypeScript's config overwrite, as it interferes with incremental build caching.
// This ensures that `yarn start` and `yarn typecheck` share one cache.
if (plugin.constructor.name == 'ForkTsCheckerWebpackPlugin') {
delete plugin.options.typescript.configOverwrite
}
return plugin
})
.filter((plugin) => {
// Case sensitive paths are enforced by TypeScript.
// Case sensitive paths are already enforced by TypeScript.
// See https://www.typescriptlang.org/tsconfig#forceConsistentCasingInFileNames.
if (plugin instanceof CaseSensitivePathsPlugin) return false
@@ -100,20 +131,67 @@ module.exports = {
return true
})
// We're currently on Webpack 4.x which doesn't support the `exports` field in package.json.
// Instead, we need to manually map the import path to the correct exports path (eg dist or build folder).
// See https://github.com/webpack/webpack/issues/9509.
webpackConfig.resolve.alias['@uniswap/conedison'] = '@uniswap/conedison/dist'
// Configure webpack resolution:
webpackConfig.resolve = Object.assign(webpackConfig.resolve, {
plugins: webpackConfig.resolve.plugins.map((plugin) => {
// Allow vanilla-extract in production builds.
// This is necessary because create-react-app guards against external imports.
// See https://sandroroth.com/blog/vanilla-extract-cra#production-build.
if (plugin instanceof ModuleScopePlugin) {
plugin.allowedPaths.push(path.join(__dirname, 'node_modules/@vanilla-extract/webpack-plugin'))
}
return plugin
}),
// Webpack 5 does not resolve node modules, so we do so for those necessary:
fallback: {
// - react-markdown requires path
path: require.resolve('path-browserify'),
},
})
// Configure webpack transpilation (create-react-app specifies transpilation rules in a oneOf):
webpackConfig.module.rules[1].oneOf = webpackConfig.module.rules[1].oneOf.map((rule) => {
// The fallback rule (eg for dependencies).
if (rule.loader && rule.loader.match(/babel-loader/) && !rule.include) {
// Allow not-fully-specified modules so that legacy packages are still able to build.
rule.resolve = { fullySpecified: false }
// The class properties transform is required for @uniswap/analytics to build.
rule.options.plugins.push('@babel/plugin-proposal-class-properties')
}
return rule
})
// Configure webpack optimization:
webpackConfig.optimization.splitChunks = Object.assign(webpackConfig.optimization.splitChunks, {
// Cap the chunk size to 5MB.
// react-scripts suggests a chunk size under 1MB after gzip, but we can only measure maxSize before gzip.
// react-scripts also caps cacheable chunks at 5MB, which gzips to below 1MB, so we cap chunk size there.
// See https://github.com/facebook/create-react-app/blob/d960b9e/packages/react-scripts/config/webpack.config.js#L713-L716.
maxSize: 5 * 1024 * 1024,
webpackConfig.optimization = Object.assign(
webpackConfig.optimization,
isProduction
? {
splitChunks: {
// Cap the chunk size to 5MB.
// react-scripts suggests a chunk size under 1MB after gzip, but we can only measure maxSize before gzip.
// react-scripts also caps cacheable chunks at 5MB, which gzips to below 1MB, so we cap chunk size there.
// See https://github.com/facebook/create-react-app/blob/d960b9e/packages/react-scripts/config/webpack.config.js#L713-L716.
maxSize: 5 * 1024 * 1024,
// Optimize over all chunks, instead of async chunks (the default), so that initial chunks are also optimized.
chunks: 'all',
},
}
: {}
)
// Configure webpack caching:
webpackConfig.cache = Object.assign(webpackConfig.cache, {
cacheDirectory: getCacheDirectory('webpack'),
})
// Ignore failed source mappings to avoid spamming the console.
// Source mappings for a package will fail if the package does not provide them, but the build will still succeed,
// so it is unnecessary (and bothersome) to log it. This should be turned off when debugging missing sourcemaps.
// See https://webpack.js.org/loaders/source-map-loader#ignoring-warnings.
webpackConfig.ignoreWarnings = [/Failed to parse source map/]
return webpackConfig
},
},

View File

@@ -1,17 +1,30 @@
import codeCoverageTask from '@cypress/code-coverage/task'
import { defineConfig } from 'cypress'
import { setupHardhatEvents } from 'cypress-hardhat'
import { unlinkSync } from 'fs'
export default defineConfig({
projectId: 'yp82ef',
videoUploadOnPasses: false,
defaultCommandTimeout: 24000, // 2x average block time
chromeWebSecurity: false,
experimentalMemoryManagement: true, // better memory management, see https://github.com/cypress-io/cypress/pull/25462
retries: { runMode: 2 },
videoCompression: false,
e2e: {
async setupNodeEvents(on, config) {
await setupHardhatEvents(on, config)
codeCoverageTask(on, config)
// Delete recorded videos for specs that passed without flakes.
on('after:spec', async (spec, results) => {
if (results && results.video) {
// If there were no failures (including flakes), delete the recorded video.
if (!results.tests?.some((test) => test.attempts.some((attempt) => attempt?.state === 'failed'))) {
unlinkSync(results.video)
}
}
})
return {
...config,
// Only enable Chrome.

202
cypress/README.md Normal file
View File

@@ -0,0 +1,202 @@
# e2e testing with Cypress
End-to-end tests are run through [Cypress](https://docs.cypress.io/api/table-of-contents/), which runs tests in a real browser. Cypress is a little different than other testing frameworks, and e2e tests are a little different than unit tests, so this directory has its own set of patterns, idioms, and best practices. Not only that, but we're testing against a forked blockchain, not just against typical Web APIs, so we have unique flows that you may not have seen elsewhere.
## Running your first e2e tests
Cypress tests run against a local server, so you'll need to run the application locally at the same time. The fastest way to run e2e tests is to use your dev server: `yarn start`.
Open cypress at the same time with `yarn cypress:open`. You should do this from another window or tab, so that you can continue to see any typechecking/linting warnings from `yarn start`.
Cypress opens its own instance of Chrome, with a list of "E2E specs" for you to select. When you're developing locally, you usually only want to run one spec file at a time. Select your spec by clicking on the filename and it will run.
## Glossary
#### spec
Cypress considers each file a separate spec, or collection of tests.
Specs are always run as a whole through `yarn cypress:open` or on the same machine through CI.
#### Thenable
Cypress queues commands to run in the browser using [Thenables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables), not Promises.
For this reason, you should not use `async/await` syntax in Cypress unless it is wholly-contained in a `cy.then` function argument.
## Writing your first e2e test
_For an excellent treatment on tests, check out the [Cypress Fundamentals](https://learn.cypress.io/cypress-fundamentals/how-to-write-a-test) course._
_While some of that will be paraphrased here, this should be sufficient to get you started:_
### What is a test?
Cypress tests are just like any other test: you should set up an initial state, execute an action, and verify the action's consequence. This is codified in the AAA (Arrange-Act-Assert) pattern, and you'll see this in most of our tests. In _our_ case, it plays out as:
1. Arrange: Visit a page, eg `cy.visit('/swap')`, and set up the state, on the blockchain and the page.
2. Act: Initiate your action under test, eg `initiateSwap()`
3. Assert: Verify that the action has occured, eg `// Verify swap has occured`
You'll usually see the setup, followed by a newline, followed by assertions with comments stating what they are asserting.
Because Cypress tests are translated into user actions, it may be hard to follow the action being described. You should use comments liberally to describe what you are doing and what you intend to test, to make tests easier to read and maintain in the future.
### Thinking about tests: queuing up a sequence of commands
Cypress uses `Thenable`s to achieve "command chaining". A test is described as a series of commands, which are only executed once the previous command in the chain has executed.
```
cy.visit('/swap')
cy.contains('Select token').click()
cy.contains('DAI').click()
```
In this example, `cy.contains('Select token').click()` is queued up right away (all the code is synchronous), but it will not execute until `/swap` has loaded (all the commands are chained); and `click()` will not execute until `Select token` has been found.
This becomes more relevant as you work with data on the blockchain, as you'll need to load it at the correct time, _after_ it's been modified by the application:
```
cy.hardhat().then(async (hardhat) => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' })
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
// wait for the transaction to be executed
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
// BAD: This will get the balance _before_ the other queued actions have executed.
const balance = await hardhat.getBalance(hardhat.wallet, USDC_MAINNET)
cy.wrap(balance).should('deep.equal', expectedBalance)
})
```
```
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' })
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
// wait for the transaction to be executed
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
// GOOD: cy.then chains the command so that it runs _after_ executing the swap
cy.hardhat()
.then((hardhat) => hardhat.getBalance(hardhat.wallet, USDC_MAINNET))
.should('deep.equal', expectedBalance)
})
```
### Working with the blockchain (ie hardhat)
Our tests use a local hardhat node to simulate blockchain transactions. This can be accessed with `cy.hardhat().then((hardhat) => ...)`.
Currently, tests using hardhat must opt-in in when they load the page: `cy.visit('/swap', { ethereum: 'hardhat' })`. This will not be necessary once we've totally migrated to hardhat.
By default, automining is turned on, so that any transaction that you send to the blockchain is mined immediately. If you want to assert on intermediate states (between sending a transaction and mining it), you can turn off automining: `cy.hardhat({ automine: false })`.
The hardhat integration has built-in utilities to let you modify and assert on balances, approvals, and permits, and should be fully typed. Check it out at [Uniswap/cypress-hardhat](https://github.com/Uniswap/cypress-hardhat).
### Asserting on wallet methods
Wallet methods to hardhat are all aliased. If you'd like to assert that a method was sent to the wallet, you can do so using the method name, prefixed with `@`:
```
// Asserts that `eth_sendRawTransaction` was sent to the wallet.
cy.wait('@eth_sendRawTransaction')
```
Sometimes, you may want a method to _fail_. In this case, you can stub it, but you should disable logging to avoid spamming the test:
```
// Stub calls to eth_signTypedData_v4 and fail them
cy.hardhat().then((hardhat) => {
// Note the closure to keep signTypedDataStub in scope. Using closures instead of variables (eg let) helps prevent misuse of chaining.
const signTypedDataStub = cy.stub(hardhat.provider, 'send').log(false)
signTypedDataStub.withArgs('eth_signTypedData_v4).rejects(USER_REJECTION)
signTypedDataStub.callThrough() // allws other methods to call through to hardhat
cy.contains('Confirm swap').click()
// Verify the call occured
// Note the call to cy.wrap to correctly queue the chained command. Without this, the test would occur before the stub is called.
cy.wrap(permitApprovalStub).should('be.calledWith', 'eth_signTypedData_v4')
// Restore the stub
// note the call to cy.then to correctly queue the chained command. Without this, the stub would be restored immediately.
cy.then(() => permitApprovalStub.restore())
})
```
## Best practices
<!-- Best practices should all be labeled using H3, with the rationale italicized at the end of the section. -->
<!-- Best practice 🤣 is to also include an example before your rationale. -->
### Spec / test grouping
Each spec should be specific to one route, _not_ one functional behavior.
For example, `token-details.test.ts` is separated from `swap.test.ts`.
If a route has different functional behaviors, that route should become a directory name, and its spec should be split.
For example, `swap.test.ts` may be split into `swap/swap.test.ts`, `swap/wrap.test.ts`, `swap/permit2.test.ts`.
_This prevents specs from growing too large, which is important because they are always run as a whole locally and on the same machine through CI. If a spec grows too large, it will have a longer local feedback loop, and it will become the bottleneck for CI test runtime._
_Similarly, avoid actions outside the scope of your spec, as it will cause total testing time to increase._
### Use closures instead of variables
Avoid usage of `let`, instead assigning a constant. In practice, this means using closures for your variables:
```javascript
let badVariable
cy.hardhat({ automine: false })
.then((hardhat) => cy.then(() => hardhat.provider.getBalance(hardhat.wallet.address)))
.then((initialBalance) => {
// Do not assign to a variable outside of your closure!
badVariable = initialBalance // <-- bad!
// Use initial balance here, within the closure.
})
cy.get('.class-name').then((el) => {
// Do not use badVariable here! It may have changed value due to the queued async nature of Cypress.
expect(el).should('contain', badVariable) // <-- bad!
})
```
_This prevents misuse of a not-yet-initialized variable, or a variable that has changed as the test progresses._
### Prefer selecting elements using on-screen text over data-testid attributes
When selecting components (eg with `cy.get`), prefer defining your selector with visible UI. Sometimes this is not possible (eg if the text is duplicated on-screen), and you'll need to add a `data-testid` property.
_Defining tests using visual fields helps ensure that we don't break them. `data-testid` may select an element that is only selectable programmatically, and should be used only when necessary, as its use may cover up UI breakages._
_You'll still want to use `data-testid` in cases where the text is rendered in multiple containers and you need to select the correct one, or where the component doesn't render predictable text output._
### Avoid branching logic
Do not write tests that rely on if-statements or conditionals. Do not create helper methods which do more than one thing, and rely on branching logic to apply to different but similar situations.
_Tests should be readable and simple. Branching logic makes it harder to reason about tests, and may hide otherwise flaky or ill-defined behaviors._
_Similarly, you should avoid complicated for-loops. Sometimes, for simple repetition, for-loops are ok._
### Avoid spamming the console
It is ok to include logging while you are developing a test, but that logging should be removed if it is not needed to debug (potential) errors.
For example, stubbing a wallet method will result in dumping a hex string (the calldata) to the log. Instead, suppress logging from methods which you know will flood the log.
```javascript
cy.stub(hardhat.wallet, 'sendTransaction')
.log(false) // <-- suppresses logs from this stub
.rejects(new Error('user cancelled'))
```
_Unnecessary logs it makes it harder to reason about a test overall._
### Name helper methods using transitive verbs
Name helper methods using "action verbs": `expectsThisToHappen`, not `expectThisToHappen`; `selectsToken(token: string)`, not `selectAToken(token: string)`.
_This makes your tests read more naturally, and makes it easier to follow given existing `should` syntax._

View File

@@ -0,0 +1,28 @@
import { getTestSelector } from '../utils'
describe('Buy Crypto Modal', () => {
it('should open and close', () => {
cy.visit('/')
// Open the fiat onramp modal
cy.get(getTestSelector('buy-fiat-button')).click()
cy.get(getTestSelector('fiat-onramp-modal')).should('be.visible')
// Click on a location that should be outside the modal, which should close it
cy.get('body').click(0, 100)
cy.get(getTestSelector('fiat-onramp-modal')).should('not.exist')
})
it('should open and close, mobile viewport', () => {
cy.viewport('iphone-6')
cy.visit('/')
// Open the fiat onramp modal
cy.get(getTestSelector('buy-fiat-button')).click()
cy.get(getTestSelector('fiat-onramp-modal')).should('be.visible')
// Click on a location that should be outside the modal, which should close it
cy.get('body').click(10, 10)
cy.get(getTestSelector('fiat-onramp-modal')).should('not.exist')
})
})

View File

@@ -21,6 +21,7 @@ describe('Landing Page', () => {
})
it('shows landing page when the unicorn icon in nav is selected', () => {
cy.visit('/swap')
cy.get(getTestSelector('uniswap-logo')).click()
cy.get(getTestSelector('landing-page'))
})

View File

@@ -2,7 +2,7 @@
describe('Link', () => {
it('should update route', () => {
cy.viewport(2000, 1600)
cy.visit('/')
cy.visit('/swap')
cy.contains('Pool').click()
cy.get('[data-cy="join-pool-button"]').should('exist')
})

View File

@@ -0,0 +1,79 @@
import { getTestSelector } from '../../utils'
describe('Mini Portfolio account drawer', () => {
beforeEach(() => {
cy.intercept(/api.uniswap.org\/v1\/graphql/, cy.spy().as('gqlSpy'))
cy.visit('/swap', { ethereum: 'hardhat' })
})
it('fetches balances when account button is first hovered', () => {
// The balances should not be fetched before the account button is hovered
cy.get('@gqlSpy').should('not.have.been.called')
// Balances should have been fetched once after hover
cy.get(getTestSelector('web3-status-connected')).trigger('mouseover')
cy.get('@gqlSpy').should('have.been.calledOnce')
// Balances should not be refetched upon second hover
cy.get(getTestSelector('web3-status-connected')).trigger('mouseover')
cy.get('@gqlSpy').should('have.been.calledOnce')
// Balances should not be refetched upon opening drawer
cy.get(getTestSelector('web3-status-connected')).click()
cy.get('@gqlSpy').should('have.been.calledOnce')
// Balances should not be refetched upon closing & reopening drawer
cy.get(getTestSelector('close-account-drawer')).click()
cy.get(getTestSelector('web3-status-connected')).click()
cy.get('@gqlSpy').should('have.been.calledOnce')
})
it('fetches account information', () => {
// Open the mini portfolio
cy.intercept(/graphql/, { fixture: 'mini-portfolio/tokens.json' })
cy.get(getTestSelector('web3-status-connected')).click()
// Verify that wallet state loads correctly
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Tokens')
cy.get(getTestSelector('mini-portfolio-page')).contains('Hidden (201)')
cy.intercept(/graphql/, { fixture: 'mini-portfolio/nfts.json' })
cy.get(getTestSelector('mini-portfolio-navbar')).contains('NFTs').click()
cy.get(getTestSelector('mini-portfolio-page')).contains('I Got Plenty')
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/activity.json' })
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click()
cy.get(getTestSelector('mini-portfolio-page')).contains('Contract Interaction')
})
it('refetches balances when account changes', () => {
cy.hardhat().then((hardhat) => {
const accountA = hardhat.wallets[0].address
const accountB = hardhat.wallets[1].address
// Opens the account drawer
cy.get(getTestSelector('web3-status-connected')).click()
// A shortened version of the first account's address should be shown
cy.contains(accountA.slice(0, 6)).should('exist')
// Stores the current portfolio balance to later compare to next account's balance
cy.get(getTestSelector('portfolio-total-balance'))
.invoke('text')
.then((originalBalance) => {
// TODO(INFRA-3) Replace window.ethereum access below with cypress-hardhat utility
// Simulates the wallet changing accounts via eip-1193 event
cy.window().then((win) => win.ethereum.emit('accountsChanged', [accountB]))
// The second account's address should now be shown
cy.contains(accountB.slice(0, 6)).should('exist')
// The second account's portfolio balance should differ from the original balance
cy.get(getTestSelector('portfolio-total-balance')).should('not.have.text', originalBalance)
})
})
})
})

View File

@@ -0,0 +1,116 @@
import { USDC_MAINNET } from '../../../src/constants/tokens'
import { getTestSelector } from '../../utils'
describe('mini-portfolio activity history', () => {
beforeEach(() => {
cy.hardhat()
.then((hardhat) => hardhat.wallet.getTransactionCount())
.then((nonce) => {
// Mock graphql response to include specific nonces.
cy.intercept(
{
method: 'POST',
url: 'https://beta.api.uniswap.org/v1/graphql',
},
{
body: {
data: {
portfolios: [
{
id: 'UG9ydGZvbGlvOjB4NUNlYUI3NGU0NDZkQmQzYkY2OUUyNzcyMDBGMTI5ZDJiQzdBMzdhMQ==',
assetActivities: [
{
id: 'QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhnME5tUm1PVGs0T0RrNVl6UmtNR1kzWTJNNE9HRTVNVFEzTURBME9EWmtOVGhrTURnNFpqbG1NelkxTnpRM1l6WXdZek15WVRFNE4yWXlaRFEwWVdVNFh6QjRZV1EyWXpCa05XTmlOVEZsWWpjMU5qUTFaRGszT1RneE4yRTJZVEkxTmpreU1UbG1ZbVE1Wmw4d2VEQXpOR0UwTURjMk5EUTROV1kzWlRBNFkyRXhOak0yTm1VMU1ETTBPVEZoTm1GbU56ZzFNR1E9',
timestamp: 1681150079,
type: 'UNKNOWN',
chain: 'ETHEREUM',
transaction: {
id: 'VHJhbnNhY3Rpb246MHg0NmRmOTk4ODk5YzRkMGY3Y2M4OGE5MTQ3MDA0ODZkNThkMDg4ZjlmMzY1NzQ3YzYwYzMyYTE4N2YyZDQ0YWU4XzB4YWQ2YzBkNWNiNTFlYjc1NjQ1ZDk3OTgxN2E2YTI1NjkyMTlmYmQ5Zl8weDAzNGE0MDc2NDQ4NWY3ZTA4Y2ExNjM2NmU1MDM0OTFhNmFmNzg1MGQ=',
blockNumber: 17019453,
hash: '0x46df998899c4d0f7cc88a914700486d58d088f9f365747c60c32a187f2d44ae8',
status: 'CONFIRMED',
to: '0x034a40764485f7e08ca16366e503491a6af7850d',
from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
nonce,
__typename: 'Transaction',
},
assetChanges: [],
__typename: 'AssetActivity',
},
{
id: 'QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhneE16UXpaR1ppTlROaE9XRmpNR00yWW1aaVpqUTNNRFEyWWpObFkyRXhORGN3TUdZd00yWXhOMkV3WWpnM1pqWXpPRFpsWVRnNU16QTRNVFZtWmpoaFh6QjRZMkUzTXpOalkySm1OelZoTXpnME1ERXhPR1ZpT1RjNU9EVTJOemRpTkdRMk56TTBZemMwWmw4d2VERmlOVEUxTkdGaE5HSTRaakF5TjJJNVptUXhPVE0wTVRFek1tWmpPV1JoWlRFd1pqY3pOVGs9',
timestamp: 1681149995,
type: 'SEND',
chain: 'ETHEREUM',
transaction: {
id: 'VHJhbnNhY3Rpb246MHgxMzQzZGZiNTNhOWFjMGM2YmZiZjQ3MDQ2YjNlY2ExNDcwMGYwM2YxN2EwYjg3ZjYzODZlYTg5MzA4MTVmZjhhXzB4Y2E3MzNjY2JmNzVhMzg0MDExOGViOTc5ODU2NzdiNGQ2NzM0Yzc0Zl8weDFiNTE1NGFhNGI4ZjAyN2I5ZmQxOTM0MTEzMmZjOWRhZTEwZjczNTk=',
blockNumber: 17019446,
hash: '0x1343dfb53a9ac0c6bfbf47046b3eca14700f03f17a0b87f6386ea8930815ff8a',
status: 'CONFIRMED',
to: '0x1b5154aa4b8f027b9fd19341132fc9dae10f7359',
from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
nonce: nonce + 1,
__typename: 'Transaction',
},
assetChanges: [
{
__typename: 'TokenTransfer',
id: 'VG9rZW5UcmFuc2ZlcjoweDVjZWFiNzRlNDQ2ZGJkM2JmNjllMjc3MjAwZjEyOWQyYmM3YTM3YTFfMHhiMWRjNDlmMDY1N2FkNTA1YjUzNzUyN2RkOWE1MDk0YTM2NTkzMWMxXzB4MTM0M2RmYjUzYTlhYzBjNmJmYmY0NzA0NmIzZWNhMTQ3MDBmMDNmMTdhMGI4N2Y2Mzg2ZWE4OTMwODE1ZmY4YQ==',
asset: {
id: 'VG9rZW46RVRIRVJFVU1fMHgxY2MyYjA3MGNhZjAxNmE3ZGRjMzA0N2Y5MzI3MmU4Yzc3YzlkZGU5',
name: 'USD Coin (USDC)',
symbol: 'USDC',
address: '0x1cc2b070caf016a7ddc3047f93272e8c77c9dde9',
decimals: 6,
chain: 'ETHEREUM',
standard: null,
project: {
id: 'VG9rZW5Qcm9qZWN0OkVUSEVSRVVNXzB4MWNjMmIwNzBjYWYwMTZhN2RkYzMwNDdmOTMyNzJlOGM3N2M5ZGRlOQ==',
isSpam: true,
logo: null,
__typename: 'TokenProject',
},
__typename: 'Token',
},
tokenStandard: 'ERC20',
quantity: '18011.212084',
sender: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
recipient: '0xb1dc49f0657ad505b537527dd9a5094a365931c1',
direction: 'OUT',
transactedValue: null,
},
],
__typename: 'AssetActivity',
},
],
__typename: 'Portfolio',
},
],
},
},
}
).as('graphql')
})
})
it('should deduplicate activity history by nonce', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' }).hardhat({
automine: false,
})
// Input swap info.
cy.get('#swap-currency-input .token-amount-input').clear().type('1').should('have.value', '1')
cy.get('#swap-currency-output .token-amount-input').should('not.have.value', '')
cy.get('#swap-button').click()
cy.get('#confirm-swap-or-send').click()
cy.get(getTestSelector('confirmation-close-icon')).click()
// Check activity history tab.
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click()
// Assert that the local pending transaction is replaced by a remote transaction with the same nonce.
cy.contains('Swapping').should('not.exist')
})
})

View File

@@ -4,11 +4,8 @@ const PUDGY_COLLECTION_ADDRESS = '0xbd3531da5cf5857e7cfaa92426877b022e612cf8'
const BONSAI_COLLECTION_ADDRESS = '0xec9c519d49856fd2f8133a0741b4dbe002ce211b'
describe('Testing nfts', () => {
beforeEach(() => {
cy.visit('/')
})
it('should load nft leaderboard', () => {
cy.visit('/')
cy.get(getTestSelector('nft-nav')).first().click()
cy.get(getTestSelector('nft-nav')).first().should('exist')
cy.get(getTestSelector('nft-nav')).first().click()
@@ -49,15 +46,11 @@ describe('Testing nfts', () => {
cy.get(getTestSelector('nft-bag')).should('exist')
})
it('should navigate to the owned nfts page', () => {
it('should navigate to and from the owned nfts page', () => {
cy.visit('/')
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('nft-view-self-nfts')).click()
})
it('should close the sidebar when navigating to NFT details', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('mini-portfolio-nav-nfts')).click()
cy.get(getTestSelector('mini-portfolio-nft')).first().click()
cy.contains('Buy crypto').should('not.be.visible')
cy.get(getTestSelector('mini-portfolio-navbar')).contains('NFTs').click()
cy.get(getTestSelector('mini-portfolio-nft')).click()
cy.get(getTestSelector('mini-portfolio-navbar')).should('not.be.visible')
})
})

199
cypress/e2e/permit2.test.ts Normal file
View File

@@ -0,0 +1,199 @@
import { MaxUint160, MaxUint256 } from '@uniswap/permit2-sdk'
import { DAI, USDC_MAINNET } from '../../src/constants/tokens'
import { getTestSelector } from '../utils'
/** Initiates a swap. */
function initiateSwap() {
// The swap button is re-rendered once enabled, so we must wait until the original button is not disabled to re-select the appropriate button.
cy.get('#swap-button').should('not.be.disabled')
// Completes the swap.
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
}
describe('Permit2', () => {
// The same tokens are used for all permit2 tests.
const INPUT_TOKEN = DAI
const OUTPUT_TOKEN = USDC_MAINNET
beforeEach(() => {
// Sets up a swap between INPUT_TOKEN and OUTPUT_TOKEN.
cy.visit(`/swap/?inputCurrency=${INPUT_TOKEN.address}&outputCurrency=${OUTPUT_TOKEN.address}`, {
ethereum: 'hardhat',
})
cy.get('#swap-currency-input .token-amount-input').type('0.01')
})
/** Asserts permit2 has a max approval for spend of the input token on-chain. */
function expectTokenAllowanceForPermit2ToBeMax() {
// check token approval
cy.hardhat()
.then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }))
.should('deep.equal', MaxUint256)
}
/** Asserts the universal router has a max permit2 approval for spend of the input token on-chain. */
function expectPermit2AllowanceForUniversalRouterToBeMax() {
cy.hardhat()
.then((hardhat) => hardhat.approval.getPermit2Allowance({ owner: hardhat.wallet, token: INPUT_TOKEN }))
.then((allowance) => {
cy.wrap(MaxUint160.eq(allowance.amount)).should('eq', true)
// Asserts that the on-chain expiration is in 30 days, within a tolerance of 40 seconds.
const THIRTY_DAYS_SECONDS = 2_592_000
const expected = Math.floor(Date.now() / 1000 + THIRTY_DAYS_SECONDS)
cy.wrap(allowance.expiration).should('be.closeTo', expected, 40)
})
}
describe('approval process (with intermediate screens)', () => {
// Turn off automine so that intermediate screens are available to assert on.
beforeEach(() => cy.hardhat({ automine: false }))
it('swaps after completing full permit2 approval process', () => {
initiateSwap()
// verify that the modal retains its state when the window loses focus
cy.window().trigger('blur')
// Verify token approval
cy.contains('Enable spending DAI on Uniswap')
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax()
// Verify permit2 approval
cy.contains('Allow DAI to be used for swapping')
cy.wait('@eth_signTypedData_v4')
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax()
})
it('swaps with existing permit approval and missing token approval', () => {
cy.hardhat().then(async (hardhat) => {
await hardhat.approval.setPermit2Allowance({ owner: hardhat.wallet, token: INPUT_TOKEN })
await hardhat.mine()
})
initiateSwap()
// Verify token approval
cy.contains('Enable spending DAI on Uniswap')
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax()
// Verify transaction
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
})
})
it('swaps when user has already approved token and permit2', () => {
cy.hardhat().then(({ approval, wallet }) =>
Promise.all([
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }),
])
)
initiateSwap()
// Verify transaction
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
})
it('swaps after handling user rejection of both approval and signature', () => {
const USER_REJECTION = { code: 4001 }
cy.hardhat().then((hardhat) => {
// Reject token approval
const tokenApprovalStub = cy.stub(hardhat.wallet, 'sendTransaction').log(false)
tokenApprovalStub.rejects(USER_REJECTION) // rejects token approval
initiateSwap()
// Verify token approval rejection
cy.wrap(tokenApprovalStub).should('be.calledOnce')
cy.contains('Review swap')
// Allow token approval
cy.then(() => tokenApprovalStub.restore())
// Reject permit2 approval
const permitApprovalStub = cy.stub(hardhat.provider, 'send').log(false)
permitApprovalStub.withArgs('eth_signTypedData_v4').rejects(USER_REJECTION) // rejects permit approval
permitApprovalStub.callThrough() // allows non-eth_signTypedData_v4 send calls to return non-stubbed values
cy.contains('Confirm swap').click()
// Verify token approval
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax()
// Verify permit2 approval rejection
cy.wrap(permitApprovalStub).should('be.calledWith', 'eth_signTypedData_v4')
cy.contains('Review swap')
// Allow permit2 approval
cy.then(() => permitApprovalStub.restore())
cy.contains('Confirm swap').click()
// Verify permit2 approval
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax()
})
})
it('prompts token approval when existing approval amount is too low', () => {
cy.hardhat().then(({ approval, wallet }) =>
Promise.all([
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }),
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }, 1),
])
)
initiateSwap()
// Verify token approval
cy.get(getTestSelector('popups')).contains('Approved')
expectPermit2AllowanceForUniversalRouterToBeMax()
})
it('prompts signature when existing permit approval is expired', () => {
const expiredAllowance = { expiration: Math.floor((Date.now() - 1) / 1000) }
cy.hardhat().then(({ approval, wallet }) =>
Promise.all([
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }, expiredAllowance),
])
)
initiateSwap()
// Verify permit2 approval
cy.wait('@eth_signTypedData_v4')
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax()
})
it('prompts signature when existing permit approval amount is too low', () => {
const smallAllowance = { amount: 1 }
cy.hardhat().then(({ approval, wallet }) =>
Promise.all([
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }, smallAllowance),
])
)
initiateSwap()
// Verify permit2 approval
cy.wait('@eth_signTypedData_v4')
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax()
})
})

View File

@@ -9,7 +9,7 @@ describe('Service Worker', () => {
throw new Error(
'\n' +
'Service Worker tests must be run on a production-like build\n' +
'To test, build with `yarn build:e2e` and serve with `yarn serve`'
'To test, build with `yarn build` and serve with `yarn serve`'
)
}
})
@@ -20,65 +20,78 @@ describe('Service Worker', () => {
}
})
function unregister() {
return cy.log('unregister service worker').then(async () => {
const cacheKeys = await window.caches.keys()
const cacheKey = cacheKeys.find((key) => key.match(/precache/))
if (cacheKey) {
await window.caches.delete(cacheKey)
}
function unregisterServiceWorker() {
return cy.log('unregisters service worker').then(async () => {
const sw = await window.navigator.serviceWorker.getRegistration(Cypress.config().baseUrl ?? undefined)
await sw?.unregister()
})
}
before(unregister)
after(unregister)
before(unregisterServiceWorker)
after(unregisterServiceWorker)
beforeEach(() => {
cy.intercept({ hostname: 'www.google-analytics.com' }, (req) => {
const body = req.body.toString()
if (req.query['ep.event_category'] === 'Service Worker' || body.includes('Service%20Worker')) {
if (req.query['en'] === 'Not Installed' || body.includes('Not%20Installed')) {
req.alias = 'NotInstalled'
} else if (req.query['en'] === 'Cache Hit' || body.includes('Cache%20Hit')) {
req.alias = 'CacheHit'
} else if (req.query['en'] === 'Cache Miss' || body.includes('Cache%20Miss')) {
req.alias = 'CacheMiss'
}
cy.intercept('https://api.uniswap.org/v1/amplitude-proxy', (req) => {
const body = JSON.stringify(req.body)
const serviceWorkerStatus = body.match(/"service_worker":"(\w+)"/)?.[1]
if (serviceWorkerStatus) {
req.alias = `ServiceWorker:${serviceWorkerStatus}`
}
})
})
it('installs a ServiceWorker', () => {
it('installs a ServiceWorker and reports the uninstalled status to analytics', () => {
cy.visit('/', { serviceWorker: true })
.get('#swap-page')
.wait('@NotInstalled', { timeout: 20000 })
.window({ timeout: 20000 })
.and((win) => {
expect(win.navigator.serviceWorker.controller?.state).to.equal('activated')
})
cy.wait('@ServiceWorker:uninstalled')
cy.window().should(
'have.nested.property',
// The parent is checked instead of the AUT because it is on the same origin,
// and the AUT will not be considered "activated" until the parent is idle.
'parent.navigator.serviceWorker.controller.state',
'activated'
)
})
it('records a cache hit', () => {
cy.visit('/', { serviceWorker: true }).get('#swap-page').wait('@CacheHit', { timeout: 20000 })
})
it('records a cache miss', () => {
cy.then(async () => {
const cacheKeys = await window.caches.keys()
const cacheKey = cacheKeys.find((key) => key.match(/precache/))
assert(cacheKey)
const cache = await window.caches.open(cacheKey)
const keys = await cache.keys()
const key = keys.find((key) => key.url.match(/index/))
assert(key)
await cache.put(key, new Response())
describe('cache hit', () => {
it('reports the hit to analytics', () => {
cy.visit('/', { serviceWorker: true })
cy.wait('@ServiceWorker:hit')
})
})
describe('cache miss', () => {
let cache: Cache | undefined
let request: Request | undefined
let response: Response | undefined
before(() => {
// Mocks the index.html in the cache to force a cache miss.
cy.visit('/', { serviceWorker: true }).then(async () => {
const cacheKeys = await window.caches.keys()
const cacheKey = cacheKeys.find((key) => key.match(/precache/))
assert(cacheKey)
cache = await window.caches.open(cacheKey)
const keys = await cache.keys()
request = keys.find((key) => key.url.match(/index/))
assert(request)
response = await cache.match(request)
assert(response)
await cache.put(request, new Response())
})
})
after(() => {
// Restores the index.html in the cache so that re-runs behave as expected.
// This is necessary because the Service Worker will not re-populate the cache.
cy.then(async () => {
if (cache && request && response) {
await cache.put(request, response)
}
})
})
it('reports the miss to analytics', () => {
cy.visit('/', { serviceWorker: true })
cy.wait('@ServiceWorker:miss')
})
.visit('/', { serviceWorker: true })
.get('#swap-page')
.wait('@CacheMiss', { timeout: 20000 })
})
})

View File

@@ -1,415 +0,0 @@
import { SupportedChainId, WETH9 } from '@uniswap/sdk-core'
import { UNI as UNI_MAINNET, USDC_MAINNET } from '../../src/constants/tokens'
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
import { WETH_GOERLI } from '../fixtures/constants'
import { getTestSelector } from '../utils'
describe('Swap', () => {
const verifyAmount = (field: 'input' | 'output', amountText: string | null) => {
if (amountText === null) {
cy.get(`#swap-currency-${field} .token-amount-input`).should('not.have.value')
} else {
cy.get(`#swap-currency-${field} .token-amount-input`).should('have.value', amountText)
}
}
const verifyToken = (field: 'input' | 'output', tokenSymbol: string | null) => {
if (tokenSymbol === null) {
cy.get(`#swap-currency-${field} .token-symbol-container`).should('contain.text', 'Select token')
} else {
cy.get(`#swap-currency-${field} .token-symbol-container`).should('contain.text', tokenSymbol)
}
}
const selectToken = (tokenSymbol: string, field: 'input' | 'output') => {
// open token selector...
cy.get(`#swap-currency-${field} .open-currency-select-button`).click()
// select token...
cy.contains(tokenSymbol).click()
cy.get('body')
.then(($body) => {
if ($body.find(getTestSelector('TokenSafetyWrapper')).length) {
return 'I understand'
}
return 'no-op' // Don't click on anything, a no-op
})
.then((content) => {
if (content !== 'no-op') {
cy.contains(content).click()
}
})
// token selector should close...
cy.contains('Search name or paste address').should('not.exist')
}
describe('Swap on main page', () => {
before(() => {
cy.visit('/swap', { ethereum: 'hardhat' })
})
it('starts with ETH selected by default', () => {
verifyAmount('input', '')
verifyToken('input', 'ETH')
verifyAmount('output', null)
verifyToken('output', null)
})
it('can enter an amount into input', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('0.001').should('have.value', '0.001')
})
it('zero swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0').should('have.value', '0.0')
})
it('invalid swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('\\').should('have.value', '')
})
it('can enter an amount into output', () => {
cy.get('#swap-currency-output .token-amount-input').clear().type('0.001').should('have.value', '0.001')
})
it('zero output amount', () => {
cy.get('#swap-currency-output .token-amount-input').clear().type('0.0').should('have.value', '0.0')
})
it('should render an error when a transaction fails due to a passed deadline', () => {
const DEADLINE_MINUTES = 1
const TEN_MINUTES_MS = 1000 * 60 * DEADLINE_MINUTES * 10
cy.visit('/swap', { ethereum: 'hardhat' })
.hardhat()
.then((hardhat) => {
cy.then(() => hardhat.setAutomine(false))
.then(() => hardhat.getBalance(hardhat.wallet.address, USDC_MAINNET))
.then((balance) => Number(balance.toFixed(1)))
.then((initialBalance) => {
// Input swap info.
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.contains('USDC').click()
cy.get('#swap-currency-output .token-amount-input').clear().type('1')
cy.get('#swap-currency-input .token-amount-input').should('not.equal', '')
// Set deadline to minimum. (1 minute)
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.get(getTestSelector('deadline-input')).clear().type(DEADLINE_MINUTES.toString())
cy.get('body').click('topRight')
cy.get(getTestSelector('deadline-input')).should('not.exist')
cy.get('#swap-button').click()
cy.get('#confirm-swap-or-send').click()
// Dismiss the modal that appears when a transaction is broadcast to the network.
cy.get(getTestSelector('dismiss-tx-confirmation')).click()
// The UI should show the transaction as pending.
cy.contains('1 Pending').should('exist')
// Mine a block past the deadline.
cy.then(() => hardhat.mine(1, TEN_MINUTES_MS)).then(() => {
// The UI should no longer show the transaction as pending.
cy.contains('1 Pending').should('not.exist')
// Check that the user is informed of the failure
cy.contains('Swap failed').should('exist')
// Check that the balance is unchanged in the UI
cy.get('#swap-currency-output [data-testid="balance-text"]').should(
'have.text',
`Balance: ${initialBalance}`
)
// Check that the balance is unchanged on chain
cy.then(() => hardhat.getBalance(hardhat.wallet.address, USDC_MAINNET))
.then((balance) => Number(balance.toFixed(1)))
.should('eq', initialBalance)
})
})
})
})
it('should have the correct default input from URL params ', () => {
cy.visit(`/swap?inputCurrency=${WETH_GOERLI}`)
verifyToken('input', 'WETH')
verifyToken('output', null)
selectToken('Ether', 'output')
cy.get(getTestSelector('swap-currency-button')).first().click()
verifyToken('input', 'ETH')
verifyToken('output', 'WETH')
})
it('should have the correct default output from URL params ', () => {
cy.visit(`/swap?outputCurrency=${WETH_GOERLI}`)
verifyToken('input', null)
verifyToken('output', 'WETH')
cy.get(getTestSelector('swap-currency-button')).first().click()
verifyToken('input', 'WETH')
verifyToken('output', null)
selectToken('Ether', 'output')
cy.get(getTestSelector('swap-currency-button')).first().click()
verifyToken('input', 'ETH')
verifyToken('output', 'WETH')
})
it('ETH to wETH is same value (wrapped swaps have no price impact)', () => {
cy.visit('/swap')
selectToken('WETH', 'output')
cy.get('#swap-currency-input .token-amount-input').clear().type('0.01')
cy.get('#swap-currency-output .token-amount-input').should('have.value', '0.01')
})
it('Opens and closes the settings menu', () => {
cy.visit('/swap')
cy.contains('Settings').should('not.exist')
cy.get(getTestSelector('swap-settings-button')).click()
cy.contains('Slippage tolerance').should('exist')
cy.contains('Transaction deadline').should('exist')
cy.contains('Auto Router API').should('exist')
cy.contains('Expert Mode').should('exist')
cy.get(getTestSelector('swap-settings-button')).click()
cy.contains('Settings').should('not.exist')
})
it('inputs reset when navigating between pages', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('0.01')
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
cy.visit('/pool')
cy.visit('/swap')
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
})
it('can swap ETH for USDC', () => {
cy.visit('/swap', { ethereum: 'hardhat' })
const TOKEN_ADDRESS = USDC_MAINNET.address
const BALANCE_INCREMENT = 1
cy.hardhat().then((hardhat) => {
cy.then(() => hardhat.getBalance(hardhat.wallet.address, USDC_MAINNET))
.then((balance) => Number(balance.toFixed(1)))
.then((initialBalance) => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get(getTestSelector('token-search-input')).clear().type(TOKEN_ADDRESS)
cy.contains('USDC').click()
cy.get('#swap-currency-output .token-amount-input').clear().type(BALANCE_INCREMENT.toString())
cy.get('#swap-currency-input .token-amount-input').should('not.equal', '')
cy.get('#swap-button').click()
cy.get('#confirm-swap-or-send').click()
cy.get(getTestSelector('dismiss-tx-confirmation')).click()
cy.then(() => hardhat.provider.send('hardhat_mine', ['0x1', '0xc'])).then(() => {
// ui check
cy.get('#swap-currency-output [data-testid="balance-text"]').should(
'have.text',
`Balance: ${initialBalance + BALANCE_INCREMENT}`
)
// chain state check
cy.then(() => hardhat.getBalance(hardhat.wallet.address, USDC_MAINNET))
.then((balance) => Number(balance.toFixed(1)))
.should('eq', initialBalance + BALANCE_INCREMENT)
})
})
})
})
})
it('should be able to wrap ETH', () => {
const BALANCE_INCREMENT = 1
cy.visit('/swap', { ethereum: 'hardhat' })
.hardhat()
.then((hardhat) => {
cy.then(() => hardhat.getBalance(hardhat.wallet.address, WETH9[SupportedChainId.MAINNET]))
.then((balance) => Number(balance.toFixed(1)))
.then((initialWethBalance) => {
// Select WETH for the token output.
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.contains('WETH').click()
// Enter the amount to wrap.
cy.get('#swap-currency-output .token-amount-input').clear().type(BALANCE_INCREMENT.toString())
cy.get('#swap-currency-input .token-amount-input').should('not.equal', '')
// Click the wrap button.
cy.get(getTestSelector('wrap-button')).should('not.be.disabled')
cy.get(getTestSelector('wrap-button')).click()
// The pending transaction indicator should be visible.
cy.get(getTestSelector('web3-status-connected')).should('have.descendants', ':contains("1 Pending")')
// <automine transaction>
// The pending transaction indicator should be gone.
cy.get(getTestSelector('web3-status-connected')).should('not.have.descendants', ':contains("1 Pending")')
// The UI balance should have increased.
cy.get('#swap-currency-output [data-testid="balance-text"]').should(
'have.text',
`Balance: ${initialWethBalance + BALANCE_INCREMENT}`
)
// There should be a successful wrap notification.
cy.contains('Wrapped').should('exist')
// The user's WETH account balance should have increased
cy.then(() => hardhat.getBalance(hardhat.wallet.address, WETH9[SupportedChainId.MAINNET]))
.then((balance) => Number(balance.toFixed(1)))
.should('eq', initialWethBalance + BALANCE_INCREMENT)
})
})
})
it('should be able to unwrap WETH', () => {
const BALANCE_INCREMENT = 1
cy.visit('/swap', { ethereum: 'hardhat' })
.hardhat()
.then((hardhat) => {
cy.then(() => hardhat.getBalance(hardhat.wallet.address, WETH9[SupportedChainId.MAINNET])).then(
(initialBalance) => {
// Select WETH for the token output.
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.contains('WETH').click()
// Enter the amount to wrap.
cy.get('#swap-currency-output .token-amount-input').clear().type(BALANCE_INCREMENT.toString())
cy.get('#swap-currency-input .token-amount-input').should('not.equal', '')
// Click the wrap button.
cy.get(getTestSelector('wrap-button')).should('not.be.disabled')
cy.get(getTestSelector('wrap-button')).click()
// <automine transaction>
// The pending transaction indicator should be visible.
cy.contains('1 Pending').should('exist')
// The user should see a notification telling them they successfully wrapped their ETH.
cy.contains('Wrapped').should('exist')
// Switch to unwrapping the ETH we just wrapped.
cy.get(getTestSelector('swap-currency-button')).click()
cy.get(getTestSelector('wrap-button')).should('not.be.disabled')
// Click the Unwrap button.
cy.get(getTestSelector('wrap-button')).click()
// The pending transaction indicator should be visible.
cy.contains('1 Pending').should('exist')
// <automine transaction>
// The pending transaction indicator should be gone.
cy.contains('1 Pending').should('not.exist')
// The user should see a notification telling them they successfully unwrapped their ETH.
cy.contains('Unwrapped').should('exist')
// The UI balance should have decreased.
cy.get('#swap-currency-input [data-testid="balance-text"]').should(
'have.text',
`Balance: ${initialBalance.toFixed(0)}`
)
// There should be a successful unwrap notification.
cy.contains('Unwrapped').should('exist')
// The user's WETH account balance should not have changed from the initial balance
cy.then(() => hardhat.getBalance(hardhat.wallet.address, WETH9[SupportedChainId.MAINNET]))
.then((balance) => balance.toFixed(0))
.should('eq', initialBalance.toFixed(0))
}
)
})
})
it('should render and dismiss the wallet rejection modal', () => {
cy.visit('/swap', { ethereum: 'hardhat' })
.hardhat()
.then((hardhat) => {
cy.stub(hardhat.wallet, 'sendTransaction').rejects(new Error('user cancelled'))
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get(getTestSelector('token-search-input')).clear().type(USDC_MAINNET.address)
cy.contains('USDC').click()
cy.get('#swap-currency-output .token-amount-input').clear().type('1')
cy.get('#swap-currency-input .token-amount-input').should('not.equal', '')
cy.get('#swap-button').click()
cy.get('#confirm-swap-or-send').click()
cy.contains('Transaction rejected').should('exist')
cy.contains('Dismiss').click()
cy.contains('Transaction rejected').should('not.exist')
})
})
describe('Swap on Token Detail Page', () => {
beforeEach(() => {
// On mobile widths, we just link back to /swap instead of rendering the swap component.
cy.viewport(1200, 800)
cy.visit(`/tokens/ethereum/${UNI_MAINNET[1].address}`, {
ethereum: 'hardhat',
featureFlags: [FeatureFlag.removeWidget],
}).then(() => {
cy.wait('@eth_blockNumber')
cy.scrollTo('top')
})
})
it('should have the expected output for a tokens detail page', () => {
verifyAmount('input', '')
verifyToken('input', null)
verifyAmount('output', null)
verifyToken('output', 'UNI')
})
it('should automatically navigate to the new TDP', () => {
selectToken('WETH', 'output')
cy.url().should('include', `${WETH9[1].address}`)
cy.url().should('not.include', `${UNI_MAINNET[1].address}`)
})
it('should not share swap state with the main swap page', () => {
verifyToken('output', 'UNI')
selectToken('WETH', 'input')
cy.visit('/swap', { featureFlags: [FeatureFlag.removeWidget] })
cy.contains('UNI').should('not.exist')
cy.contains('WETH').should('not.exist')
})
it('can enter an amount into input', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('0.001').should('have.value', '0.001')
})
it('zero swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0').should('have.value', '0.0')
})
it('invalid swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('\\').should('have.value', '')
})
it('can enter an amount into output', () => {
cy.get('#swap-currency-output .token-amount-input').clear().type('0.001').should('have.value', '0.001')
})
it('zero output amount', () => {
cy.get('#swap-currency-output .token-amount-input').clear().type('0.0').should('have.value', '0.0')
})
it('should show a L2 token even if the user is connected to a different network', () => {
cy.visit('/tokens', { ethereum: 'hardhat', featureFlags: [FeatureFlag.removeWidget] })
cy.get(getTestSelector('tokens-network-filter-selected')).click()
cy.get(getTestSelector('tokens-network-filter-option-arbitrum')).click()
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Arbitrum')
cy.get(getTestSelector('token-table-row-ARB')).click()
verifyToken('output', 'ARB')
cy.contains('Connect to Arbitrum').should('exist')
})
})
})

View File

@@ -0,0 +1,107 @@
import { BigNumber } from '@ethersproject/bignumber'
import { SupportedChainId } from '@uniswap/sdk-core'
import { DEFAULT_DEADLINE_FROM_NOW } from '../../../src/constants/misc'
import { UNI, USDC_MAINNET } from '../../../src/constants/tokens'
import { getBalance, getTestSelector } from '../../utils'
const UNI_MAINNET = UNI[SupportedChainId.MAINNET]
describe('Swap errors', () => {
it('wallet rejection', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' })
cy.hardhat().then((hardhat) => {
// Stub the wallet to reject any transaction.
cy.stub(hardhat.wallet, 'sendTransaction').log(false).rejects(new Error('user cancelled'))
// Enter amount to swap
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
// Submit transaction
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.wait('@eth_estimateGas')
// Verify rejection
cy.contains('Review swap')
cy.contains('Confirm swap')
})
})
it('transaction past deadline', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`, { ethereum: 'hardhat' })
cy.hardhat({ automine: false })
getBalance(USDC_MAINNET).then((initialBalance) => {
// Enter amount to swap
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
// Submit transaction
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.wait('@eth_estimateGas').wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
cy.contains('Transaction submitted')
cy.get(getTestSelector('confirmation-close-icon')).click()
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
// Mine transaction
cy.hardhat().then(async (hardhat) => {
// Remove the transaction from the mempool, so that it doesn't fail but it is past the deadline.
// This should result in it being removed from pending transactions, without a failure notificiation.
const transactions = await hardhat.send('eth_pendingTransactions', [])
await hardhat.send('hardhat_dropTransaction', [transactions[0].hash])
// Mine past the deadline
await hardhat.mine(1, DEFAULT_DEADLINE_FROM_NOW + 1)
})
cy.wait('@eth_getTransactionReceipt')
// Verify transaction did not occur
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
cy.get(getTestSelector('popups')).should('not.contain', 'Swap failed')
cy.get('#swap-currency-output').contains(`Balance: ${initialBalance}`)
getBalance(USDC_MAINNET).should('eq', initialBalance)
})
})
it('slippage failure', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${UNI_MAINNET.address}`, { ethereum: 'hardhat' })
cy.hardhat({ automine: false })
getBalance(USDC_MAINNET).then((initialBalance) => {
// Gas estimation fails for this transaction (that would normally fail), so we stub it.
cy.hardhat().then((hardhat) => {
const send = cy.stub(hardhat.provider, 'send').log(false)
send.withArgs('eth_estimateGas').resolves(BigNumber.from(2_000_000))
send.callThrough()
})
// Set slippage to a very low value.
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('body').click('topRight') // close modal
cy.get(getTestSelector('slippage-input')).should('not.exist')
// Submit 2 transactions
for (let i = 0; i < 2; i++) {
cy.get('#swap-currency-input .token-amount-input').type('200').should('have.value', '200')
cy.get('#swap-currency-output .token-amount-input').should('not.have.value', '')
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
cy.contains('Transaction submitted')
cy.get(getTestSelector('confirmation-close-icon')).click()
}
cy.get(getTestSelector('web3-status-connected')).should('contain', '2 Pending')
// Mine transactions
cy.hardhat().then((hardhat) => hardhat.mine())
cy.wait('@eth_getTransactionReceipt')
// Verify transaction did not occur
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
cy.get(getTestSelector('popups')).contains('Swap failed')
getBalance(UNI_MAINNET).should('eq', initialBalance)
})
})
})

View File

@@ -0,0 +1,14 @@
import { getTestSelector } from '../../utils'
describe('Swap settings', () => {
it('Opens and closes the settings menu', () => {
cy.visit('/swap')
cy.contains('Settings').should('not.exist')
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.contains('Max slippage').should('exist')
cy.contains('Transaction deadline').should('exist')
cy.contains('Auto Router API').should('exist')
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.contains('Settings').should('not.exist')
})
})

View File

@@ -0,0 +1,76 @@
import { SupportedChainId } from '@uniswap/sdk-core'
import { UNI, USDC_MAINNET } from '../../../src/constants/tokens'
import { getBalance, getTestSelector } from '../../utils'
const UNI_MAINNET = UNI[SupportedChainId.MAINNET]
describe('Swap', () => {
describe('Swap on main page', () => {
it('starts with ETH selected by default', () => {
cy.visit('/swap')
cy.get(`#swap-currency-input .token-amount-input`).should('have.value', '')
cy.get(`#swap-currency-input .token-symbol-container`).should('contain.text', 'ETH')
cy.get(`#swap-currency-output .token-amount-input`).should('not.have.value')
cy.get(`#swap-currency-output .token-symbol-container`).should('contain.text', 'Select token')
})
it('should default inputs from URL params ', () => {
cy.visit(`/swap?inputCurrency=${UNI_MAINNET.address}`)
cy.get(`#swap-currency-input .token-symbol-container`).should('contain.text', 'UNI')
cy.get(`#swap-currency-output .token-symbol-container`).should('contain.text', 'Select token')
cy.visit(`/swap?outputCurrency=${UNI_MAINNET.address}`)
cy.get(`#swap-currency-input .token-symbol-container`).should('contain.text', 'Select token')
cy.get(`#swap-currency-output .token-symbol-container`).should('contain.text', 'UNI')
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${UNI_MAINNET.address}`)
cy.get(`#swap-currency-input .token-symbol-container`).should('contain.text', 'ETH')
cy.get(`#swap-currency-output .token-symbol-container`).should('contain.text', 'UNI')
})
it('inputs reset when navigating between pages', () => {
cy.visit('/swap')
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
cy.get('#swap-currency-input .token-amount-input').type('0.01').should('have.value', '0.01')
cy.visit('/pool').visit('/swap')
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
})
it('swaps ETH for USDC', () => {
cy.visit('/swap', { ethereum: 'hardhat' })
cy.hardhat({ automine: false })
getBalance(USDC_MAINNET).then((initialBalance) => {
// Select USDC
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get(getTestSelector('token-search-input')).type(USDC_MAINNET.address)
cy.contains('USDC').click()
// Enter amount to swap
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
// Submit transaction
cy.get('#swap-button').click()
cy.contains('Review swap')
cy.contains('Confirm swap').click()
cy.wait('@eth_estimateGas').wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
cy.contains('Transaction submitted')
cy.get(getTestSelector('confirmation-close-icon')).click()
cy.contains('Transaction submitted').should('not.exist')
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
// Mine transaction
cy.hardhat().then((hardhat) => hardhat.mine())
cy.wait('@eth_getTransactionReceipt')
// Verify transaction
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
cy.get(getTestSelector('popups')).contains('Swapped')
const finalBalance = initialBalance + 1
cy.get('#swap-currency-output').contains(`Balance: ${finalBalance}`)
getBalance(USDC_MAINNET).should('eq', finalBalance)
})
})
})
})

View File

@@ -0,0 +1,80 @@
import { CurrencyAmount, SupportedChainId, WETH9 } from '@uniswap/sdk-core'
import { getBalance, getTestSelector } from '../../utils'
const WETH = WETH9[SupportedChainId.MAINNET]
describe('Swap wrap', () => {
beforeEach(() => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${WETH.address}`, { ethereum: 'hardhat' }).hardhat({
automine: false,
})
})
it('ETH to wETH is same value (wrapped swaps have no price impact)', () => {
cy.get('#swap-currency-input .token-amount-input').type('0.01').should('have.value', '0.01')
cy.get('#swap-currency-output .token-amount-input').should('have.value', '0.01')
cy.get('#swap-currency-output .token-amount-input').clear().type('0.02').should('have.value', '0.02')
cy.get('#swap-currency-input .token-amount-input').should('have.value', '0.02')
})
it('should be able to wrap ETH', () => {
getBalance(WETH).then((initialBalance) => {
cy.contains('Enter ETH amount')
// Enter amount to wrap
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', 1)
cy.get('#swap-currency-input .token-amount-input').should('have.value', 1)
// Submit transaction
cy.contains('Wrap').click()
cy.wait('@eth_estimateGas').wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
// Mine transaction
cy.hardhat().then((hardhat) => hardhat.mine())
cy.wait('@eth_getTransactionReceipt')
// Verify transaction
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
cy.get(getTestSelector('popups')).contains('Wrapped')
const finalBalance = initialBalance + 1
cy.get('#swap-currency-output').contains(`Balance: ${finalBalance}`)
getBalance(WETH).should('equal', finalBalance)
})
})
it('should be able to unwrap WETH', () => {
cy.hardhat().then(async (hardhat) => {
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(WETH, 1e18))
await hardhat.mine()
})
getBalance(WETH).then((initialBalance) => {
// Swap input/output to unwrap WETH
cy.get(getTestSelector('swap-currency-button')).click()
cy.contains('Enter WETH amount')
// Enter the amount to unwrap
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', 1)
cy.get('#swap-currency-input .token-amount-input').should('have.value', 1)
// Submit transaction
cy.contains('Unwrap').click()
cy.wait('@eth_estimateGas').wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
// Mine transaction
cy.hardhat().then((hardhat) => hardhat.mine())
cy.wait('@eth_getTransactionReceipt')
// Verify transaction
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
cy.get(getTestSelector('popups')).contains('Unwrapped')
const finalBalance = initialBalance - 1
cy.get('#swap-currency-input').contains(`Balance: ${finalBalance}`)
getBalance(WETH).should('equal', finalBalance)
})
})
})

View File

@@ -1,5 +1,10 @@
import { SupportedChainId, WETH9 } from '@uniswap/sdk-core'
import { UNI } from '../../src/constants/tokens'
import { getTestSelector } from '../utils'
const UNI_MAINNET = UNI[SupportedChainId.MAINNET]
const UNI_ADDRESS = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'
describe('Token details', () => {
@@ -83,4 +88,71 @@ describe('Token details', () => {
.should('include.text', 'Warning')
.and('include.text', "This token isn't traded on leading U.S. centralized exchanges")
})
describe('swapping', () => {
beforeEach(() => {
// On mobile widths, we just link back to /swap instead of rendering the swap component.
cy.viewport(1200, 800)
cy.visit(`/tokens/ethereum/${UNI_MAINNET.address}`, {
ethereum: 'hardhat',
}).then(() => {
cy.wait('@eth_blockNumber')
cy.scrollTo('top')
})
})
it('should have the expected output for a tokens detail page', () => {
cy.get(`#swap-currency-input .token-amount-input`).should('have.value', '')
cy.get(`#swap-currency-input .token-symbol-container`).should('contain.text', 'Select token')
cy.get(`#swap-currency-output .token-amount-input`).should('not.have.value')
cy.get(`#swap-currency-output .token-symbol-container`).should('contain.text', 'UNI')
})
it('should automatically navigate to the new TDP', () => {
cy.get(`#swap-currency-output .open-currency-select-button`).click()
cy.contains('WETH').click()
cy.url().should('include', `${WETH9[1].address}`)
cy.url().should('not.include', `${UNI_MAINNET.address}`)
})
it('should not share swap state with the main swap page', () => {
cy.get(`#swap-currency-output .token-symbol-container`).should('contain.text', 'UNI')
cy.get(`#swap-currency-input .open-currency-select-button`).click()
cy.contains('WETH').click()
cy.visit('/swap')
cy.contains('UNI').should('not.exist')
cy.contains('WETH').should('not.exist')
})
it('can enter an amount into input', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('0.001').should('have.value', '0.001')
})
it('zero swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0').should('have.value', '0.0')
})
it('invalid swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('\\').should('have.value', '')
})
it('can enter an amount into output', () => {
cy.get('#swap-currency-output .token-amount-input').clear().type('0.001').should('have.value', '0.001')
})
it('zero output amount', () => {
cy.get('#swap-currency-output .token-amount-input').clear().type('0.0').should('have.value', '0.0')
})
it('should show a L2 token even if the user is connected to a different network', () => {
cy.visit('/tokens', { ethereum: 'hardhat' })
cy.get(getTestSelector('tokens-network-filter-selected')).click()
cy.get(getTestSelector('tokens-network-filter-option-arbitrum')).click()
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Arbitrum')
cy.get(getTestSelector('token-table-row-ARB')).click()
cy.get(`#swap-currency-output .token-symbol-container`).should('contain.text', 'ARB')
cy.get(getTestSelector('open-settings-dialog-button')).should('be.disabled')
cy.contains('Connect to Arbitrum').should('exist')
})
})
})

View File

@@ -1,83 +1,24 @@
describe.skip('Token explore filter', () => {
before(() => {
cy.visit('/')
})
it('should filter correctly by uni search term', () => {
describe('Token explore filter', () => {
beforeEach(() => {
cy.visit('/tokens')
cy.get('[data-cy="token-name"]').then(($els) => {
const tokenNames = Array.from($els, (el) => el.innerText)
const filteredByUni = tokenNames.filter((tokenName) => tokenName.toLowerCase().includes('uni'))
cy.wrap(filteredByUni).as('filteredByUni')
})
cy.get('[data-cy="explore-tokens-search-input"]')
.clear()
.type('uni')
.type('{enter}')
.then(() => {
cy.get('[data-cy="token-name"]').its('length').should('be.lt', 100)
cy.get('@filteredByUni').then((filteredByUni) => {
cy.get('[data-cy="token-name"]').then(($els) => {
const tokenNames = Array.from($els, (el) => el.innerText)
expect(tokenNames.length).to.equal(filteredByUni.length)
tokenNames.forEach((tokenName) => {
expect(filteredByUni).to.include(tokenName)
})
})
})
})
})
function aliasFilteredTokens(filter: string) {
cy.get('[data-cy="token-name"]').then((tokens) => {
cy.wrap(Array.from(tokens).filter((token) => token.innerText.toLowerCase().includes(filter))).as('filteredTokens')
})
}
function searchFor(filter: string) {
cy.get('[data-cy="explore-tokens-search-input"]').clear().type(filter).type('{enter}')
}
it('should filter correctly by dao search term', () => {
cy.visit('/tokens')
cy.get('[data-cy="token-name"]').then(($els) => {
const tokenNames = Array.from($els, (el) => el.innerText)
const filteredByDao = tokenNames.filter((tokenName) => tokenName.toLowerCase().includes('dao'))
cy.wrap(filteredByDao).as('filteredByDao')
aliasFilteredTokens('dao')
searchFor('dao')
cy.get('@filteredTokens').then((filteredTokens) => {
cy.get('[data-cy="token-name"]').should('deep.equal', filteredTokens)
})
cy.get('[data-cy="explore-tokens-search-input"]')
.clear()
.type('dao')
.type('{enter}')
.then(() => {
cy.get('[data-cy="token-name"]').its('length').should('be.lt', 100)
cy.get('@filteredByDao').then((filteredByDao) => {
cy.get('[data-cy="token-name"]').then(($els) => {
const tokenNames = Array.from($els, (el) => el.innerText)
expect(tokenNames.length).to.equal(filteredByDao.length)
tokenNames.forEach((tokenName) => {
expect(filteredByDao).to.include(tokenName)
})
})
})
})
})
it('should filter correctly by ax search term', () => {
cy.visit('/tokens')
cy.get('[data-cy="token-name"]').then(($els) => {
const tokenNames = Array.from($els, (el) => el.innerText)
const filteredByAx = tokenNames.filter((tokenName) => tokenName.toLowerCase().includes('ax'))
cy.wrap(filteredByAx).as('filteredByAx')
})
cy.get('[data-cy="explore-tokens-search-input"]')
.clear()
.type('ax')
.type('{enter}')
.then(() => {
cy.get('[data-cy="token-name"]').its('length').should('be.lt', 100)
cy.get('@filteredByAx').then((filteredByAx) => {
cy.get('[data-cy="token-name"]').then(($els) => {
const tokenNames = Array.from($els, (el) => el.innerText)
expect(tokenNames.length).to.equal(filteredByAx.length)
tokenNames.forEach((tokenName) => {
expect(filteredByAx).to.include(tokenName)
})
})
})
})
})
})

View File

@@ -0,0 +1,40 @@
import { getTestSelector } from '../../utils'
describe('disconnect wallet', () => {
it('should clear state', () => {
cy.visit('/swap', { ethereum: 'hardhat' })
cy.get('#swap-currency-input .token-amount-input').clear().type('1')
// Verify wallet is connected
cy.hardhat().then((hardhat) => cy.contains(hardhat.wallet.address.substring(0, 6)))
cy.contains('Balance:')
// Disconnect the wallet
cy.hardhat().then((hardhat) => cy.contains(hardhat.wallet.address.substring(0, 6)).click())
cy.get(getTestSelector('wallet-disconnect')).click()
cy.get(getTestSelector('wallet-disconnect')).contains('Disconnect')
cy.get(getTestSelector('wallet-disconnect')).click()
// Verify wallet has disconnected
cy.contains('Connect a wallet').should('exist')
cy.get(getTestSelector('navbar-connect-wallet')).contains('Connect')
cy.contains('Connect Wallet')
// Verify swap input is cleared
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
})
})
describe('connect wallet', () => {
it('should load state', () => {
cy.visit('/swap', { ethereum: 'hardhat', userState: {} })
// Connect the wallet
cy.get(getTestSelector('navbar-connect-wallet')).contains('Connect').click()
cy.contains('MetaMask').click()
// Verify wallet is connected
cy.hardhat().then((hardhat) => cy.contains(hardhat.wallet.address.substring(0, 6)))
cy.contains('Balance:')
})
})

View File

@@ -1,96 +1,111 @@
import { getTestSelector } from '../utils'
function visit(darkMode: boolean) {
cy.visit('/swap', {
onBeforeLoad(win) {
cy.stub(win, 'matchMedia')
.withArgs('(prefers-color-scheme: dark)')
.returns({
matches: darkMode,
addEventListener() {
// do nothing
},
})
},
})
}
describe('Wallet Dropdown', () => {
before(() => {
cy.visit('/pools')
function itShouldChangeTheTheme() {
it('should change the theme', () => {
cy.get(getTestSelector('theme-lightmode')).click()
cy.get(getTestSelector('theme-lightmode')).should('not.have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-darkmode')).should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-auto')).should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-darkmode')).click()
cy.get(getTestSelector('theme-lightmode')).should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-darkmode')).should('not.have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-auto')).should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-auto')).click()
cy.get(getTestSelector('theme-lightmode')).should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-darkmode')).should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-auto')).should('not.have.css', 'background-color', 'rgba(0, 0, 0, 0)')
})
}
function itShouldChangeTheLanguage() {
it('should select a language', () => {
cy.get(getTestSelector('wallet-language-item')).contains('Deutsch').click({ force: true })
cy.get(getTestSelector('wallet-header')).should('contain', 'Sprache')
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
cy.get(getTestSelector('wallet-back')).click()
})
}
describe('connected', () => {
beforeEach(() => {
cy.visit('/')
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
})
itShouldChangeTheTheme()
itShouldChangeTheLanguage()
})
it('should change the theme', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.get(getTestSelector('theme-lightmode')).click()
cy.get(getTestSelector('theme-lightmode')).should('not.have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-darkmode')).should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-auto')).should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-darkmode')).click()
cy.get(getTestSelector('theme-lightmode')).should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-darkmode')).should('not.have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-auto')).should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-auto')).click()
cy.get(getTestSelector('theme-lightmode')).should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-darkmode')).should('have.css', 'background-color', 'rgba(0, 0, 0, 0)')
cy.get(getTestSelector('theme-auto')).should('not.have.css', 'background-color', 'rgba(0, 0, 0, 0)')
describe('disconnected', () => {
beforeEach(() => {
cy.visit('/')
cy.get(getTestSelector('web3-status-connected')).click()
// click twice, first time to show confirmation, second to confirm
cy.get(getTestSelector('wallet-disconnect')).click()
cy.get(getTestSelector('wallet-disconnect')).should('contain', 'Disconnect')
cy.get(getTestSelector('wallet-disconnect')).click()
cy.get(getTestSelector('wallet-settings')).click()
})
itShouldChangeTheTheme()
itShouldChangeTheLanguage()
})
it('should select a language', () => {
cy.get(getTestSelector('wallet-language-item')).contains('Deutsch').click({ force: true })
cy.get(getTestSelector('wallet-header')).should('contain', 'Sprache')
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
cy.get(getTestSelector('wallet-back')).click()
describe('with color theme', () => {
function visitSwapWithColorTheme({ dark }: { dark: boolean }) {
cy.visit('/swap', {
onBeforeLoad(win) {
cy.stub(win, 'matchMedia')
.withArgs('(prefers-color-scheme: dark)')
.returns({
matches: dark,
addEventListener() {
/* noop */
},
removeEventListener() {
/* noop */
},
})
},
})
}
it('should properly use dark system theme when auto theme setting is selected', () => {
visitSwapWithColorTheme({ dark: true })
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.get(getTestSelector('theme-auto')).click()
cy.get(getTestSelector('wallet-header')).should('have.css', 'color', 'rgb(152, 161, 192)')
})
it('should properly use light system theme when auto theme setting is selected', () => {
visitSwapWithColorTheme({ dark: false })
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.get(getTestSelector('theme-auto')).click()
cy.get(getTestSelector('wallet-header')).should('have.css', 'color', 'rgb(119, 128, 160)')
})
})
it('should change the theme when not connected', () => {
cy.get(getTestSelector('wallet-disconnect')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.get(getTestSelector('theme-lightmode')).should('exist')
})
describe('mobile', () => {
beforeEach(() => {
cy.viewport('iphone-6').visit('/')
})
it('should select a language when not connected', () => {
cy.get(getTestSelector('wallet-language-item')).contains('Deutsch').click({ force: true })
cy.get(getTestSelector('wallet-header')).should('contain', 'Sprache')
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
cy.get(getTestSelector('wallet-back')).click()
})
it('should dismiss the wallet bottom sheet when clicking buy crypto', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-buy-crypto')).click()
cy.contains('Buy crypto').should('not.be.visible')
})
it('should properly use dark system theme when auto theme setting is selected', () => {
visit(true)
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.get(getTestSelector('theme-auto')).click()
cy.get(getTestSelector('wallet-header')).should('have.css', 'color', 'rgb(152, 161, 192)')
})
it('should properly use light system theme when auto theme setting is selected', () => {
visit(false)
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.get(getTestSelector('theme-auto')).click()
cy.get(getTestSelector('wallet-header')).should('have.css', 'color', 'rgb(119, 128, 160)')
})
it('should dismiss the wallet bottom sheet when clicking buy crypto', () => {
visit(false)
cy.viewport('iphone-6')
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-buy-crypto')).click()
cy.contains('Buy crypto').should('not.be.visible')
})
it('should use a bottom sheet and dismiss when on a mobile screen size', () => {
visit(true)
cy.viewport('iphone-6')
cy.get(getTestSelector('web3-status-connected')).click()
cy.root().click(15, 40)
cy.get(getTestSelector('wallet-settings')).should('not.be.visible')
it('should use a bottom sheet and dismiss when on a mobile screen size', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.root().click(15, 40)
cy.get(getTestSelector('wallet-settings')).should('not.be.visible')
})
})
})

View File

@@ -1 +0,0 @@
export const WETH_GOERLI = '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6'

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,80 @@
import 'cypress-hardhat/lib/browser'
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
import { FeatureFlag } from '../../src/featureFlags'
import { UserState } from '../../src/state/user/reducer'
import { CONNECTED_WALLET_USER_STATE } from '../utils/user-state'
import { injected } from './ethereum'
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface ApplicationWindow {
ethereum: Eip1193Bridge
}
interface VisitOptions {
serviceWorker?: true
featureFlags?: Array<FeatureFlag>
/**
* The mock ethereum provider to inject into the page.
* @default 'goerli'
*/
// TODO(INFRA-175): Migrate all usage of 'goerli' to 'hardhat'.
ethereum?: 'goerli' | 'hardhat'
/**
* Initial user state.
* @default {@type import('../utils/user-state').CONNECTED_WALLET_USER_STATE}
*/
userState?: Partial<UserState>
}
}
}
// sets up the injected provider to be a mock ethereum provider with the given mnemonic/index
// eslint-disable-next-line no-undef
Cypress.Commands.overwrite(
'visit',
(original, url: string | Partial<Cypress.VisitOptions>, options?: Partial<Cypress.VisitOptions>) => {
if (typeof url !== 'string') throw new Error('Invalid arguments. The first argument to cy.visit must be the path.')
// Add a hash in the URL if it is not present (to use hash-based routing correctly with queryParams).
let hashUrl = url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url
if (options?.ethereum === 'goerli') hashUrl += `${url.includes('?') ? '&' : '?'}chain=goerli`
return cy
.intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 })
.provider()
.then((provider) =>
original({
...options,
url: hashUrl,
onBeforeLoad(win) {
options?.onBeforeLoad?.(win)
// We want to test from a clean state, so we clear the local storage (which clears redux).
win.localStorage.clear()
// Set initial user state.
win.localStorage.setItem(
'redux_localstorage_simple_user', // storage key for the user reducer using 'redux-localstorage-simple'
JSON.stringify(options?.userState ?? CONNECTED_WALLET_USER_STATE)
)
// Set feature flags, if configured.
if (options?.featureFlags) {
const featureFlags = options.featureFlags.reduce((flags, flag) => ({ ...flags, [flag]: 'enabled' }), {})
win.localStorage.setItem('featureFlags', JSON.stringify(featureFlags))
}
// Inject the mock ethereum provider.
if (options?.ethereum === 'hardhat') {
win.ethereum = provider
} else {
win.ethereum = injected
}
},
})
)
}
)

View File

@@ -6,115 +6,17 @@
// ***********************************************************
import '@cypress/code-coverage/support'
import 'cypress-hardhat/lib/browser'
import './commands'
import './setupTests'
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
import assert from 'assert'
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
import { UserState } from '../../src/state/user/reducer'
import { CONNECTED_WALLET_USER_STATE } from '../utils/user-state'
import { injected } from './ethereum'
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface ApplicationWindow {
ethereum: Eip1193Bridge
}
interface VisitOptions {
serviceWorker?: true
featureFlags?: Array<FeatureFlag>
/**
* The mock ethereum provider to inject into the page.
* @default 'goerli'
*/
// TODO(INFRA-175): Migrate all usage of 'goerli' to 'hardhat'.
ethereum?: 'goerli' | 'hardhat'
/**
* Initial user state.
* @default {@type import('../utils/user-state').CONNECTED_WALLET_USER_STATE}
*/
userState?: Partial<UserState>
}
}
}
// sets up the injected provider to be a mock ethereum provider with the given mnemonic/index
// eslint-disable-next-line no-undef
Cypress.Commands.overwrite(
'visit',
(original, url: string | Partial<Cypress.VisitOptions>, options?: Partial<Cypress.VisitOptions>) => {
assert(typeof url === 'string')
// Add a hash in the URL if it is not present (to use hash-based routing correctly with queryParams).
let hashUrl = url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url
if (options?.ethereum === 'goerli') hashUrl += `${url.includes('?') ? '&' : '?'}chain=goerli`
return cy
.intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 })
.provider()
.then((provider) =>
original({
...options,
url: hashUrl,
onBeforeLoad(win) {
options?.onBeforeLoad?.(win)
// We want to test from a clean state, so we clear the local storage (which clears redux).
win.localStorage.clear()
// Set initial user state.
win.localStorage.setItem(
'redux_localstorage_simple_user', // storage key for the user reducer using 'redux-localstorage-simple'
JSON.stringify(options?.userState ?? CONNECTED_WALLET_USER_STATE)
)
// Set feature flags, if configured.
if (options?.featureFlags) {
const featureFlags = options.featureFlags.reduce((flags, flag) => ({ ...flags, [flag]: 'enabled' }), {})
win.localStorage.setItem('featureFlags', JSON.stringify(featureFlags))
}
// Inject the mock ethereum provider.
if (options?.ethereum === 'hardhat') {
win.ethereum = provider
} else {
win.ethereum = injected
}
},
})
)
}
)
beforeEach(() => {
// Many API calls enforce that requests come from our app, so we must mock Origin and Referer.
cy.intercept('*', (req) => {
req.headers['referer'] = 'https://app.uniswap.org'
req.headers['origin'] = 'https://app.uniswap.org'
})
// Infura uses a test endpoint, which allow-lists http://localhost:3000 instead.
.intercept(/infura.io/, (req) => {
req.headers['referer'] = 'http://localhost:3000'
req.headers['origin'] = 'http://localhost:3000'
req.alias = req.body.method
req.continue()
})
// Mock Amplitude responses to avoid analytics from tests.
.intercept('https://api.uniswap.org/v1/amplitude-proxy', (req) => {
const requestBody = JSON.stringify(req.body)
const byteSize = new Blob([requestBody]).size
req.reply(
JSON.stringify({
code: 200,
server_upload_time: Date.now(),
payload_size_bytes: byteSize,
events_ingested: req.body.events.length,
})
)
})
})
// Squelch logs from fetches, as they clutter the logs so much as to make them unusable.
// See https://docs.cypress.io/api/commands/intercept#Disabling-logs-for-a-request.
// TODO(https://github.com/cypress-io/cypress/issues/26069): Squelch only wildcard logs once Cypress allows it.
const log = Cypress.log
Cypress.log = function (options, ...args) {
if (options.displayName === 'script' || options.name === 'request') return
return log(options, ...args)
} as typeof log
Cypress.on('uncaught:exception', () => {
// returning false here prevents Cypress from failing the test

View File

@@ -0,0 +1,63 @@
// @ts-ignore
import TokenListJSON from '@uniswap/default-token-list'
import { CyHttpMessages } from 'cypress/types/net-stubbing'
beforeEach(() => {
// Many API calls enforce that requests come from our app, so we must mock Origin and Referer.
cy.intercept('*', (req) => {
req.headers['referer'] = 'https://app.uniswap.org'
req.headers['origin'] = 'https://app.uniswap.org'
})
// Infura uses a test endpoint, which allow-lists http://localhost:3000 instead.
cy.intercept(/infura.io/, (req) => {
req.headers['referer'] = 'http://localhost:3000'
req.headers['origin'] = 'http://localhost:3000'
req.alias = req.body.method
req.continue()
})
// Log requests to hardhat.
cy.intercept(/:8545/, logJsonRpc)
// Mock analytics responses to avoid analytics in tests.
cy.intercept('https://api.uniswap.org/v1/amplitude-proxy', (req) => {
const requestBody = JSON.stringify(req.body)
const byteSize = new Blob([requestBody]).size
req.reply(
JSON.stringify({
code: 200,
server_upload_time: Date.now(),
payload_size_bytes: byteSize,
events_ingested: req.body.events.length,
})
)
}).intercept('https://*.sentry.io', { statusCode: 200 })
// 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 })
.intercept('https://gateway.ipfs.io/ipns/unsupportedtokens.uniswap.org', { statusCode: 404 })
// Reset hardhat between tests to ensure isolation.
// This resets the fork, as well as options like automine.
cy.hardhat().then((hardhat) => hardhat.reset())
})
function logJsonRpc(req: CyHttpMessages.IncomingHttpRequest) {
req.alias = req.body.method
const log = Cypress.log({
autoEnd: false,
name: req.body.method,
message: req.body.params?.map((param: unknown) =>
typeof param === 'object' ? '{...}' : param?.toString().substring(0, 10)
),
})
req.on('after:response', (res) => {
if (res.statusCode === 200) {
log.end()
} else {
log.error(new Error(`${res.statusCode}: ${res.statusMessage}`))
}
})
}

View File

@@ -7,7 +7,8 @@
"strict": true,
"target": "ES5",
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/cypress", // avoid clobbering the build tsbuildinfo
"types": ["cypress", "node"]
"types": ["cypress", "node"],
"jsx": "react"
},
"exclude": ["node_modules"],
"include": ["**/*.ts"],

View File

@@ -1,3 +1,13 @@
import { Currency } from '@uniswap/sdk-core'
export const getTestSelector = (selectorId: string) => `[data-testid=${selectorId}]`
export const getTestSelectorStartsWith = (selectorId: string) => `[data-testid^=${selectorId}]`
/** Gets the balance of a token as a Chainable. */
export function getBalance(token: Currency) {
return cy
.hardhat()
.then((hardhat) => hardhat.getBalance(hardhat.wallet, token))
.then((balance) => Number(balance.toFixed(1)))
}

View File

@@ -0,0 +1,36 @@
/* eslint-env node */
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce use of retry() for dynamic imports',
category: 'Best Practices',
recommended: false,
},
schema: [],
},
create(context) {
return {
ImportExpression(node) {
const grandParent = node.parent.parent
if (
!(
grandParent &&
grandParent.type === 'CallExpression' &&
// Technically, we are only checking that a function named `retry` wraps the dynamic import.
// We do not go as far as enforcing that it is import('utils/retry').retry
grandParent.callee.name === 'retry' &&
grandParent.arguments.length === 1 &&
grandParent.arguments[0].type === 'ArrowFunctionExpression'
)
) {
context.report({
node,
message: 'Dynamic import should be wrapped in retry (see `utils/retry.ts`): `retry(() => import(...))`',
})
}
},
}
},
}

View File

@@ -0,0 +1,44 @@
/* eslint-env node */
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'Enforce the use of optional object fields',
category: 'Best Practices',
recommended: true,
},
fixable: 'code',
schema: [],
},
create(context) {
return {
'TSPropertySignature > TSTypeAnnotation > TSUnionType': (node) => {
const types = node.types
const hasUndefined = types.some((typeNode) => typeNode.type === 'TSUndefinedKeyword')
if (hasUndefined) {
const typesWithoutUndefined = types.filter((typeNode) => typeNode.type !== 'TSUndefinedKeyword')
// If there is more than one type left after removing 'undefined',
// join them together with ' | ' to create a new union type.
const newTypeSource =
typesWithoutUndefined.length > 1
? typesWithoutUndefined.map((typeNode) => context.getSourceCode().getText(typeNode)).join(' | ')
: context.getSourceCode().getText(typesWithoutUndefined[0])
context.report({
node,
message: `Prefer optional properties to "Type | undefined".`,
fix(fixer) {
const propertySignature = node.parent.parent
const isAlreadyOptional = propertySignature.optional
const newTypeAnnotation = isAlreadyOptional ? `: ${newTypeSource}` : `?: ${newTypeSource}`
return fixer.replaceText(node.parent, newTypeAnnotation)
},
})
}
},
}
},
}

View File

@@ -1,6 +1,6 @@
/* eslint-env node */
const defaultConfig = require('./graphql.config')
const defaultConfig = require('./graphql.data.config')
module.exports = {
src: defaultConfig.src,

View File

@@ -3,7 +3,8 @@ require('dotenv').config()
// Block selection is arbitrary, as e2e tests will build up their own state.
// The only requirement is that all infrastructure under test (eg Permit2 contracts) are already deployed.
const BLOCK_NUMBER = 17023328
// TODO(WEB-2187): Make more dynamic to avoid manually updating
const BLOCK_NUMBER = 17388567
const mainnetFork = {
url: `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
@@ -19,11 +20,11 @@ module.exports = {
chainId: 1,
forking: mainnetFork,
accounts: {
count: 1,
count: 2,
},
mining: {
auto: true,
interval: 0,
auto: true, // automine to make tests easier to write.
interval: 0, // do not interval mine so that tests remain deterministic
},
},
},

View File

@@ -1,8 +1,77 @@
/* eslint-env node */
import { default as babelExtractor } from '@lingui/cli/api/extractors/babel'
import { createHash } from 'crypto'
import { mkdirSync, readFileSync, writeFileSync } from 'fs'
import { existsSync } from 'fs'
import * as path from 'path'
/** A custom caching extractor built on top of babelExtractor. */
const cachingExtractor: typeof babelExtractor = {
/** Delegates to babelExtractor.match. */
match(filename: string) {
return babelExtractor.match(filename)
},
/**
* Checks a cache before extraction, only delegating to babelExtractor.extract if the file has changed.
*
* The lingui extractor works by extracting JSON (the catalog) from `filename` to `buildDir/filename.json`.
* Caching works by man-in-the-middling this:
* - File freshness is computed as a hash of `filename` contents.
* - Before extracting, we check the cache to see if we already have a fresh catalog for the file.
* If we do, we copy it to `localeDir/filename.json`. Copying is significantly faster than extracting.
* - After extracting, we copy the catalog to the cache.
*/
extract(filename: string, localeDir: string, ...options: unknown[]) {
// This runs from node_modules/@lingui/conf, so we need to back out to the root.
const root = __dirname.split('/node_modules')[0]
// This logic mimics catalogFilename in @lingui/babel-plugin-extract-messages.
const buildDir = path.join(localeDir, '_build')
const localePath = path.join(buildDir, filename + '.json')
const filePath = path.join(root, filename)
const fileHash = createHash('sha256').update(readFileSync(filePath)).digest('hex')
const cacheRoot = path.join(root, 'node_modules/.cache/lingui')
const cachePath = path.join(cacheRoot, filename + '.json')
// If we have a matching cached copy of the catalog, we can copy it to localePath and return early.
if (existsSync(cachePath)) {
const { hash, catalog } = JSON.parse(readFileSync(cachePath, 'utf8'))
if (hash === fileHash) {
if (catalog) {
mkdirSync(path.dirname(localePath), { recursive: true })
writeFileSync(localePath, JSON.stringify(catalog, null, 2))
}
return
}
}
babelExtractor.extract(filename, localeDir, ...options)
// Cache the extracted catalog.
mkdirSync(path.dirname(cachePath), { recursive: true })
if (existsSync(localePath)) {
const catalog = JSON.parse(readFileSync(localePath, 'utf8'))
writeFileSync(cachePath, JSON.stringify({ hash: fileHash, catalog }))
} else {
writeFileSync(cachePath, JSON.stringify({ hash: fileHash }))
}
},
}
const linguiConfig = {
catalogs: [
{
path: '<rootDir>/src/locales/{locale}',
include: ['<rootDir>/src'],
include: ['<rootDir>/src/**/*.ts', '<rootDir>/src/**/*.tsx'],
exclude: [
'<rootDir>/src/**/*.d.ts',
'<rootDir>/src/**/*.test.*',
'<rootDir>/src/types/v3/**',
'<rootDir>/src/abis/types/**',
'<rootDir>/src/graphql/**/__generated__/**',
],
},
],
compileNamespace: 'cjs',
@@ -53,6 +122,7 @@ const linguiConfig = {
runtimeConfigModule: ['@lingui/core', 'i18n'],
sourceLocale: 'en-US',
pseudoLocale: 'pseudo',
extractors: [cachingExtractor],
}
export default linguiConfig

View File

@@ -5,18 +5,20 @@
"homepage": ".",
"license": "GPL-3.0-or-later",
"scripts": {
"ajv": "node scripts/compile-ajv-validators.js",
"contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/abis/types \"./src/abis/**/*.json\"",
"contracts:compile:v3": "typechain --target ethers-v5 --out-dir src/types/v3 \"./node_modules/@uniswap/**/artifacts/contracts/**/*[!dbg].json\"",
"contracts:compile": "yarn contracts:compile:abi && yarn contracts:compile:v3",
"contracts": "yarn contracts:compile:abi && yarn contracts:compile:v3",
"graphql:fetch": "node scripts/fetch-schema.js",
"graphql:generate:data": "graphql-codegen --config apollo-codegen.ts",
"graphql:generate:thegraph": "graphql-codegen --config apollo-codegen_thegraph.ts",
"graphql:generate:data": "graphql-codegen --config graphql.data.codegen.config.ts",
"graphql:generate:thegraph": "graphql-codegen --config graphql.thegraph.codegen.config.ts",
"graphql:generate": "yarn graphql:generate:data && yarn graphql:generate:thegraph",
"prei18n:extract": "node scripts/prei18n-extract.js",
"graphql": "yarn graphql:fetch && yarn graphql:generate",
"i18n:extract": "lingui extract --locale en-US",
"i18n:compile": "yarn i18n:extract && lingui compile",
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
"prepare": "yarn contracts:compile && yarn graphql:fetch && yarn graphql:generate && yarn i18n:compile",
"i18n:pseudo": "lingui extract --locale pseudo",
"i18n:compile": "lingui compile",
"i18n": "yarn i18n:extract --clean && yarn i18n:compile",
"prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\"",
"start": "craco start",
"build": "craco build",
"build:e2e": "REACT_APP_CSP_ALLOW_UNSAFE_EVAL=true REACT_APP_ADD_COVERAGE_INSTRUMENTATION=true craco build",
@@ -65,7 +67,7 @@
]
},
"devDependencies": {
"@craco/craco": "6.4.3",
"@craco/craco": "^7.1.0",
"@ethersproject/experimental": "^5.4.0",
"@lingui/cli": "^3.9.0",
"@testing-library/jest-dom": "^5.16.4",
@@ -93,26 +95,32 @@
"@types/rebass": "^4.0.7",
"@types/styled-components": "^5.1.25",
"@types/testing-library__cypress": "^5.0.5",
"@types/ua-parser-js": "^0.7.35",
"@types/ua-parser-js": "^0.7.36",
"@types/uuid": "^8.3.4",
"@types/wcag-contrast": "^3.0.0",
"@uniswap/eslint-config": "^1.1.1",
"@uniswap/default-token-list": "^9.6.0",
"@uniswap/eslint-config": "^1.2.0",
"@vanilla-extract/babel-plugin": "^1.1.7",
"@vanilla-extract/jest-transform": "^1.1.1",
"@vanilla-extract/webpack-plugin": "^2.1.11",
"babel-plugin-istanbul": "^6.1.1",
"cypress": "10.3.1",
"cypress-hardhat": "^2.2.0",
"buffer": "^6.0.3",
"concurrently": "^8.0.1",
"cypress": "12.12.0",
"cypress-hardhat": "^2.3.0",
"env-cmd": "^10.1.0",
"eslint": "^7.11.0",
"eslint-plugin-import": "^2.27",
"eslint-plugin-rulesdir": "^0.2.2",
"hardhat": "^2.14.0",
"jest-fail-on-console": "^3.1.1",
"jest-fetch-mock": "^3.0.3",
"jest-styled-components": "^7.0.8",
"ms.macro": "^2.0.0",
"path-browserify": "^1.0.1",
"prettier": "^2.7.1",
"react-scripts": "^4.0.3",
"process": "^0.11.10",
"react-scripts": "^5.0.1",
"resize-observer-polyfill": "^1.5.1",
"serve": "^11.3.2",
"source-map-explorer": "^2.5.3",
@@ -133,6 +141,7 @@
"@graphql-codegen/typescript-operations": "^2.5.8",
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
"@graphql-codegen/typescript-resolvers": "^2.7.8",
"@juggle/resize-observer": "^3.4.0",
"@lingui/core": "^3.14.0",
"@lingui/macro": "^3.14.0",
"@lingui/react": "^3.14.0",
@@ -149,24 +158,23 @@
"@types/react-window-infinite-loader": "^1.0.6",
"@uniswap/analytics": "^1.3.1",
"@uniswap/analytics-events": "^2.10.0",
"@uniswap/conedison": "^1.4.0",
"@uniswap/conedison": "^1.7.1",
"@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.3.0",
"@uniswap/sdk-core": "^3.2.2",
"@uniswap/smart-order-router": "^3.6.1",
"@uniswap/token-lists": "^1.0.0-beta.30",
"@uniswap/universal-router-sdk": "^1.3.8",
"@uniswap/sdk-core": "^3.2.3",
"@uniswap/smart-order-router": "^3.12.1",
"@uniswap/token-lists": "^1.0.0-beta.31",
"@uniswap/universal-router-sdk": "^1.5.1",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v2-sdk": "^3.0.1",
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-sdk": "^3.9.0",
"@uniswap/widgets": "^2.49.0",
"@vanilla-extract/css": "^1.7.2",
"@vanilla-extract/css-utils": "^0.1.2",
"@vanilla-extract/dynamic": "^2.0.2",
@@ -178,7 +186,6 @@
"@visx/react-spring": "^2.12.2",
"@visx/responsive": "^2.10.0",
"@visx/shape": "^2.11.1",
"@walletconnect/ethereum-provider": "^1.8.0",
"@web3-react/coinbase-wallet": "^8.2.0",
"@web3-react/core": "^8.2.0",
"@web3-react/eip1193": "^8.2.0",
@@ -189,6 +196,9 @@
"@web3-react/types": "^8.2.0",
"@web3-react/url": "^8.2.0",
"@web3-react/walletconnect": "^8.2.0",
"@web3-react/walletconnect-v2": "8.3.2",
"ajv": "^8.11.0",
"ajv-formats": "^2.1.1",
"array.prototype.flat": "^1.2.4",
"array.prototype.flatmap": "^1.2.4",
"cids": "^1.0.0",
@@ -237,8 +247,8 @@
"statsig-react": "^1.22.0",
"styled-components": "^5.3.5",
"tiny-invariant": "^1.2.0",
"ua-parser-js": "^0.7.28",
"use-resize-observer": "^9.0.2",
"ua-parser-js": "^1.0.35",
"use-resize-observer": "^9.1.0",
"uuid": "^8.3.2",
"video-extensions": "^1.2.0",
"wcag-contrast": "^3.0.0",
@@ -249,21 +259,9 @@
"workbox-routing": "^6.1.0",
"zustand": "^4.3.6"
},
"resolutions": {
"@web3-react/coinbase-wallet": "^8.2.0",
"@web3-react/core": "^8.2.0",
"@web3-react/eip1193": "^8.2.0",
"@web3-react/empty": "^8.2.0",
"@web3-react/gnosis-safe": "^8.2.0",
"@web3-react/metamask": "^8.2.0",
"@web3-react/network": "^8.2.0",
"@web3-react/types": "^8.2.0",
"@web3-react/url": "^8.2.0",
"@web3-react/walletconnect": "^8.2.0"
},
"engines": {
"npm": "please-use-yarn",
"node": "14",
"node": "18.x",
"yarn": ">=1.22"
}
}

View File

@@ -0,0 +1,20 @@
/* eslint-env node */
const fs = require('fs')
const path = require('path')
const Ajv = require('ajv')
const standaloneCode = require('ajv/dist/standalone').default
const addFormats = require('ajv-formats')
const schema = require('@uniswap/token-lists/dist/tokenlist.schema.json')
const tokenListAjv = new Ajv({ code: { source: true, esm: true } })
addFormats(tokenListAjv)
const validateTokenList = tokenListAjv.compile(schema)
let tokenListModuleCode = standaloneCode(tokenListAjv, validateTokenList)
fs.writeFileSync(path.join(__dirname, '../src/utils/__generated__/validateTokenList.js'), tokenListModuleCode)
const tokensAjv = new Ajv({ code: { source: true, esm: true } })
addFormats(tokensAjv)
const validateTokens = tokensAjv.compile({ ...schema, required: ['tokens'] })
let tokensModuleCode = standaloneCode(tokensAjv, validateTokens)
fs.writeFileSync(path.join(__dirname, '../src/utils/__generated__/validateTokens.js'), tokensModuleCode)

View File

@@ -4,13 +4,13 @@ require('dotenv').config({ path: '.env.production' })
const child_process = require('child_process')
const fs = require('fs/promises')
const { promisify } = require('util')
const dataConfig = require('../graphql.config')
const thegraphConfig = require('../graphql_thegraph.config')
const dataConfig = require('../graphql.data.config')
const thegraphConfig = require('../graphql.thegraph.config')
const exec = promisify(child_process.exec)
function fetchSchema(url, outputFile) {
exec(`npx get-graphql-schema --h Origin=https://app.uniswap.org ${url}`)
exec(`yarn --silent get-graphql-schema --h Origin=https://app.uniswap.org ${url}`)
.then(({ stderr, stdout }) => {
if (stderr) {
throw new Error(stderr)

View File

@@ -1,10 +0,0 @@
/* eslint-env node */
const { exec } = require('child_process')
const isWindows = process.platform === 'win32' || /^(msys|cygwin)$/.test(process.env.OSTYPE)
if (isWindows) {
exec(`type nul > src/locales/en-US.po`)
} else {
exec(`touch src/locales/en-US.po`)
}

View File

@@ -0,0 +1 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="36" height="36" rx="18" fill="#7D00FF"/><path d="M24.933 14.14a3.07 3.07 0 0 0 0-6.14 3.07 3.07 0 0 0 0 6.14ZM15.5 28A7.495 7.495 0 0 1 8 20.493a7.495 7.495 0 0 1 7.5-7.506c4.149 0 7.5 3.354 7.5 7.506A7.495 7.495 0 0 1 15.5 28Z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 356 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.2601 18.3918C12.9161 18.5558 12.5141 18.6779 12.0591 18.7599L5.48907 19.9099C3.29907 20.2999 2.00896 19.3999 1.62896 17.2099L0.0891657 8.45987C-0.300834 6.26987 0.599117 4.97988 2.78912 4.58988L4.58697 4.27384C4.79197 4.23784 4.97114 4.41686 4.93414 4.62186L3.75811 11.2799C3.22811 14.3099 4.65803 16.3499 7.67803 16.8799C7.67803 16.8799 12.8971 17.7969 13.1661 17.8489C13.4981 17.9088 13.5511 18.2628 13.2601 18.3918ZM19.9131 5.10587L18.3689 13.8598C17.997 15.9678 16.7811 16.8688 14.7361 16.5888C14.6581 16.5778 14.5881 16.5779 14.5071 16.5639L7.94195 15.4059C5.75295 15.0199 4.85209 13.7329 5.23809 11.5439L6.58111 3.92783L6.78204 2.78983C7.16804 0.600828 8.4551 -0.300151 10.6441 0.0858488L17.21 1.24387C19.398 1.62987 20.2991 2.91787 19.9131 5.10587ZM13.554 11.8958C13.626 11.4878 13.3541 11.0988 12.9461 11.0268L8.8421 10.3028C8.4361 10.2298 8.04518 10.5039 7.97418 10.9109C7.90218 11.3189 8.17409 11.7079 8.58209 11.7799L12.6861 12.5039C12.7301 12.5119 12.7739 12.5149 12.8169 12.5149C13.1739 12.5159 13.49 12.2598 13.554 11.8958ZM16.597 9.03482C16.669 8.62682 16.3971 8.23787 15.9891 8.16587L9.42413 7.00785C9.02013 6.93685 8.62696 7.20888 8.55596 7.61588C8.48396 8.02388 8.75612 8.41284 9.16412 8.48484L15.7291 9.64286C15.7731 9.65086 15.8172 9.65384 15.8602 9.65384C16.2172 9.65384 16.533 9.39782 16.597 9.03482ZM17.2972 5.77286C17.3692 5.36486 17.097 4.97584 16.689 4.90384L10.1241 3.74582C9.72008 3.67382 9.32716 3.94685 9.25616 4.35385C9.18416 4.76185 9.45607 5.15087 9.86407 5.22287L16.429 6.38083C16.473 6.38883 16.5171 6.39188 16.5601 6.39188C16.9171 6.39288 17.2332 6.13686 17.2972 5.77286Z" fill="#F5F6FC" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -10,14 +10,14 @@ export enum CardType {
Secondary = 'Secondary',
}
const StyledCard = styled.div<{ isDarkMode: boolean; backgroundImgSrc?: string; type: CardType }>`
const StyledCard = styled.div<{ $isDarkMode: boolean; $backgroundImgSrc?: string; $type: CardType }>`
display: flex;
background: ${({ isDarkMode, backgroundImgSrc, type, theme }) =>
isDarkMode
? `${type === CardType.Primary ? theme.backgroundModule : theme.backgroundSurface} ${
backgroundImgSrc ? ` url(${backgroundImgSrc})` : ''
background: ${({ $isDarkMode, $backgroundImgSrc, $type, theme }) =>
$isDarkMode
? `${$type === CardType.Primary ? theme.backgroundModule : theme.backgroundSurface} ${
$backgroundImgSrc ? ` url(${$backgroundImgSrc})` : ''
}`
: `${type === CardType.Primary ? 'white' : theme.backgroundModule} url(${backgroundImgSrc})`};
: `${$type === CardType.Primary ? 'white' : theme.backgroundModule} url(${$backgroundImgSrc})`};
background-size: auto 100%;
background-position: right;
background-repeat: no-repeat;
@@ -30,15 +30,15 @@ const StyledCard = styled.div<{ isDarkMode: boolean; backgroundImgSrc?: string;
padding: 24px;
height: 212px;
border-radius: 24px;
border: 1px solid ${({ theme, type }) => (type === CardType.Primary ? 'transparent' : theme.backgroundOutline)};
border: 1px solid ${({ theme, $type }) => ($type === CardType.Primary ? 'transparent' : theme.backgroundOutline)};
box-shadow: 0px 10px 24px 0px rgba(51, 53, 72, 0.04);
transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} border`};
&:hover {
border: 1px solid ${({ theme, isDarkMode }) => (isDarkMode ? theme.backgroundInteractive : theme.textTertiary)};
border: 1px solid ${({ theme, $isDarkMode }) => ($isDarkMode ? theme.backgroundInteractive : theme.textTertiary)};
}
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
height: ${({ backgroundImgSrc }) => (backgroundImgSrc ? 360 : 260)}px;
height: ${({ $backgroundImgSrc }) => ($backgroundImgSrc ? 360 : 260)}px;
}
@media screen and (min-width: ${BREAKPOINTS.xl}px) {
padding: 32px;
@@ -125,14 +125,14 @@ const Card = ({
return (
<TraceEvent events={[BrowserEvent.onClick]} name={SharedEventName.ELEMENT_CLICKED} element={elementName}>
<StyledCard
type={type}
as={external ? 'a' : Link}
to={external ? undefined : to}
href={external ? to : undefined}
target={external ? '_blank' : undefined}
rel={external ? 'noopenener noreferrer' : undefined}
isDarkMode={isDarkMode}
backgroundImgSrc={backgroundImgSrc}
$backgroundImgSrc={backgroundImgSrc}
$isDarkMode={isDarkMode}
$type={type}
>
<TitleRow>
<CardTitle>{title}</CardTitle>

View File

@@ -10,14 +10,14 @@ import { AutoRow } from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading'
import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart'
import Tooltip from 'components/Tooltip'
import { useGetConnection } from 'connection'
import { getConnection } from 'connection'
import { usePortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks'
import { useAtomValue } from 'jotai/utils'
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
import { ProfilePageStateType } from 'nft/types'
import { useCallback, useState } from 'react'
import { ArrowDownRight, ArrowUpRight, Copy, CreditCard, IconProps, Info, Power, Settings } from 'react-feather'
import { ArrowDownRight, ArrowUpRight, Copy, CreditCard, IconProps, Info, LogOut, Settings } from 'react-feather'
import { useNavigate } from 'react-router-dom'
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
import { useAppDispatch } from 'state/hooks'
@@ -31,7 +31,7 @@ import { ApplicationModal } from '../../state/application/reducer'
import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks'
import StatusIcon from '../Identicon/StatusIcon'
import { useToggleAccountDrawer } from '.'
import IconButton, { IconHoverText } from './IconButton'
import IconButton, { IconHoverText, IconWithConfirmTextButton } from './IconButton'
import MiniPortfolio from './MiniPortfolio'
import { portfolioFadeInAnimation } from './MiniPortfolio/PortfolioRow'
@@ -103,7 +103,9 @@ const FiatOnrampAvailabilityExternalLink = styled(ExternalLink)`
const StatusWrapper = styled.div`
display: inline-block;
width: 70%;
padding-right: 4px;
max-width: 70%;
overflow: hidden;
padding-right: 14px;
display: inline-flex;
`
@@ -158,6 +160,10 @@ export function PortfolioArrow({ change, ...rest }: { change: number } & IconPro
)
}
const LogOutCentered = styled(LogOut)`
transform: translateX(2px);
`
export default function AuthenticatedHeader({ account, openSettings }: { account: string; openSettings: () => void }) {
const { connector, ENSName } = useWeb3React()
const dispatch = useAppDispatch()
@@ -172,7 +178,6 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(account)
const isUnclaimed = useUserHasAvailableClaim(account)
const getConnection = useGetConnection()
const connection = getConnection(connector)
const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
const openNftModal = useToggleModal(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
@@ -232,6 +237,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
const totalBalance = portfolio?.tokensTotalDenominatedValue?.value
const absoluteChange = portfolio?.tokensTotalDenominatedValueChange?.absolute?.value
const percentChange = portfolio?.tokensTotalDenominatedValueChange?.percentage?.value
const [showDisconnectConfirm, setShowDisconnectConfirm] = useState(false)
return (
<AuthenticatedHeaderWrapper>
@@ -253,20 +259,28 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
)}
</StatusWrapper>
<IconContainer>
<IconButton data-testid="wallet-settings" onClick={openSettings} Icon={Settings} />
{!showDisconnectConfirm && (
<IconButton data-testid="wallet-settings" onClick={openSettings} Icon={Settings} />
)}
<TraceEvent
events={[BrowserEvent.onClick]}
name={SharedEventName.ELEMENT_CLICKED}
element={InterfaceElementName.DISCONNECT_WALLET_BUTTON}
>
<IconButton data-testid="wallet-disconnect" onClick={disconnect} Icon={Power} />
<IconWithConfirmTextButton
data-testid="wallet-disconnect"
onConfirm={disconnect}
onShowConfirm={setShowDisconnectConfirm}
Icon={LogOutCentered}
text="Disconnect"
/>
</TraceEvent>
</IconContainer>
</HeaderWrapper>
<PortfolioDrawerContainer>
{totalBalance !== undefined ? (
<FadeInColumn gap="xs">
<ThemedText.HeadlineLarge fontWeight={500}>
<ThemedText.HeadlineLarge fontWeight={500} data-testid="portfolio-total-balance">
{formatNumber(totalBalance, NumberType.PortfolioBalance)}
</ThemedText.HeadlineLarge>
<AutoRow marginBottom="20px">

View File

@@ -1,5 +1,9 @@
import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react'
import { Icon } from 'react-feather'
import styled, { css } from 'styled-components/macro'
import useResizeObserver from 'use-resize-observer'
import Row from '../Row'
export const IconHoverText = styled.span`
color: ${({ theme }) => theme.textPrimary};
@@ -13,12 +17,17 @@ export const IconHoverText = styled.span`
left: 10px;
`
const widthTransition = `width ease-in 80ms`
const IconStyles = css`
background-color: ${({ theme }) => theme.backgroundInteractive};
transition: ${widthTransition};
border-radius: 12px;
display: inline-block;
display: flex;
padding: 0;
cursor: pointer;
position: relative;
overflow: hidden;
height: 32px;
width: 32px;
color: ${({ theme }) => theme.textPrimary};
@@ -28,7 +37,7 @@ const IconStyles = css`
theme: {
transition: { duration, timing },
},
}) => `${duration.fast} background-color ${timing.in}`};
}) => `${duration.fast} background-color ${timing.in}, ${widthTransition}`};
${IconHoverText} {
opacity: 1;
@@ -36,7 +45,7 @@ const IconStyles = css`
}
:active {
background-color: ${({ theme }) => theme.backgroundSurface};
transition: background-color 50ms linear;
transition: background-color 50ms linear, ${widthTransition};
}
`
@@ -51,27 +60,29 @@ const IconBlockButton = styled.button`
`
const IconWrapper = styled.span`
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 16px;
height: 16px;
margin: auto;
display: flex;
`
interface BaseProps {
Icon: Icon
children?: React.ReactNode
}
interface IconLinkProps extends React.ComponentPropsWithoutRef<'a'>, BaseProps {}
interface IconButtonProps extends React.ComponentPropsWithoutRef<'button'>, BaseProps {}
const IconBlock = (props: React.ComponentPropsWithoutRef<'a' | 'button'>) => {
type IconBlockProps = React.ComponentPropsWithoutRef<'a' | 'button'>
const IconBlock = forwardRef<HTMLAnchorElement | HTMLDivElement, IconBlockProps>(function IconBlock(props, ref) {
if ('href' in props) {
return <IconBlockLink {...props} />
return <IconBlockLink ref={ref as React.ForwardedRef<HTMLAnchorElement>} {...props} />
}
// ignoring 'button' 'type' conflict between React and styled-components
// @ts-ignore
return <IconBlockButton {...props} />
}
return <IconBlockButton ref={ref} {...props} />
})
const IconButton = ({ Icon, ...rest }: IconButtonProps | IconLinkProps) => (
<IconBlock {...rest}>
@@ -81,4 +92,119 @@ const IconButton = ({ Icon, ...rest }: IconButtonProps | IconLinkProps) => (
</IconBlock>
)
type IconWithTextProps = (IconButtonProps | IconLinkProps) & {
text: string
onConfirm?: () => void
onShowConfirm?: (on: boolean) => void
}
const TextWrapper = styled.div`
display: flex;
flex-shrink: 0;
overflow: hidden;
min-width: min-content;
`
const TextHide = styled.div`
overflow: hidden;
`
/**
* Allows for hiding and showing some text next to an IconButton
* Note that for width transitions to animate in CSS we need to always specify the width (no auto)
* so there's resize observing and measuring going on here.
*/
export const IconWithConfirmTextButton = ({
Icon,
text,
onConfirm,
onShowConfirm,
onClick,
...rest
}: IconWithTextProps) => {
const [showText, setShowTextWithoutCallback] = useState(false)
const frameObserver = useResizeObserver<HTMLElement>()
const hiddenObserver = useResizeObserver<HTMLElement>()
const setShowText = useCallback(
(val: boolean) => {
setShowTextWithoutCallback(val)
onShowConfirm?.(val)
},
[onShowConfirm]
)
const dimensionsRef = useRef({
frame: 0,
hidden: 0,
})
const dimensions = (() => {
// once opened, we avoid updating it to prevent constant resize loop
if (!showText) {
dimensionsRef.current = { frame: frameObserver.width || 0, hidden: hiddenObserver.width || 0 }
}
return dimensionsRef.current
})()
// keyboard action to cancel
useEffect(() => {
if (!showText) return
const isClient = typeof window !== 'undefined'
if (!isClient) return
if (!showText) return
const keyHandler = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
setShowText(false)
e.preventDefault()
e.stopPropagation()
}
}
window.addEventListener('keydown', keyHandler, { capture: true })
return () => {
window.removeEventListener('keydown', keyHandler, { capture: true })
}
}, [setShowText, showText])
const xPad = showText ? 12 : 0
const width = showText ? dimensions.frame + dimensions.hidden + xPad : 32
return (
<IconBlock
ref={frameObserver.ref}
{...rest}
style={{
width,
paddingLeft: xPad,
paddingRight: xPad,
}}
// @ts-ignore MouseEvent is valid, its a subset of the two mouse events,
// even manually typing this all out more specifically it still gets mad about any casting for some reason
onClick={(e: MouseEvent<HTMLAnchorElement>) => {
if (showText) {
onConfirm?.()
} else {
onClick?.(e)
setShowText(!showText)
}
}}
>
<Row height="100%" gap="xs">
<IconWrapper>
<Icon strokeWidth={1.5} size={16} />
</IconWrapper>
{/* this outer div is so we can cut it off but keep the inner text width full-width so we can measure it */}
<TextHide
style={{
maxWidth: showText ? dimensions.hidden : 0,
minWidth: showText ? dimensions.hidden : 0,
}}
>
<TextWrapper ref={hiddenObserver.ref}>{text}</TextWrapper>
</TextHide>
</Row>
</IconBlock>
)
}
export default IconButton

View File

@@ -82,15 +82,38 @@ const ActivityGroupWrapper = styled(Column)`
gap: 8px;
`
function combineActivities(localMap: ActivityMap = {}, remoteMap: ActivityMap = {}): Array<Activity> {
/* Detects transactions from same account with the same nonce and different hash */
function wasTxCancelled(localActivity: Activity, remoteMap: ActivityMap, account: string): boolean {
// handles locally cached tx's that were stored before we started tracking nonces
if (!localActivity.nonce || localActivity.status !== TransactionStatus.Pending) return false
return Object.values(remoteMap).some((remoteTx) => {
if (!remoteTx) return false
// Cancellations are only possible when both nonce and tx.from are the same
if (remoteTx.nonce === localActivity.nonce && remoteTx.receipt?.from.toLowerCase() === account.toLowerCase()) {
// If the remote tx has a different hash than the local tx, the local tx was cancelled
return remoteTx.hash.toLowerCase() !== localActivity.hash.toLowerCase()
}
return false
})
}
function combineActivities(localMap: ActivityMap = {}, remoteMap: ActivityMap = {}, account: string): Array<Activity> {
const txHashes = [...new Set([...Object.keys(localMap), ...Object.keys(remoteMap)])]
// Merges local and remote activities w/ same hash, preferring remote data
return txHashes.reduce((acc: Array<Activity>, hash) => {
const localActivity = localMap?.[hash] ?? {}
const remoteActivity = remoteMap?.[hash] ?? {}
// TODO(cartcrom): determine best logic for which fields to prefer from which sources, i.e. prefer remote exact swap output instead of local estimated output
acc.push({ ...remoteActivity, ...localActivity } as Activity)
const localActivity = (localMap?.[hash] ?? {}) as Activity
const remoteActivity = (remoteMap?.[hash] ?? {}) as Activity
// TODO(WEB-2064): Display cancelled status in UI rather than completely hiding cancelled TXs
if (wasTxCancelled(localActivity, remoteMap, account)) return acc
// TODO(cartcrom): determine best logic for which fields to prefer from which sources
// i.e.prefer remote exact swap output instead of local estimated output
acc.push({ ...localActivity, ...remoteActivity } as Activity)
return acc
}, [])
}
@@ -122,9 +145,9 @@ export function ActivityTab({ account }: { account: string }) {
const activityGroups = useMemo(() => {
const remoteMap = parseRemoteActivities(data?.portfolios?.[0].assetActivities)
const allActivities = combineActivities(localMap, remoteMap)
const allActivities = combineActivities(localMap, remoteMap, account)
return createGroups(allActivities)
}, [data?.portfolios, localMap])
}, [data?.portfolios, localMap, account])
if (!data && loading)
return (

View File

@@ -2,7 +2,7 @@ import { SupportedChainId, Token, TradeType as MockTradeType } from '@uniswap/sd
import { PERMIT2_ADDRESS } from '@uniswap/universal-router-sdk'
import { DAI as MockDAI, nativeOnChain, USDC_MAINNET as MockUSDC_MAINNET } from 'constants/tokens'
import { TransactionStatus as MockTxStatus } from 'graphql/data/__generated__/types-and-hooks'
import { TokenAddressMap } from 'state/lists/hooks'
import { ChainTokenMap } from 'hooks/Tokens'
import {
ExactInputSwapTransactionInfo,
ExactOutputSwapTransactionInfo,
@@ -89,15 +89,15 @@ function mockMultiStatus(info: TransactionInfo, id: string): [TransactionDetails
]
}
const mockTokenAddressMap: TokenAddressMap = {
const mockTokenAddressMap: ChainTokenMap = {
[mockChainId]: {
[MockDAI.address]: { token: MockDAI },
[MockUSDC_MAINNET.address]: { token: MockUSDC_MAINNET },
} as TokenAddressMap[number],
[MockDAI.address]: MockDAI,
[MockUSDC_MAINNET.address]: MockUSDC_MAINNET,
},
}
jest.mock('../../../../state/lists/hooks', () => ({
useCombinedActiveList: () => mockTokenAddressMap,
jest.mock('../../../../hooks/Tokens', () => ({
useAllTokensMultichain: () => mockTokenAddressMap,
}))
jest.mock('../../../../state/transactions/hooks', () => {
@@ -300,7 +300,7 @@ describe('parseLocalActivity', () => {
},
} as TransactionDetails
const chainId = SupportedChainId.MAINNET
const tokens = {} as TokenAddressMap
const tokens = {} as ChainTokenMap
expect(parseLocalActivity(details, chainId, tokens)).toMatchObject({
chainId: 1,
currencies: [undefined, undefined],

View File

@@ -3,9 +3,9 @@ import { formatCurrencyAmount } from '@uniswap/conedison/format'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { nativeOnChain } from '@uniswap/smart-order-router'
import { SupportedChainId } from 'constants/chains'
import { TransactionPartsFragment, TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
import { ChainTokenMap, useAllTokensMultichain } from 'hooks/Tokens'
import { useMemo } from 'react'
import { TokenAddressMap, useCombinedActiveList } from 'state/lists/hooks'
import { useMultichainTransactions } from 'state/transactions/hooks'
import {
AddLiquidityV2PoolTransactionInfo,
@@ -25,8 +25,8 @@ import {
import { getActivityTitle } from '../constants'
import { Activity, ActivityMap } from './types'
function getCurrency(currencyId: string, chainId: SupportedChainId, tokens: TokenAddressMap): Currency | undefined {
return currencyId === 'ETH' ? nativeOnChain(chainId) : tokens[chainId]?.[currencyId]?.token
function getCurrency(currencyId: string, chainId: SupportedChainId, tokens: ChainTokenMap): Currency | undefined {
return currencyId === 'ETH' ? nativeOnChain(chainId) : tokens[chainId]?.[currencyId]
}
function buildCurrencyDescriptor(
@@ -46,7 +46,7 @@ function buildCurrencyDescriptor(
function parseSwap(
swap: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo,
chainId: SupportedChainId,
tokens: TokenAddressMap
tokens: ChainTokenMap
): Partial<Activity> {
const tokenIn = getCurrency(swap.inputCurrencyId, chainId, tokens)
const tokenOut = getCurrency(swap.outputCurrencyId, chainId, tokens)
@@ -76,7 +76,7 @@ function parseWrap(wrap: WrapTransactionInfo, chainId: SupportedChainId, status:
function parseApproval(
approval: ApproveTransactionInfo,
chainId: SupportedChainId,
tokens: TokenAddressMap
tokens: ChainTokenMap
): Partial<Activity> {
// TODO: Add 'amount' approved to ApproveTransactionInfo so we can distinguish between revoke and approve
const currency = getCurrency(approval.tokenAddress, chainId, tokens)
@@ -91,7 +91,7 @@ type GenericLPInfo = Omit<
AddLiquidityV3PoolTransactionInfo | RemoveLiquidityV3TransactionInfo | AddLiquidityV2PoolTransactionInfo,
'type'
>
function parseLP(lp: GenericLPInfo, chainId: SupportedChainId, tokens: TokenAddressMap): Partial<Activity> {
function parseLP(lp: GenericLPInfo, chainId: SupportedChainId, tokens: ChainTokenMap): Partial<Activity> {
const baseCurrency = getCurrency(lp.baseCurrencyId, chainId, tokens)
const quoteCurrency = getCurrency(lp.quoteCurrencyId, chainId, tokens)
const [baseRaw, quoteRaw] = [lp.expectedAmountBaseRaw, lp.expectedAmountQuoteRaw]
@@ -103,7 +103,7 @@ function parseLP(lp: GenericLPInfo, chainId: SupportedChainId, tokens: TokenAddr
function parseCollectFees(
collect: CollectFeesTransactionInfo,
chainId: SupportedChainId,
tokens: TokenAddressMap
tokens: ChainTokenMap
): Partial<Activity> {
// Adapts CollectFeesTransactionInfo to generic LP type
const {
@@ -118,7 +118,7 @@ function parseCollectFees(
function parseMigrateCreateV3(
lp: MigrateV2LiquidityToV3TransactionInfo | CreateV3PoolTransactionInfo,
chainId: SupportedChainId,
tokens: TokenAddressMap
tokens: ChainTokenMap
): Partial<Activity> {
const baseCurrency = getCurrency(lp.baseCurrencyId, chainId, tokens)
const baseSymbol = baseCurrency?.symbol ?? t`Unknown`
@@ -132,7 +132,7 @@ function parseMigrateCreateV3(
export function parseLocalActivity(
details: TransactionDetails,
chainId: SupportedChainId,
tokens: TokenAddressMap
tokens: ChainTokenMap
): Activity | undefined {
try {
const status = !details.receipt
@@ -141,7 +141,7 @@ export function parseLocalActivity(
? TransactionStatus.Confirmed
: TransactionStatus.Failed
const receipt: TransactionPartsFragment | undefined = details.receipt
const receipt = details.receipt
? {
id: details.receipt.transactionHash,
...details.receipt,
@@ -157,6 +157,7 @@ export function parseLocalActivity(
status,
timestamp: (details.confirmedTime ?? details.addedTime) / 1000,
receipt,
nonce: details.nonce,
}
let additionalFields: Partial<Activity> = {}
@@ -188,7 +189,7 @@ export function parseLocalActivity(
export function useLocalActivities(account: string): ActivityMap {
const allTransactions = useMultichainTransactions()
const tokens = useCombinedActiveList()
const tokens = useAllTokensMultichain()
return useMemo(() => {
const activityByHash: ActivityMap = {}

View File

@@ -1,11 +1,13 @@
import { t } from '@lingui/macro'
import { formatNumberOrString, NumberType } from '@uniswap/conedison/format'
import { formatFiatPrice, formatNumberOrString, NumberType } from '@uniswap/conedison/format'
import { SupportedChainId } from '@uniswap/sdk-core'
import moonpayLogoSrc from 'assets/svg/moonpay.svg'
import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, UNI_ADDRESS } from 'constants/addresses'
import { nativeOnChain } from 'constants/tokens'
import {
ActivityType,
AssetActivityPartsFragment,
Currency,
NftApprovalPartsFragment,
NftApproveForAllPartsFragment,
NftTransferPartsFragment,
@@ -17,6 +19,7 @@ import ms from 'ms.macro'
import { useEffect, useState } from 'react'
import { isAddress } from 'utils'
import { MOONPAY_SENDER_ADDRESSES } from '../constants'
import { Activity } from './types'
type TransactionChanges = {
@@ -106,6 +109,17 @@ function getSwapTitle(sent: TokenTransferPartsFragment, received: TokenTransferP
}
}
/**
*
* @param transactedValue Transacted value amount from TokenTransfer API response
* @returns parsed & formatted USD value as a string if currency is of type USD
*/
function formatTransactedValue(transactedValue: TokenTransferPartsFragment['transactedValue']): string {
if (!transactedValue) return '-'
const price = transactedValue?.currency === Currency.Usd ? transactedValue.value ?? undefined : undefined
return formatFiatPrice(price)
}
function parseSwap(changes: TransactionChanges) {
if (changes.NftTransfer.length > 0 && changes.TokenTransfer.length === 1) {
const collectionCounts = getCollectionCounts(changes.NftTransfer)
@@ -175,17 +189,27 @@ function parseSendReceive(changes: TransactionChanges, assetActivity: AssetActiv
}
if (transfer && assetName && amount) {
return transfer.direction === 'IN'
? {
title: t`Received`,
descriptor: `${amount} ${assetName} ${t`from`} `,
otherAccount: isAddress(transfer.sender) || undefined,
}
: {
title: t`Sent`,
descriptor: `${amount} ${assetName} ${t`to`} `,
otherAccount: isAddress(transfer.recipient) || undefined,
}
const isMoonpayPurchase = MOONPAY_SENDER_ADDRESSES.some((address) => isSameAddress(address, transfer?.sender))
if (transfer.direction === 'IN') {
return isMoonpayPurchase && transfer.__typename === 'TokenTransfer'
? {
title: t`Purchased`,
descriptor: `${amount} ${assetName} ${t`for`} ${formatTransactedValue(transfer.transactedValue)}`,
logos: [moonpayLogoSrc],
}
: {
title: t`Received`,
descriptor: `${amount} ${assetName} ${t`from`} `,
otherAccount: isAddress(transfer.sender) || undefined,
}
} else {
return {
title: t`Sent`,
descriptor: `${amount} ${assetName} ${t`to`} `,
otherAccount: isAddress(transfer.recipient) || undefined,
}
}
}
return { title: t`Unknown Send` }
}
@@ -254,6 +278,7 @@ function parseRemoteActivity(assetActivity: AssetActivityPartsFragment): Activit
title: assetActivity.type,
descriptor: assetActivity.transaction.to,
receipt: assetActivity.transaction,
nonce: assetActivity.transaction.nonce,
}
const parsedFields = ActivityParserByType[assetActivity.type]?.(changes, assetActivity)

View File

@@ -14,7 +14,8 @@ export type Activity = {
logos?: Array<string | undefined>
currencies?: Array<Currency | undefined>
otherAccount?: string
receipt?: Receipt
receipt?: Omit<Receipt, 'nonce'>
nonce?: number | null
}
export type ActivityMap = { [hash: string]: Activity | undefined }

View File

@@ -30,6 +30,7 @@ const Wrapper = styled(Column)<{ numItems: number; isExpanded: boolean }>`
overflow: hidden;
`
// TODO(WEB-3288): Replace this component to use `components/Expand` under the hood
type ExpandoRowProps = PropsWithChildren<{ title?: string; numItems: number; isExpanded: boolean; toggle: () => void }>
export function ExpandoRow({ title = t`Hidden`, numItems, isExpanded, toggle, children }: ExpandoRowProps) {
if (numItems === 0) return null

View File

@@ -126,7 +126,7 @@ export function useGetCachedTokens(chains: SupportedChainId[]): TokenGetterFn {
const local: { [address: string]: Token | undefined } = {}
const missing = new Set<string>()
addresses.forEach((address) => {
const cached = tokenCache.get(chainId, address) ?? allTokens[chainId][address]?.token
const cached = tokenCache.get(chainId, address) ?? allTokens[chainId]?.[address]
cached ? (local[address] = cached) : missing.add(address)
})

View File

@@ -1,7 +1,7 @@
import { Token } from '@uniswap/sdk-core'
import { AddressMap } from '@uniswap/smart-order-router'
import { abi as MulticallABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json'
import { abi as NFTPositionManagerABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import MulticallJSON from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json'
import NFTPositionManagerJSON from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import { useWeb3React } from '@web3-react/core'
import { MULTICALL_ADDRESS, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES as V3NFT_ADDRESSES } from 'constants/addresses'
import { isSupportedChain, SupportedChainId } from 'constants/chains'
@@ -43,11 +43,11 @@ function useContractMultichain<T extends BaseContract>(
}
export function useV3ManagerContracts(chainIds: SupportedChainId[]): ContractMap<NonfungiblePositionManager> {
return useContractMultichain<NonfungiblePositionManager>(V3NFT_ADDRESSES, NFTPositionManagerABI, chainIds)
return useContractMultichain<NonfungiblePositionManager>(V3NFT_ADDRESSES, NFTPositionManagerJSON.abi, chainIds)
}
export function useInterfaceMulticallContracts(chainIds: SupportedChainId[]): ContractMap<UniswapInterfaceMulticall> {
return useContractMultichain<UniswapInterfaceMulticall>(MULTICALL_ADDRESS, MulticallABI, chainIds)
return useContractMultichain<UniswapInterfaceMulticall>(MULTICALL_ADDRESS, MulticallJSON.abi, chainIds)
}
type PriceMap = { [key: CurrencyKey]: number | undefined }

View File

@@ -1,5 +1,5 @@
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { abi as IUniswapV3PoolStateABI } from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json'
import IUniswapV3PoolStateJSON from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json'
import { computePoolAddress, Pool, Position } from '@uniswap/v3-sdk'
import { V3_CORE_FACTORY_ADDRESSES } from 'constants/addresses'
import { SupportedChainId } from 'constants/chains'
@@ -49,7 +49,7 @@ const DEFAULT_CHAINS = [
SupportedChainId.CELO,
]
type UseMultiChainPositionsData = { positions: PositionInfo[] | undefined; loading: boolean }
type UseMultiChainPositionsData = { positions?: PositionInfo[]; loading: boolean }
/**
* Returns all positions for a given account on multiple chains.
@@ -118,7 +118,7 @@ export default function useMultiChainPositions(account: string, chains = DEFAULT
// Combines PositionDetails with Pool data to build our return type
const fetchPositionInfo = useCallback(
async (positionDetails: PositionDetails[], chainId: SupportedChainId, multicall: UniswapInterfaceMulticall) => {
const poolInterface = new Interface(IUniswapV3PoolStateABI) as UniswapV3PoolInterface
const poolInterface = new Interface(IUniswapV3PoolStateJSON.abi) as UniswapV3PoolInterface
const tokens = await getTokens(
positionDetails.flatMap((details) => [details.token0, details.token1]),
chainId

View File

@@ -1,6 +1,6 @@
import { SupportedChainId } from '@uniswap/sdk-core'
import { DAI_ARBITRUM } from '@uniswap/smart-order-router'
import { DAI, USDC_ARBITRUM, USDC_MAINNET } from 'constants/tokens'
import { BRIDGED_USDC_ARBITRUM, DAI, USDC_MAINNET } from 'constants/tokens'
import { render } from 'test-utils/render'
import { PortfolioLogo } from './PortfolioLogo'
@@ -13,7 +13,7 @@ describe('PortfolioLogo', () => {
it('renders with L2 icon', () => {
const { container } = render(
<PortfolioLogo chainId={SupportedChainId.ARBITRUM_ONE} currencies={[DAI_ARBITRUM, USDC_ARBITRUM]} />
<PortfolioLogo chainId={SupportedChainId.ARBITRUM_ONE} currencies={[DAI_ARBITRUM, BRIDGED_USDC_ARBITRUM]} />
)
expect(container).toMatchSnapshot()
})

View File

@@ -22,12 +22,14 @@ const EndColumn = styled(Column)`
`
export default function PortfolioRow({
['data-testid']: testId,
left,
title,
descriptor,
right,
onClick,
}: {
'data-testid'?: string
left: React.ReactNode
title: React.ReactNode
descriptor?: React.ReactNode
@@ -36,7 +38,7 @@ export default function PortfolioRow({
onClick?: () => void
}) {
return (
<PortfolioRowWrapper onClick={onClick}>
<PortfolioRowWrapper data-testid={testId} onClick={onClick}>
{left}
<AutoColumn grow>
{title}

View File

@@ -80,11 +80,11 @@ exports[`PortfolioLogo renders with L2 icon 1`] = `
>
<img
class="c2 c3"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/arbitrum/assets/0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1/logo.png"
src="blank_token.svg"
/>
<img
class="c2 c3"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/arbitrum/assets/0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8/logo.png"
src="blank_token.svg"
/>
</div>
<div
@@ -152,11 +152,11 @@ exports[`PortfolioLogo renders without L2 icon 1`] = `
>
<img
class="c2 c3"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png"
src="blank_token.svg"
/>
<img
class="c2 c3"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
src="blank_token.svg"
/>
</div>
</div>

View File

@@ -153,3 +153,11 @@ export function getActivityTitle(type: TransactionType, status: TransactionStatu
}
return TransactionTitleTable[type][status]
}
// Non-exhaustive list of addresses Moonpay uses when sending purchased tokens
export const MOONPAY_SENDER_ADDRESSES = [
'0x8216874887415e2650d12d53ff53516f04a74fd7',
'0x151b381058f91cf871e7ea1ee83c45326f61e96d',
'0xb287eac48ab21c5fb1d3723830d60b4c797555b0',
'0xd108fd0e8c8e71552a167e7a44ff1d345d233ba6',
]

View File

@@ -95,7 +95,7 @@ export default function MiniPortfolio({ account }: { account: string }) {
return (
<Trace section={InterfaceSectionName.MINI_PORTFOLIO}>
<Wrapper>
<Nav>
<Nav data-testid="mini-portfolio-navbar">
{Pages.map(({ title, loggingElementName, key }, index) => {
if (shouldDisableNFTRoutes && loggingElementName.includes('nft')) return null
return (
@@ -105,19 +105,14 @@ export default function MiniPortfolio({ account }: { account: string }) {
element={loggingElementName}
key={index}
>
<NavItem
data-testid={`mini-portfolio-nav-${key}`}
onClick={() => setCurrentPage(index)}
active={currentPage === index}
key={`Mini Portfolio page ${index}`}
>
<NavItem onClick={() => setCurrentPage(index)} active={currentPage === index} key={key}>
{title}
</NavItem>
</TraceEvent>
)
})}
</Nav>
<PageWrapper>
<PageWrapper data-testid="mini-portfolio-page">
<Page account={account} />
</PageWrapper>
</Wrapper>

View File

@@ -13,7 +13,7 @@ function wasPending(previousTxs: { [hash: string]: TransactionDetails | undefine
return previousTx && isTxPending(previousTx)
}
function useHasUpdatedTx() {
function useHasUpdatedTx(account: string | undefined) {
// TODO: consider monitoring tx's on chains other than the wallet's current chain
const currentChainTxs = useAllTransactions()
@@ -27,12 +27,12 @@ function useHasUpdatedTx() {
const previousPendingTxs = usePrevious(pendingTxs)
return useMemo(() => {
if (!previousPendingTxs) return false
if (!previousPendingTxs || !account) return false
return Object.values(currentChainTxs).some(
(tx) => !isTxPending(tx) && wasPending(previousPendingTxs, tx),
(tx) => tx.from === account && !isTxPending(tx) && wasPending(previousPendingTxs, tx),
[currentChainTxs, previousPendingTxs]
)
}, [currentChainTxs, previousPendingTxs])
}, [account, currentChainTxs, previousPendingTxs])
}
/* Prefetches & caches portfolio balances when the wrapped component is hovered or the user completes a transaction */
@@ -49,16 +49,22 @@ export default function PrefetchBalancesWrapper({ children }: PropsWithChildren)
}
}, [account, prefetchPortfolioBalances])
// TODO(cartcrom): add delay for refetching on optimism, as there is high latency in new balances being available
const hasUpdatedTx = useHasUpdatedTx()
// Listens for recently updated transactions to keep portfolio balances fresh in apollo cache
useEffect(() => {
if (!hasUpdatedTx) return
const prevAccount = usePrevious(account)
// If the drawer is open, fetch balances immediately, else set a flag to fetch on next hover
if (drawerOpen) fetchBalances()
else setHasUnfetchedBalances(true)
}, [drawerOpen, fetchBalances, hasUpdatedTx])
// TODO(cartcrom): add delay for refetching on optimism, as there is high latency in new balances being available
const hasUpdatedTx = useHasUpdatedTx(account)
// Listens for account changes & recently updated transactions to keep portfolio balances fresh in apollo cache
useEffect(() => {
const accountChanged = prevAccount !== undefined && prevAccount !== account
if (hasUpdatedTx || accountChanged) {
// If the drawer is open, fetch balances immediately, else set a flag to fetch on next hover
if (drawerOpen) {
fetchBalances()
} else {
setHasUnfetchedBalances(true)
}
}
}, [account, prevAccount, drawerOpen, fetchBalances, hasUpdatedTx])
const onHover = useCallback(() => {
if (hasUnfetchedBalances) fetchBalances()

View File

@@ -44,7 +44,7 @@ export default function UniwalletModal() {
// Displays the modal if a Uniswap Wallet Connection is pending & qrcode URI is available
const open =
activationState.status === ActivationStatus.PENDING &&
activationState.connection.type === ConnectionType.UNIWALLET &&
activationState.connection.type === ConnectionType.UNISWAP_WALLET &&
!!uri
useEffect(() => {

View File

@@ -205,7 +205,7 @@ function AccountDrawer() {
name={InterfaceEventName.MINI_PORTFOLIO_TOGGLED}
properties={{ type: 'close' }}
>
<CloseDrawer onClick={toggleWalletDrawer}>
<CloseDrawer onClick={toggleWalletDrawer} data-testid="close-account-drawer">
<CloseIcon />
</CloseDrawer>
</TraceEvent>

View File

@@ -0,0 +1,31 @@
import { render, screen, waitFor } from 'test-utils/render'
import AnimatedDropdown from './index'
describe('AnimatedDropdown', () => {
it('does not render children when closed', () => {
render(<AnimatedDropdown open={false}>Body</AnimatedDropdown>)
expect(screen.getByText('Body')).not.toBeVisible()
})
it('renders children when open', () => {
render(<AnimatedDropdown open={true}>Body</AnimatedDropdown>)
expect(screen.getByText('Body')).toBeVisible()
})
it('animates when open changes', async () => {
const { rerender } = render(<AnimatedDropdown open={false}>Body</AnimatedDropdown>)
const body = screen.getByText('Body')
expect(body).not.toBeVisible()
rerender(<AnimatedDropdown open={true}>Body</AnimatedDropdown>)
expect(body).not.toBeVisible()
// wait for React Spring animation to finish
await waitFor(() => {
expect(body).toBeVisible()
})
})
})

View File

@@ -9,7 +9,10 @@ export default function AnimatedDropdown({ open, children }: React.PropsWithChil
const { ref, height } = useResizeObserver()
const props = useSpring({
height: open ? height ?? 0 : 0,
// On initial render, `height` will be undefined as ref has not been set yet.
// If the dropdown should be open, we fallback to `auto` to avoid flickering.
// Otherwise, we just animate between actual height (when open) and 0 (when closed).
height: open ? height ?? 'auto' : 0,
config: {
mass: 1.2,
tension: 300,
@@ -20,14 +23,7 @@ export default function AnimatedDropdown({ open, children }: React.PropsWithChil
})
return (
<animated.div
style={{
...props,
overflow: 'hidden',
width: '100%',
willChange: 'height',
}}
>
<animated.div style={{ ...props, overflow: 'hidden', width: '100%', willChange: 'height' }}>
<div ref={ref}>{children}</div>
</animated.div>
)

View File

@@ -32,13 +32,7 @@ const LabelText = styled.div<{ color: string }>`
justify-content: flex-end;
`
export default function RangeBadge({
removed,
inRange,
}: {
removed: boolean | undefined
inRange: boolean | undefined
}) {
export default function RangeBadge({ removed, inRange }: { removed?: boolean; inRange?: boolean }) {
const theme = useTheme()
return (
<BadgeWrapper>

View File

@@ -14,7 +14,7 @@ import { useHideUniswapWalletBanner } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { Z_INDEX } from 'theme/zIndex'
import { isIOS } from 'utils/userAgent'
import { isIOS, isMobileSafari } from 'utils/userAgent'
const PopupContainer = styled.div<{ show: boolean }>`
display: flex;
@@ -93,6 +93,8 @@ export default function UniswapWalletBanner() {
const screenSize = useScreenSize()
if (isMobileSafari) return null
return (
<PopupContainer show={shouldDisplay}>
<StyledXButton

View File

@@ -1,4 +1,5 @@
import { darken } from 'polished'
import { forwardRef } from 'react'
import { Check, ChevronDown } from 'react-feather'
import { Button as RebassButton, ButtonProps as ButtonPropsOriginal } from 'rebass/styled-components'
import styled, { DefaultTheme, useTheme } from 'styled-components/macro'
@@ -296,7 +297,7 @@ export function ButtonConfirmed({
}
}
export function ButtonError({ error, ...rest }: { error?: boolean } & ButtonProps) {
export function ButtonError({ error, ...rest }: { error?: boolean } & BaseButtonProps) {
if (error) {
return <ButtonErrorStyle {...rest} />
} else {
@@ -391,6 +392,7 @@ export enum ButtonEmphasis {
low,
warning,
destructive,
failure,
}
interface BaseThemeButtonProps {
size: ButtonSize
@@ -411,6 +413,8 @@ function pickThemeButtonBackgroundColor({ theme, emphasis }: { theme: DefaultThe
return theme.accentWarningSoft
case ButtonEmphasis.destructive:
return theme.accentCritical
case ButtonEmphasis.failure:
return theme.accentFailureSoft
case ButtonEmphasis.medium:
default:
return theme.backgroundInteractive
@@ -465,6 +469,8 @@ function pickThemeButtonTextColor({ theme, emphasis }: { theme: DefaultTheme; em
return theme.accentWarning
case ButtonEmphasis.destructive:
return theme.accentTextDarkPrimary
case ButtonEmphasis.failure:
return theme.accentFailure
case ButtonEmphasis.medium:
default:
return theme.textPrimary
@@ -519,15 +525,19 @@ const BaseThemeButton = styled.button<BaseThemeButtonProps>`
`
interface ThemeButtonProps extends React.ComponentPropsWithoutRef<'button'>, BaseThemeButtonProps {}
type ThemeButtonRef = HTMLButtonElement
export const ThemeButton = ({ children, ...rest }: ThemeButtonProps) => {
export const ThemeButton = forwardRef<ThemeButtonRef, ThemeButtonProps>(function ThemeButton(
{ children, ...rest },
ref
) {
return (
<BaseThemeButton {...rest}>
<BaseThemeButton {...rest} ref={ref}>
<ButtonOverlay />
{children}
</BaseThemeButton>
)
}
})
export const ButtonLight = ({ children, ...rest }: BaseButtonProps) => {
return (

View File

@@ -19,7 +19,7 @@ interface SparklineChartProps {
width: number
height: number
tokenData: TopToken
pricePercentChange: number | undefined | null
pricePercentChange?: number | null
sparklineMap: SparklineMap
}

View File

@@ -1,6 +1,5 @@
import styled, { DefaultTheme } from 'styled-components/macro'
type Gap = keyof DefaultTheme['grids']
import styled from 'styled-components/macro'
import { Gap } from 'theme'
export const Column = styled.div<{
gap?: Gap

View File

@@ -14,7 +14,7 @@ const ContentWrapper = styled(Column)`
font-size: 12px;
`
interface ConnectedAccountBlockedProps {
account: string | null | undefined
account?: string | null
isOpen: boolean
}

View File

@@ -1,15 +1,13 @@
import { Trans } from '@lingui/macro'
// eslint-disable-next-line no-restricted-imports
import { t } from '@lingui/macro'
import { formatNumber, formatPriceImpact, NumberType } from '@uniswap/conedison/format'
import { Percent } from '@uniswap/sdk-core'
import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading'
import { MouseoverTooltip } from 'components/Tooltip'
import { useMemo } from 'react'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from '../../theme'
import { warningSeverity } from '../../utils/prices'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { warningSeverity } from 'utils/prices'
const FiatLoadingBubble = styled(LoadingBubble)`
border-radius: 4px;
@@ -21,36 +19,40 @@ export function FiatValue({
fiatValue,
priceImpact,
}: {
fiatValue?: { data?: number; isLoading: boolean }
fiatValue: { data?: number; isLoading: boolean }
priceImpact?: Percent
}) {
const theme = useTheme()
const priceImpactColor = useMemo(() => {
if (!priceImpact) return undefined
if (priceImpact.lessThan('0')) return theme.accentSuccess
if (priceImpact.lessThan('0')) return 'accentSuccess'
const severity = warningSeverity(priceImpact)
if (severity < 1) return theme.textTertiary
if (severity < 3) return theme.deprecated_yellow1
return theme.accentFailure
}, [priceImpact, theme.accentSuccess, theme.accentFailure, theme.textTertiary, theme.deprecated_yellow1])
if (severity < 1) return 'textTertiary'
if (severity < 3) return 'deprecated_yellow1'
return 'accentFailure'
}, [priceImpact])
if (fiatValue.isLoading) {
return <FiatLoadingBubble />
}
return (
<ThemedText.DeprecatedBody fontSize={14} color={theme.textSecondary}>
{fiatValue?.isLoading ? (
<FiatLoadingBubble />
) : (
<div>
{fiatValue?.data ? formatNumber(fiatValue.data, NumberType.FiatTokenPrice) : undefined}
{priceImpact && (
<span style={{ color: priceImpactColor }}>
{' '}
<MouseoverTooltip text={t`The estimated difference between the USD values of input and output amounts.`}>
(<Trans>{formatPriceImpact(priceImpact)}</Trans>)
</MouseoverTooltip>
</span>
)}
</div>
<Row gap="sm">
<ThemedText.BodySmall>
{fiatValue.data ? (
formatNumber(fiatValue.data, NumberType.FiatTokenPrice)
) : (
<MouseoverTooltip text={<Trans>Not enough liquidity to show accurate USD value.</Trans>}>-</MouseoverTooltip>
)}
</ThemedText.BodySmall>
{priceImpact && (
<ThemedText.BodySmall color={priceImpactColor}>
<MouseoverTooltip
text={<Trans>The estimated difference between the USD values of input and output amounts.</Trans>}
>
(<Trans>{formatPriceImpact(priceImpact)}</Trans>)
</MouseoverTooltip>
</ThemedText.BodySmall>
)}
</ThemedText.DeprecatedBody>
</Row>
)
}

View File

@@ -195,7 +195,7 @@ interface SwapCurrencyInputPanelProps {
pair?: Pair | null
hideInput?: boolean
otherCurrency?: Currency | null
fiatValue: { data?: number; isLoading: boolean }
fiatValue?: { data?: number; isLoading: boolean }
priceImpact?: Percent
id: string
showCommonBases?: boolean
@@ -308,7 +308,7 @@ export default function SwapCurrencyInputPanel({
<FiatRow>
<RowBetween>
<LoadingOpacityContainer $loading={loading}>
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
{fiatValue && <FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />}
</LoadingOpacityContainer>
{account ? (
<RowFixed style={{ height: '17px' }}>

View File

@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { useWeb3React } from '@web3-react/core'
import { AutoColumn } from 'components/Column'
@@ -183,7 +183,6 @@ interface CurrencyInputPanelProps {
hideInput?: boolean
otherCurrency?: Currency | null
fiatValue?: { data?: number; isLoading: boolean }
priceImpact?: Percent
id: string
showCommonBases?: boolean
showCurrencyAmount?: boolean
@@ -207,7 +206,6 @@ export default function CurrencyInputPanel({
disableNonToken,
renderBalance,
fiatValue,
priceImpact,
hideBalance = false,
pair = null, // used for double token logo
hideInput = false,
@@ -293,7 +291,7 @@ export default function CurrencyInputPanel({
<FiatRow>
<RowBetween>
<LoadingOpacityContainer $loading={loading}>
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
{fiatValue && <FiatValue fiatValue={fiatValue} />}
</LoadingOpacityContainer>
{account ? (
<RowFixed style={{ height: '17px' }}>

View File

@@ -0,0 +1,38 @@
import { fireEvent, render, screen } from 'test-utils/render'
import noop from 'utils/noop'
import Expand from './index'
describe('Expand', () => {
it('does not render children when closed', () => {
render(
<Expand header={<span>Header</span>} isOpen={false} onToggle={noop} button={<span>Button</span>}>
Body
</Expand>
)
expect(screen.queryByText('Body')).not.toBeVisible()
})
it('renders children when open', () => {
render(
<Expand header={<span>Header</span>} isOpen={true} onToggle={noop} button={<span>Button</span>}>
Body
</Expand>
)
expect(screen.queryByText('Body')).toBeVisible()
})
it('calls `onToggle` when button is pressed', () => {
const onToggle = jest.fn()
render(
<Expand header={<span>Header</span>} isOpen={false} onToggle={onToggle} button={<span>Button</span>}>
Body
</Expand>
)
const button = screen.getByText('Button')
fireEvent.click(button)
expect(onToggle).toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,53 @@
import AnimatedDropdown from 'components/AnimatedDropdown'
import Column from 'components/Column'
import React, { PropsWithChildren, ReactElement } from 'react'
import { ChevronDown } from 'react-feather'
import styled from 'styled-components/macro'
import Row, { RowBetween } from '../Row'
const ButtonContainer = styled(Row)`
cursor: pointer;
justify-content: flex-end;
width: unset;
`
const ExpandIcon = styled(ChevronDown)<{ $isOpen: boolean }>`
color: ${({ theme }) => theme.textSecondary};
transform: ${({ $isOpen }) => ($isOpen ? 'rotate(180deg)' : 'rotate(0deg)')};
transition: transform ${({ theme }) => theme.transition.duration.medium};
`
const Content = styled(Column)`
padding-top: ${({ theme }) => theme.grids.md};
`
export default function Expand({
header,
button,
children,
testId,
isOpen,
onToggle,
}: PropsWithChildren<{
header: ReactElement
button: ReactElement
testId?: string
isOpen: boolean
onToggle: () => void
}>) {
return (
<Column>
<RowBetween>
{header}
<ButtonContainer data-testid={testId} onClick={onToggle} aria-expanded={isOpen}>
{button}
<ExpandIcon $isOpen={isOpen} />
</ButtonContainer>
</RowBetween>
<AnimatedDropdown open={isOpen}>
<Content gap="md">{children}</Content>
</AnimatedDropdown>
</Column>
)
}

View File

@@ -1,7 +1,8 @@
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
import { useNativeUSDCArbitrumFlag } from 'featureFlags/flags/nativeUsdcArbitrum'
import { DetailsV2Variant, useDetailsV2Flag } from 'featureFlags/flags/nftDetails'
import { useWidgetRemovalFlag, WidgetRemovalVariant } from 'featureFlags/flags/removeWidgetTdp'
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
import { UnifiedRouterVariant, useRoutingAPIV2Flag } from 'featureFlags/flags/unifiedRouter'
import { useUpdateAtom } from 'jotai/utils'
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
import { X } from 'react-feather'
@@ -209,10 +210,16 @@ export default function FeatureFlagModal() {
label="Use the new details page for nfts"
/>
<FeatureFlagOption
variant={WidgetRemovalVariant}
value={useWidgetRemovalFlag()}
featureFlag={FeatureFlag.removeWidget}
label="Swap Component on TDP"
variant={UnifiedRouterVariant}
value={useRoutingAPIV2Flag()}
featureFlag={FeatureFlag.uraEnabled}
label="Enable the Unified Routing API"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useNativeUSDCArbitrumFlag()}
featureFlag={FeatureFlag.nativeUsdcArbitrum}
label="Enable Circle native USDC on Arbitrum"
/>
<FeatureFlagGroup name="Debug">
<FeatureFlagOption

View File

@@ -56,8 +56,8 @@ export default function FeeSelector({
disabled?: boolean
feeAmount?: FeeAmount
handleFeePoolSelect: (feeAmount: FeeAmount) => void
currencyA?: Currency | undefined
currencyB?: Currency | undefined
currencyA?: Currency
currencyB?: Currency
}) {
const { chainId } = useWeb3React()

View File

@@ -19,7 +19,7 @@ const Wrapper = styled.div<{ isDarkMode: boolean }>`
display: flex;
flex-flow: column nowrap;
margin: 0;
min-height: 720px;
flex: 1 1;
min-width: 375px;
position: relative;
width: 100%;
@@ -125,7 +125,7 @@ export default function FiatOnrampModal() {
}, [fetchSignedIframeUrl])
return (
<Modal isOpen={fiatOnrampModalOpen} onDismiss={closeModal} maxHeight={720}>
<Modal isOpen={fiatOnrampModalOpen} onDismiss={closeModal} height={80 /* vh */}>
<Wrapper data-testid="fiat-onramp-modal" isDarkMode={isDarkMode}>
{error ? (
<>

View File

@@ -28,7 +28,7 @@ export default function Loader({
{...rest}
>
<path
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 9.27455 20.9097 6.80375"
d="M2,12 a10,10 0 0,1 10,-10 M12,22 a10,10 0 0,1 -10,-10 M22,12 a10,10 0 0,1 -10,10"
strokeWidth={strokeWidth ?? '2.5'}
strokeLinecap="round"
strokeLinejoin="round"
@@ -58,3 +58,27 @@ export function LoaderV2() {
</StyledRotatingSVG>
)
}
export function LoaderV3({ size = '16px', color, ...rest }: { size?: string; color?: string; [k: string]: any }) {
const theme = useTheme()
return (
<StyledRotatingSVG
size={size}
viewBox="0 0 54 54"
xmlns="http://www.w3.org/2000/svg"
fill={color ?? theme.textTertiary}
stroke={color ?? theme.textTertiary}
{...rest}
>
<path
opacity="0.1"
d="M53.6666 26.9999C53.6666 41.7275 41.7276 53.6666 27 53.6666C12.2724 53.6666 0.333313 41.7275 0.333313 26.9999C0.333313 12.2723 12.2724 0.333252 27 0.333252C41.7276 0.333252 53.6666 12.2723 53.6666 26.9999ZM8.33331 26.9999C8.33331 37.3092 16.6907 45.6666 27 45.6666C37.3093 45.6666 45.6666 37.3092 45.6666 26.9999C45.6666 16.6906 37.3093 8.33325 27 8.33325C16.6907 8.33325 8.33331 16.6906 8.33331 26.9999Z"
fill={color ?? theme.textTertiary}
/>
<path
d="M49.6666 26.9999C51.8758 26.9999 53.6973 25.1992 53.3672 23.0149C53.0452 20.884 52.4652 18.7951 51.6368 16.795C50.2966 13.5597 48.3324 10.62 45.8562 8.14374C43.3799 5.66751 40.4402 3.70326 37.2049 2.36313C35.2048 1.53466 33.1159 0.954747 30.985 0.632693C28.8007 0.30256 27 2.12411 27 4.33325C27 6.54239 28.8108 8.29042 30.9695 8.76019C32.0523 8.99585 33.1146 9.32804 34.1434 9.75417C36.4081 10.6923 38.4659 12.0672 40.1993 13.8006C41.9327 15.534 43.3076 17.5918 44.2457 19.8565C44.6719 20.8853 45.004 21.9476 45.2397 23.0304C45.7095 25.1891 47.4575 26.9999 49.6666 26.9999Z"
fill={color ?? theme.textTertiary}
/>
</StyledRotatingSVG>
)
}

View File

@@ -79,8 +79,8 @@ interface StepCounterProps {
width?: string
locked?: boolean // disable input
title: ReactNode
tokenA: string | undefined
tokenB: string | undefined
tokenA?: string
tokenB?: string
}
const StepCounter = ({

View File

@@ -4,7 +4,7 @@ import styled from 'styled-components/macro'
import { ChartEntry } from './types'
const Path = styled.path<{ fill: string | undefined }>`
const Path = styled.path<{ fill?: string }>`
opacity: 0.5;
stroke: ${({ fill, theme }) => fill ?? theme.accentAction};
fill: ${({ fill, theme }) => fill ?? theme.accentAction};
@@ -23,7 +23,7 @@ export const Area = ({
yScale: ScaleLinear<number, number>
xValue: (d: ChartEntry) => number
yValue: (d: ChartEntry) => number
fill?: string | undefined
fill?: string
}) =>
useMemo(
() => (

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