Compare commits

...

37 Commits

Author SHA1 Message Date
UL Service Account
40d8da42a5 ci: add global CODEOWNERS 2023-08-18 20:38:47 +00:00
UL Service Account
2151ffbac7 ci(t9n): download translations from crowdin 2023-08-18 20:38:47 +00:00
Jack Short
6ec9bb7362 chore: moving conedison/provider to interface (#7119)
* chore: moving provider from coned to interface

* moving signing over to interface

* updating lockfile

* dedup

* use d3-array build for jest

* downgrading jest/types

* Revert "downgrading jest/types"

This reverts commit 88d3746c00.
2023-08-18 16:26:08 -04:00
Brendan Wong
31d0c3c9b3 fix: reorganize chain priority (#7189)
reorganize chain priority
2023-08-18 15:39:42 -04:00
Zach Pomerantz
88b7acf3ae feat: use cache while debouncing quotes (#7188)
* feat: check cache before debouncing quote

* feat: use cached values if available

* fix: initial loading state

* fix: no transition to loading

* chore: return skipToken from args

* test: update snapshots

* fix: add back stale state
2023-08-18 12:36:24 -07:00
eddie
877e000da6 feat: swap quote event (#7174)
* wip: more metrics

* wip: SWAP_INPUT_FIRST_USED

* feat: track elapsed times

* feat: add e2e test

* fix: order of logging

* feat: swap quote request logging

* feat: e2e test

* feat: another property

* test: test events separately

* fix: dont log for price quotes
2023-08-18 11:47:45 -07:00
Jack Short
3f05a88409 chore: revert path based routing (#7192)
* Revert "feat: use router depending on the origin (#6982)"

This reverts commit c9b4016b78.

* updating tests

* fixing import from styled components

* fixing styled imports
2023-08-18 14:40:47 -04:00
Zach Pomerantz
03ab5c80a8 feat: prefetch eth price for swap currencies (#7187)
* feat: prefetch eth price for swap currencies

* chore: clarify namings
2023-08-18 10:47:14 -07:00
Jordan Frankfurt
4faaa60aea chore: update web3-react (#7158)
* chore: update web3-react

yarn deduplicate

* catch .warn warnings
2023-08-18 11:31:50 -05:00
Zach Pomerantz
3a94a99293 fix: debouncing as a loading state (#7183)
fix: consider debouncing as loading state
2023-08-17 17:35:54 -07:00
Brendan Wong
e893bc2685 fix: make line height bigger for token preview (#7186)
* make line height bigger

* Update [[index]].tsx
2023-08-17 17:20:18 -04:00
Brendan Wong
cccf6ac680 fix: add assets to public folder (#7153)
* fix: add assets to public folder

* Update global-teardown.ts

* update assets

* resize logos
2023-08-17 16:15:17 -04:00
Jack Short
ea5af12b1d chore: moving language selection to own settings panel (#7169)
* chore: moving language selection to own settings panel

* auto switch when close

* updating e2e

* clickable style

* moving behind feature flag

* fixing tests

* this looks nicer

* nowrap for overflow
2023-08-17 16:05:18 -04:00
eddie
bf1f613a4f fix: network connector fix and lists fix (#7185) 2023-08-17 12:29:34 -07:00
Brendan Wong
49f1acb52a fix: displays price when not updated on TDP (#7068)
* Update PriceChart.tsx

* simplify deltaarrow function

* update text coloring

* increase gap a bit more

* rename tags

* restyling of information

* fix import issue

* Update src/components/Tokens/TokenDetails/PriceChart.tsx

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

* unit testing!

---------

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2023-08-17 14:27:16 -04:00
Jordan Frankfurt
a97005e2e1 feat: dedupe on commit (#7171)
* feat: dedupe on commit

* run existing script
2023-08-17 12:58:35 -05:00
Brendan Wong
966b02b2de fix: add distance check from white for token rich link previews (#7152)
* feat: lower white levels if too close

* testing and parameterization

* Update getColor.ts

* Update getColor.test.ts

* Update getColor.ts

* Update getColor.test.ts

* Update getColor.test.ts
2023-08-17 13:30:58 -04:00
eddie
024bbce9a4 feat: more swap metric logging (#7173)
* wip: more metrics

* wip: SWAP_INPUT_FIRST_USED

* feat: track elapsed times

* feat: add e2e test

* fix: order of logging

* fix: nits
2023-08-17 10:16:51 -07:00
Brendan Wong
c960c14170 fix: update default rich link preview image (#7176)
Update 1200x630_Rich_Link_Preview_Image.png
2023-08-17 12:05:52 -04:00
Zach Pomerantz
39295e9a33 feat: add real paths to app site association (#7179) 2023-08-17 09:05:10 -07:00
Brendan Wong
0feddebcc3 feat: add CodeCov to cloud tests (#7154)
* feat: add CodeCov to cloud tests

* Update codecov.yml

* Update codecov.yml

* get cov to 80%

* remove unneeded test
2023-08-17 11:30:56 -04:00
Thomas Thachil
5e4108fbdc chore(): deeplink fingerprint update (#7181) 2023-08-17 10:30:25 -04:00
eddie
38cce46c7b feat: redux migration (#6830)
* feat: start working on redux migrations

* feat: fix migations and add tests

* feat: fix persistence and improve tests

* fix: tests

* fix: rename test file so it doesnt run in jest

* fix: tests

* fix: lint

* feat: indexedDB

* fix: e2e tests

* fix: address some comments

* fix: update legacy migrations

* fix: fix rehydrations

* fix: remove PersistGate and fix e2e tests

* fix: add comment to helper function
2023-08-16 10:56:06 -07:00
Nate Wienert
69ae42f285 fix: broken spacing in tokens fixture (#7168) 2023-08-16 07:36:15 -10:00
cartcrom
ef9619b1bd test: uniswapx e2e activity (#7137)
* test: e2e uniswapX toggle/opt-in tests

* fix: update visit to match new version of hardhat

* test: e2e gouda orders

* fix: remove swapping before allowance has loaded

* refactor: opt-in rather than toggle

* fix: test comment

* test: uniswapx activity

* fix: lint and small pending hook refactors

* test: e2e UniswapX orders (#7110)

* test: e2e gouda orders

* fix: remove swapping before allowance has loaded

* refactor: opt-in rather than toggle

* fix: test comment

* fix: PR nits
2023-08-15 15:02:24 -04:00
cartcrom
041f3d5ba2 test: e2e uniswapX toggle/opt-in and order tests (#7067)
* test: e2e uniswapX toggle/opt-in tests

* fix: update visit to match new version of hardhat

* test: e2e UniswapX orders (#7110)

* test: e2e gouda orders

* fix: remove swapping before allowance has loaded

* refactor: opt-in rather than toggle

* fix: test comment

* fix: PR nits
2023-08-15 14:50:08 -04:00
Charles Bachmeier
666bb79833 fix: test id based on address & chain instead of ticker (#7167)
* fix: test id based on address instead of ticker

* add chain to test id

* use token address

* lowercase
2023-08-15 11:22:20 -07:00
Charles Bachmeier
2736d94432 feat: Add base support to Explore & Search (#7165)
* filter search results

* feat: add full base TDP support

* add base to uni wallet supported chains
2023-08-15 10:12:33 -07:00
eddie
57b098f309 feat: swap quote latency logging (#7143)
* feat: swap quote latency

* feat: measure quote latency

* feat: swap quote latency

* fix: improve variable name
2023-08-14 14:20:03 -07:00
Tina
c802132bd5 feat: bump version of uniswapx-sdk for eth output trades (#7160)
bump ver
2023-08-14 17:08:31 -04:00
Nate Wienert
485764fe38 feat: improve logic around hidden section of mini-portfolio balances spam tokens (#6988) 2023-08-14 11:06:40 -10:00
Brendan Wong
51dc10b467 fix: remove flakey tests from cloud functions and overall deflake (#7156)
* fix: remove flakey tests

* increase setup time

* reduce max workers

* Update .github/workflows/test.yml

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

* Update functions/global-setup.ts

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

* Update global-teardown.ts

* add retry time

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-08-14 16:50:49 -04:00
Tina
7cf768b8dd chore: feature flag cleanup for routing_api_price (#7159)
feature flag cleanup
2023-08-14 16:28:01 -04:00
eddie
59b5e81d16 feat: add chainId to SWAP_TRANSACTION_COMPLETED (#7094)
* feat: add chainId to swap_tx_completed

* feat: time_to_sign

* test: add tests for SignedTransactionTimestampRegistry

* fix: remove time_to_sign
2023-08-14 13:26:51 -07:00
Brendan Wong
8fc98abb1a fix: margins for nav on mobile (#6908)
* fix margins for nav

* fix spacing on mobile

* Revert "fix spacing on mobile"

This reverts commit c87791669a.

* update css for minis
2023-08-14 15:27:39 -04:00
Tina
def4ab3bc0 feat: add time to sign analytics (#7140)
* add time to sign analytics

* move comment
2023-08-14 14:13:38 -04:00
Jack Short
bb6de9056b fix: token margin in searchbar (#7155)
fix: token margin is searchbar
2023-08-14 13:59:45 -04:00
200 changed files with 124076 additions and 1050 deletions

View File

@@ -214,7 +214,6 @@ jobs:
name: Cloud typecheck
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
# TODO(WEB-2537): Setup CodeCOV
cloud-tests:
runs-on: ubuntu-latest
steps:
@@ -225,7 +224,13 @@ jobs:
path: node_modules/.cache
key: ${{ runner.os }}-cloud-jest-${{ github.run_id }}
restore-keys: ${{ runner.os }}-cloud-jest-
- run: yarn test:cloud --coverage --maxWorkers=100%
# Only use 1 worker, so the other can be used for the proxy server under test.
- run: yarn test:cloud --coverage --maxWorkers=1
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
flags: cloud-tests
pre:
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}

4
.husky/pre-commit Normal file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged

1
CODEOWNERS Normal file
View File

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

View File

@@ -69,10 +69,10 @@ Other things to note:
The Uniswap Interface supports swapping, adding liquidity, removing liquidity and migrating liquidity for Uniswap protocol V2.
- Swap on Uniswap V2: <https://app.uniswap.org/swap?use=v2>
- View V2 liquidity: <https://app.uniswap.org/pools/v2>
- Add V2 liquidity: <https://app.uniswap.org/add/v2>
- Migrate V2 liquidity to V3: <https://app.uniswap.org/migrate/v2>
- Swap on Uniswap V2: <https://app.uniswap.org/#/swap?use=v2>
- View V2 liquidity: <https://app.uniswap.org/#/pools/v2>
- Add V2 liquidity: <https://app.uniswap.org/#/add/v2>
- Migrate V2 liquidity to V3: <https://app.uniswap.org/#/migrate/v2>
## Accessing Uniswap V1

View File

@@ -28,6 +28,10 @@ flag_management:
target: 50%
individual_flags:
- name: unit-tests
- name: cloud-tests
statuses:
- type: project
target: 80%
comment:
layout: flags

View File

@@ -56,10 +56,10 @@ module.exports = {
'\\.css\\.ts$': '@vanilla-extract/jest-transform',
'\\.(t|j)sx?$': '@swc/jest',
},
// Use @uniswap/conedison's build directly, as jest does not support its exports.
transformIgnorePatterns: ['@uniswap/conedison/provider'],
// Use d3-arrays's build directly, as jest does not support its exports.
transformIgnorePatterns: ['d3-array'],
moduleNameMapper: {
'@uniswap/conedison/provider': '@uniswap/conedison/dist/provider',
'd3-array': 'd3-array/dist/d3-array.min.js',
},
})
},

View File

@@ -45,7 +45,7 @@ describe('Mini Portfolio account drawer', () => {
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.intercept(/graphql/, { fixture: 'mini-portfolio/full_activity.json' })
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click()
cy.get(getTestSelector('mini-portfolio-page')).contains('Contract Interaction')
})

View File

@@ -12,7 +12,7 @@ describe('Testing nfts', () => {
})
it('should load pudgy penguin collection page', () => {
cy.visit(`/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.get(getTestSelector('nft-collection-asset')).should('exist')
cy.get(getTestSelector('nft-collection-filter-buy-now')).should('not.exist')
cy.get(getTestSelector('nft-filter')).first().click()
@@ -20,13 +20,13 @@ describe('Testing nfts', () => {
})
it('should be able to navigate to activity', () => {
cy.visit(`/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.get(getTestSelector('nft-activity')).first().click()
cy.get(getTestSelector('nft-activity-row')).should('exist')
})
it('should go to the details page', () => {
cy.visit(`/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.get(getTestSelector('nft-filter')).first().click()
cy.get(getTestSelector('nft-collection-filter-buy-now')).click()
cy.get(getTestSelector('nft-collection-asset')).first().click()
@@ -37,7 +37,7 @@ describe('Testing nfts', () => {
})
it('should toggle buy now on details page', () => {
cy.visit(`/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.get(getTestSelector('nft-filter')).first().click()
cy.get(getTestSelector('nft-collection-filter-buy-now')).click()
cy.get(getTestSelector('nft-collection-asset')).first().click()

View File

@@ -1,3 +1,4 @@
import { SwapEventName } from '@uniswap/analytics-events'
import { ChainId } from '@uniswap/sdk-core'
import { UNI, USDC_MAINNET } from '../../../src/constants/tokens'
@@ -64,6 +65,13 @@ describe('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', '')
// Verify logging
cy.waitForAmplitudeEvent(SwapEventName.SWAP_QUOTE_RECEIVED).then((event: any) => {
cy.wrap(event.event_properties).should('have.property', 'quote_latency_milliseconds')
cy.wrap(event.event_properties.quote_latency_milliseconds).should('be.a', 'number')
cy.wrap(event.event_properties.quote_latency_milliseconds).should('be.gte', 0)
})
// Submit transaction
cy.get('#swap-button').click()
cy.contains('Review swap')

View File

@@ -0,0 +1,76 @@
import { SwapEventName } from '@uniswap/analytics-events'
import { USDC_MAINNET } from '../../../src/constants/tokens'
import { getTestSelector } from '../../utils'
describe('swap flow logging', () => {
it('completes two swaps and verifies the TTS logging for the first, plus all intermediate steps along the way', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`)
cy.hardhat()
// First swap in the session:
// 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', '')
// Verify first swap action
cy.waitForAmplitudeEvent(SwapEventName.SWAP_FIRST_ACTION).then((event: any) => {
cy.wrap(event.event_properties).should('have.property', 'time_to_first_swap_action')
cy.wrap(event.event_properties.time_to_first_swap_action).should('be.a', 'number')
cy.wrap(event.event_properties.time_to_first_swap_action).should('be.gte', 0)
})
// Verify Swap Quote
cy.waitForAmplitudeEvent(SwapEventName.SWAP_QUOTE_FETCH).then((event: any) => {
// Price quotes don't include these values, so we only verify the types if they exist
if (event.event_properties.time_to_first_quote_request) {
cy.wrap(event.event_properties.time_to_first_quote_request).should('be.a', 'number')
cy.wrap(event.event_properties.time_to_first_quote_request).should('be.gte', 0)
cy.wrap(event.event_properties.time_to_first_quote_request_since_first_input).should('be.a', 'number')
cy.wrap(event.event_properties.time_to_first_quote_request_since_first_input).should('be.gte', 0)
}
})
// Submit transaction
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.get(getTestSelector('confirmation-close-icon')).click()
cy.get(getTestSelector('popups')).contains('Swapped')
// Verify logging
cy.waitForAmplitudeEvent(SwapEventName.SWAP_TRANSACTION_COMPLETED).then((event: any) => {
cy.wrap(event.event_properties).should('have.property', 'time_to_swap')
cy.wrap(event.event_properties.time_to_swap).should('be.a', 'number')
cy.wrap(event.event_properties.time_to_swap).should('be.gte', 0)
cy.wrap(event.event_properties).should('have.property', 'time_to_swap_since_first_input')
cy.wrap(event.event_properties.time_to_swap_since_first_input).should('be.a', 'number')
cy.wrap(event.event_properties.time_to_swap_since_first_input).should('be.gte', 0)
})
// Second swap in the session:
// Enter amount to swap (different from first trade, to trigger a new quote request)
cy.get('#swap-currency-output .token-amount-input').clear().type('10').should('have.value', '10')
cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
// Verify second Swap Quote
cy.waitForAmplitudeEvent(SwapEventName.SWAP_QUOTE_FETCH).then((event: any) => {
// Price quotes don't include these values, so we only verify the types if they exist
if (event.event_properties.time_to_first_quote_request) {
cy.wrap(event.event_properties.time_to_first_quote_request).should('be.undefined')
cy.wrap(event.event_properties.time_to_first_quote_request_since_first_input).should('be.undefined')
}
})
// Submit transaction
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.get(getTestSelector('confirmation-close-icon')).click()
cy.get(getTestSelector('popups')).contains('Swapped')
cy.waitForAmplitudeEvent(SwapEventName.SWAP_TRANSACTION_COMPLETED).then((event: any) => {
cy.wrap(event.event_properties).should('not.have.property', 'time_to_swap')
cy.wrap(event.event_properties).should('not.have.property', 'time_to_swap_since_first_input')
})
})
})

View File

@@ -1,45 +0,0 @@
import { SwapEventName } from '@uniswap/analytics-events'
import { USDC_MAINNET } from '../../../src/constants/tokens'
import { getTestSelector } from '../../utils'
describe('time-to-swap logging', () => {
it('completes two swaps and verifies the TTS logging for the first', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`)
cy.hardhat()
// First swap in the session:
// 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.get(getTestSelector('confirmation-close-icon')).click()
cy.get(getTestSelector('popups')).contains('Swapped')
// Verify logging
cy.waitForAmplitudeEvent(SwapEventName.SWAP_TRANSACTION_COMPLETED).then((event: any) => {
cy.wrap(event.event_properties).should('have.property', 'time_to_swap')
cy.wrap(event.event_properties.time_to_swap).should('be.a', 'number')
cy.wrap(event.event_properties.time_to_swap).should('be.gte', 0)
})
// Second swap in the session:
// Enter amount to swap
cy.get('#swap-currency-output .token-amount-input').clear().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.get(getTestSelector('confirmation-close-icon')).click()
cy.get(getTestSelector('popups')).contains('Swapped')
cy.waitForAmplitudeEvent(SwapEventName.SWAP_TRANSACTION_COMPLETED).then((event: any) => {
cy.wrap(event.event_properties).should('not.have.property', 'time_to_swap')
})
})
})

View File

@@ -0,0 +1,27 @@
import { SwapEventName } from '@uniswap/analytics-events'
import { USDC_MAINNET } from 'constants/tokens'
import { getTestSelector } from '../../utils'
describe('Swap inputs with no wallet connected', () => {
it('can input and load a quote with no wallet connected', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`)
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('close-account-drawer')).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', '')
// Verify logging
cy.waitForAmplitudeEvent(SwapEventName.SWAP_QUOTE_RECEIVED).then((event: any) => {
cy.wrap(event.event_properties).should('have.property', 'quote_latency_milliseconds')
cy.wrap(event.event_properties.quote_latency_milliseconds).should('be.a', 'number')
cy.wrap(event.event_properties.quote_latency_milliseconds).should('be.gte', 0)
})
})
})

View File

@@ -0,0 +1,355 @@
import { ChainId, CurrencyAmount } from '@uniswap/sdk-core'
import { DAI, nativeOnChain, USDC_MAINNET } from '../../../src/constants/tokens'
import { getTestSelector } from '../../utils'
const QuoteEndpoint = 'https://api.uniswap.org/v2/quote'
const QuoteWhereUniswapXIsBetter = 'uniswapx/quote1.json'
const QuoteWithEthInput = 'uniswapx/quote2.json'
const OrderSubmissionEndpoint = 'https://api.uniswap.org/v2/order'
const OrderStatusEndpoint =
'https://api.uniswap.org/v2/orders?swapper=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266&orderHashes=0xa9dd6f05ad6d6c79bee654c31ede4d0d2392862711be0f3bc4a9124af24a6a19'
/** Stubs the provider to return a tx receipt corresponding to the mock filled uniswapx order's txHash */
function stubSwapTxReceipt() {
cy.hardhat().then((hardhat) => {
cy.fixture('uniswapx/fillTransactionReceipt.json').then((mockTxReceipt) => {
const getTransactionReceiptStub = cy.stub(hardhat.provider, 'getTransactionReceipt').log(false)
getTransactionReceiptStub.withArgs(mockTxReceipt.transactionHash).resolves(mockTxReceipt)
getTransactionReceiptStub.callThrough()
})
})
}
describe('UniswapX Toggle', () => {
beforeEach(() => {
cy.intercept(QuoteEndpoint, { fixture: QuoteWhereUniswapXIsBetter })
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
})
it('only displays uniswapx ui when setting is on', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
// UniswapX UI should not be visible
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('not.exist')
// Opt-in to UniswapX
cy.contains('Try it now').click()
// UniswapX UI should be visible
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('exist')
})
it('prompts opt-in if UniswapX is better', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
// UniswapX should not display in gas estimate row before opt-in
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('not.exist')
// UniswapX mustache should be visible
cy.contains('Try it now').click()
// Opt-in dialog should now be hidden
cy.contains('Try it now').should('not.be.visible')
// UniswapX should display in gas estimate row
cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('exist')
// Opt-in dialog should not reappear if user manually toggles UniswapX off
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.get(getTestSelector('toggle-uniswap-x-button')).click()
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.contains('Try it now').should('not.be.visible')
})
})
describe('UniswapX Orders', () => {
beforeEach(() => {
cy.intercept(QuoteEndpoint, { fixture: QuoteWhereUniswapXIsBetter })
cy.intercept(OrderSubmissionEndpoint, { fixture: 'uniswapx/orderResponse.json' })
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/openStatusResponse.json' })
stubSwapTxReceipt()
cy.hardhat().then((hardhat) => hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8)))
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
})
it('can swap using uniswapX', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
// Submit uniswapx order signature
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.wait('@eth_signTypedData_v4')
cy.contains('Swap submitted')
cy.contains('Learn more about swapping with UniswapX')
// Return filled order status from uniswapx api
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/filledStatusResponse.json' })
// Verify swap success
cy.contains('Swapped')
})
it('renders proper view if uniswapx order expires', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
// Submit uniswapx order signature
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
// Return expired order status from uniswapx api
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/expiredStatusResponse.json' })
// Verify swap failure message
cy.contains('Swap expired')
})
it('renders proper view if uniswapx order has insufficient funds', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
// Submit uniswapx order signature
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
// Return insufficient_funds order status from uniswapx api
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/insufficientFundsStatusResponse.json' })
// Verify swap failure message
cy.contains('Insufficient funds')
})
})
describe('UniswapX Eth Input', () => {
beforeEach(() => {
cy.intercept(QuoteEndpoint, { fixture: QuoteWithEthInput })
cy.intercept(OrderSubmissionEndpoint, { fixture: 'uniswapx/orderResponse.json' })
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/openStatusResponse.json' })
// Turn off automine so that intermediate screens are available to assert on.
cy.hardhat({ automine: false }).then(async (hardhat) => {
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(nativeOnChain(ChainId.MAINNET), 2e18))
await hardhat.mine()
})
stubSwapTxReceipt()
cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`)
})
it('can swap using uniswapX with ETH as input', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('1')
cy.contains('Try it now').click()
// Prompt ETH wrap to use for order
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.contains('Wrap ETH')
// Wrap ETH
cy.wait('@eth_sendRawTransaction')
cy.contains('Pending...')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Wrapped')
// Approve WETH spend
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
// Verify signed order submission
cy.wait('@eth_signTypedData_v4')
cy.contains('Swap submitted')
cy.contains('Learn more about swapping with UniswapX')
// Return filled order status from uniswapx api
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/filledStatusResponse.json' })
// Verify swap success
cy.contains('Swapped')
})
it('switches swap input to WETH after wrap', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('1')
cy.contains('Try it now').click()
// Prompt ETH wrap and confirm
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.wait('@eth_sendRawTransaction')
// Close review modal before wrap is confirmed on chain
cy.get(getTestSelector('confirmation-close-icon')).click()
cy.hardhat().then((hardhat) => hardhat.mine())
// Confirm wrap is successful and WETH is now input token
cy.contains('Wrapped')
cy.contains('WETH')
// Reopen review modal and continue swap
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
// Approve WETH spend
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
// Submit uniswapx order signature
cy.wait('@eth_signTypedData_v4')
cy.contains('Swap submitted')
cy.contains('Learn more about swapping with UniswapX')
// Return filled order status from uniswapx api
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/filledStatusResponse.json' })
// Verify swap success
cy.contains('Swapped')
})
})
describe('UniswapX activity history', () => {
beforeEach(() => {
cy.intercept(QuoteEndpoint, { fixture: QuoteWhereUniswapXIsBetter })
cy.intercept(OrderSubmissionEndpoint, { fixture: 'uniswapx/orderResponse.json' })
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/openStatusResponse.json' })
stubSwapTxReceipt()
cy.hardhat().then(async (hardhat) => {
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8))
})
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
})
it('can view UniswapX order status progress in activity', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
// Submit uniswapx order signature
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.wait('@eth_signTypedData_v4')
cy.get(getTestSelector('confirmation-close-icon')).click()
// Open mini portfolio and navigate to activity history
cy.get(getTestSelector('web3-status-connected')).click()
cy.intercept(/graphql/, { fixture: 'mini-portfolio/empty_activity.json' })
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click()
// Open pending order modal
cy.contains('Swapping').click()
cy.get(getTestSelector('offchain-activity-modal')).contains('Swapping')
cy.get(getTestSelector('offchain-activity-modal')).contains('Learn more about swapping with UniswapX')
// Return filled order status from uniswapx api
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/filledStatusResponse.json' })
cy.get(getTestSelector('offchain-activity-modal')).contains('Swapped')
cy.get(getTestSelector('offchain-activity-modal')).contains('View on Explorer')
})
it('can view UniswapX order status progress in activity upon expiry', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
// Submit uniswapx order signature
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.wait('@eth_signTypedData_v4')
cy.get(getTestSelector('confirmation-close-icon')).click()
// Open mini portfolio and navigate to activity history
cy.get(getTestSelector('web3-status-connected')).click()
cy.intercept(/graphql/, { fixture: 'mini-portfolio/empty_activity.json' })
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click()
// Open pending order modal
cy.contains('Swapping').click()
cy.get(getTestSelector('offchain-activity-modal')).contains('Swapping')
// Return filled order status from uniswapx api
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/expiredStatusResponse.json' })
cy.get(getTestSelector('offchain-activity-modal')).contains('Swap expired')
cy.get(getTestSelector('offchain-activity-modal')).contains('learn more')
})
it('deduplicates remote vs local uniswapx orders', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
// Submit uniswapx order signature
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
cy.wait('@eth_signTypedData_v4')
cy.get(getTestSelector('confirmation-close-icon')).click()
// Return filled order status from uniswapx api
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/filledStatusResponse.json' })
cy.contains('Swapped')
// Open mini portfolio
cy.get(getTestSelector('web3-status-connected')).click()
cy.fixture('mini-portfolio/uniswapx_activity.json').then((uniswapXActivity) => {
// Replace fixture's timestamp with current time
uniswapXActivity.data.portfolios[0].assetActivities[0].timestamp = Date.now() / 1000
cy.intercept(/graphql/, uniswapXActivity)
})
// Open activity history
cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click()
// Ensure gql and local order have been deduped, such that there is only one swap activity listed
cy.get(getTestSelector('activity-content')).contains('Swapped').should('have.length', 1)
})
it('balances should refetch after uniswapx swap', () => {
// Setup a swap
cy.get('#swap-currency-input .token-amount-input').type('300')
cy.contains('Try it now').click()
const gqlSpy = cy.spy().as('gqlSpy')
cy.intercept(/graphql/, (req) => {
// Spy on request frequency
req.on('response', gqlSpy)
// Reply with a fixture to speed up test
req.reply({
fixture: 'mini-portfolio/tokens.json',
})
})
// Expect balances to fetch upon opening mini portfolio
cy.get(getTestSelector('web3-status-connected')).click()
cy.get('@gqlSpy').should('have.been.calledOnce')
// Submit uniswapx order signature
cy.get('#swap-button').click()
cy.contains('Confirm swap').click()
// Expect balances to refetch after approval
cy.get('@gqlSpy').should('have.been.calledTwice')
// Return filled order status from uniswapx api
cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/filledStatusResponse.json' })
// Expect balances to refetch after swap
cy.get('@gqlSpy').should('have.been.calledThrice')
})
})

View File

@@ -18,7 +18,9 @@ describe('Token explore filter', () => {
searchFor('dao')
cy.get('@filteredTokens').then((filteredTokens) => {
cy.get('[data-cy="token-name"]').should('deep.equal', filteredTokens)
cy.get('[data-cy="token-name"]').then((tokens) => {
cy.wrap(Array.from(tokens)).should('deep.equal', Array.from(filteredTokens))
})
})
})
})

View File

@@ -69,5 +69,8 @@ describe('Token explore', () => {
cy.get(getTestSelector('tokens-network-filter-selected')).click()
cy.get(getTestSelector('tokens-network-filter-option-optimism')).click()
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Optimism')
cy.reload()
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Optimism')
cy.get(getTestSelector('chain-selector-logo')).invoke('attr', 'alt').should('eq', 'Ethereum')
})
})

View File

@@ -1,5 +1,10 @@
import { ChainId } from '@uniswap/sdk-core'
import { UNI } from 'constants/tokens'
import { getTestSelector } from '../utils'
const UNI_ADDRESS = UNI[ChainId.MAINNET].address.toLowerCase()
describe('Universal search bar', () => {
function openSearch() {
// can't just type "/" because on mobile it doesn't respond to that
@@ -19,18 +24,18 @@ describe('Universal search bar', () => {
openSearch()
getSearchBar().clear().type('uni')
cy.get(getTestSelector('searchbar-token-row-UNI'))
cy.get(getTestSelector(`searchbar-token-row-ETHEREUM-${UNI_ADDRESS}`))
.should('contain.text', 'Uniswap')
.and('contain.text', 'UNI')
.and('contain.text', '$')
.and('contain.text', '%')
.click()
cy.location('pathname').should('equal', '/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984')
cy.location('hash').should('equal', '#/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984')
openSearch()
cy.get(getTestSelector('searchbar-dropdown'))
.contains(getTestSelector('searchbar-dropdown'), 'Recent searches')
.find(getTestSelector('searchbar-token-row-UNI'))
.find(getTestSelector(`searchbar-token-row-ETHEREUM-${UNI_ADDRESS}`))
.should('exist')
})
@@ -51,13 +56,13 @@ describe('Universal search bar', () => {
// Seed recent results with UNI.
openSearch()
getSearchBar().type('uni')
cy.get(getTestSelector('searchbar-token-row-UNI'))
cy.get(getTestSelector(`searchbar-token-row-ETHEREUM-${UNI_ADDRESS}`))
getSearchBar().clear().type('{esc}')
// Search a different token by name.
openSearch()
getSearchBar().type('eth')
cy.get(getTestSelector('searchbar-token-row-ETH'))
cy.get(getTestSelector('searchbar-token-row-ETHEREUM-NATIVE'))
// Validate that we go to the searched/selected result.
getSearchBar().type('{enter}')

View File

@@ -1,3 +1,5 @@
import { FeatureFlag } from 'featureFlags'
import { getTestSelector } from '../utils'
describe('Wallet Dropdown', () => {
@@ -21,16 +23,20 @@ describe('Wallet Dropdown', () => {
})
}
function itChangesLocale() {
function itChangesLocale({ featureFlag = false }: { featureFlag?: boolean } = {}) {
it('should change locale', () => {
cy.contains('Uniswap available in: English').should('not.exist')
if (featureFlag) {
cy.get(getTestSelector('language-settings-button')).click()
}
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
cy.location('search').should('match', /\?lng=af-ZA$/)
cy.location('hash').should('match', /\?lng=af-ZA$/)
cy.contains('Uniswap available in: English')
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
cy.location('search').should('match', /\?lng=en-US$/)
cy.location('hash').should('match', /\?lng=en-US$/)
cy.contains('Uniswap available in: English').should('not.exist')
})
}
@@ -45,6 +51,15 @@ describe('Wallet Dropdown', () => {
itChangesLocale()
})
describe('should change locale with feature flag', () => {
beforeEach(() => {
cy.visit('/', { featureFlags: [FeatureFlag.currencyConversion] })
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
})
itChangesLocale({ featureFlag: true })
})
describe('testnet toggle', () => {
beforeEach(() => {
cy.visit('/swap')

View File

@@ -0,0 +1,12 @@
{
"data": {
"portfolios": [
{
"id": "UG9ydGZvbGlvOjB4ZjM5RmQ2ZTUxYWFkODhGNkY0Y2U2YUI4ODI3Mjc5Y2ZmRmI5MjI2Ng==",
"assetActivities": [],
"__typename": "Portfolio"
}
]
},
"errors": []
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,102 @@
{
"data": {
"portfolios": [
{
"id": "UG9ydGZvbGlvOjB4ZjM5RmQ2ZTUxYWFkODhGNkY0Y2U2YUI4ODI3Mjc5Y2ZmRmI5MjI2Ng==",
"assetActivities": [
{
"id": "QXNzZXRBY3Rpdml0eTpWSEpoYm5OaFkzUnBiMjQ2TUhnNE9EZGpOemN5TlRRNU1qWTVNVEkwWVRkbVpUTXlNams1TjJJNU0yUTJabUV3TjJObE1UQXhOamxrTjJJd1pXUXhObUV6TldabU16SmtOMk13TWpBeVh6QjRaREkzTXpnek1EUTRaalF4WldZMlpXRXhaV1EzWWpBeFltVTVOemRqTjJVME1HSXdaRGswTmw4d2VEUTNZVFF5TVdKalpXTTJORE5oWWpSallURmpZamc0TmpOaU4yWm1PV0ppWm1SaU5HVmlNVE09",
"timestamp": 1691001923,
"type": "SWAP_ORDER",
"chain": "ETHEREUM",
"details": {
"__typename": "TransactionDetails",
"id": "VHJhbnNhY3Rpb246MHg4ODdjNzcyNTQ5MjY5MTI0YTdmZTMyMjk5N2I5M2Q2ZmEwN2NlMTAxNjlkN2IwZWQxNmEzNWZmMzJkN2MwMjAyXzB4ZDI3MzgzMDQ4ZjQxZWY2ZWExZWQ3YjAxYmU5NzdjN2U0MGIwZDk0Nl8weDQ3YTQyMWJjZWM2NDNhYjRjYTFjYjg4NjNiN2ZmOWJiZmRiNGViMTM=",
"type": "SWAP_ORDER",
"from": "0xd27383048f41ef6ea1ed7b01be977c7e40b0d946",
"to": "0x47a421bcec643ab4ca1cb8863b7ff9bbfdb4eb13",
"hash": "0x9f8382a94ee80ca119bc690908ab5f69c4c72f7497ee10f37e9ede0ded83cca6",
"nonce": 439,
"status": "CONFIRMED"
},
"assetChanges": [
{
"__typename": "TokenTransfer",
"id": "VG9rZW5UcmFuc2ZlcjoweDgwYmVjYjgwOGJmYWRlNDE0MzE4M2U1OGQxOGYyMDgwZTg0ZTU3YTFfMHg0N2E0MjFiY2VjNjQzYWI0Y2ExY2I4ODYzYjdmZjliYmZkYjRlYjEzXzB4ODg3Yzc3MjU0OTI2OTEyNGE3ZmUzMjI5OTdiOTNkNmZhMDdjZTEwMTY5ZDdiMGVkMTZhMzVmZjMyZDdjMDIwMg==",
"asset": {
"id": "VG9rZW46RVRIRVJFVU1fMHhhMGI4Njk5MWM2MjE4YjM2YzFkMTlkNGEyZTllYjBjZTM2MDZlYjQ4",
"name": "USD Coin",
"symbol": "USDC",
"address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"decimals": 6,
"chain": "ETHEREUM",
"standard": null,
"project": {
"id": "VG9rZW5Qcm9qZWN0OkVUSEVSRVVNXzB4YTBiODY5OTFjNjIxOGIzNmMxZDE5ZDRhMmU5ZWIwY2UzNjA2ZWI0OA==",
"isSpam": false,
"logo": {
"id": "SW1hZ2U6aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL1VuaXN3YXAvYXNzZXRzL21hc3Rlci9ibG9ja2NoYWlucy9ldGhlcmV1bS9hc3NldHMvMHhBMGI4Njk5MWM2MjE4YjM2YzFkMTlENGEyZTlFYjBjRTM2MDZlQjQ4L2xvZ28ucG5n",
"url": "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"__typename": "Image"
},
"__typename": "TokenProject"
},
"__typename": "Token"
},
"tokenStandard": "ERC20",
"quantity": "300.0",
"sender": "0x80becb808bfade4143183e58d18f2080e84e57a1",
"recipient": "0x47a421bcec643ab4ca1cb8863b7ff9bbfdb4eb13",
"direction": "OUT",
"transactedValue": {
"id": "QW1vdW50OjMwMC4xNDkxNTIwOTE5NDE2M19VU0Q=",
"currency": "USD",
"value": 300.14915209194163,
"__typename": "Amount"
}
},
{
"__typename": "TokenTransfer",
"id": "VG9rZW5UcmFuc2ZlcjoweDQ3YTQyMWJjZWM2NDNhYjRjYTFjYjg4NjNiN2ZmOWJiZmRiNGViMTNfMHg4MGJlY2I4MDhiZmFkZTQxNDMxODNlNThkMThmMjA4MGU4NGU1N2ExXzB4ODg3Yzc3MjU0OTI2OTEyNGE3ZmUzMjI5OTdiOTNkNmZhMDdjZTEwMTY5ZDdiMGVkMTZhMzVmZjMyZDdjMDIwMg==",
"asset": {
"id": "VG9rZW46RVRIRVJFVU1fMHg2YjE3NTQ3NGU4OTA5NGM0NGRhOThiOTU0ZWVkZWFjNDk1MjcxZDBm",
"name": "Dai Stablecoin",
"symbol": "DAI",
"address": "0x6b175474e89094c44da98b954eedeac495271d0f",
"decimals": 18,
"chain": "ETHEREUM",
"standard": null,
"project": {
"id": "VG9rZW5Qcm9qZWN0OkVUSEVSRVVNXzB4NmIxNzU0NzRlODkwOTRjNDRkYTk4Yjk1NGVlZGVhYzQ5NTI3MWQwZg==",
"isSpam": false,
"logo": {
"id": "SW1hZ2U6aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL1VuaXN3YXAvYXNzZXRzL21hc3Rlci9ibG9ja2NoYWlucy9ldGhlcmV1bS9hc3NldHMvMHg2QjE3NTQ3NEU4OTA5NEM0NERhOThiOTU0RWVkZUFDNDk1MjcxZDBGL2xvZ28ucG5n",
"url": "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png",
"__typename": "Image"
},
"__typename": "TokenProject"
},
"__typename": "Token"
},
"tokenStandard": "ERC20",
"quantity": "280.573117586837733376",
"sender": "0x47a421bcec643ab4ca1cb8863b7ff9bbfdb4eb13",
"recipient": "0x80becb808bfade4143183e58d18f2080e84e57a1",
"direction": "IN",
"transactedValue": {
"id": "QW1vdW50OjI4MC42ODc3OTU0NTg2ODE4X1VTRA==",
"currency": "USD",
"value": 280.6877954586818,
"__typename": "Amount"
}
}
],
"__typename": "AssetActivity"
}
],
"__typename": "Portfolio"
}
]
},
"errors": []
}

View File

@@ -0,0 +1,26 @@
{
"orders": [
{
"outputs": [
{
"recipient": "0x80becb808bfade4143183e58d18f2080e84e57a1",
"startAmount": "91371770080538616664",
"endAmount": "90914911230135923580",
"token": "0x6B175474E89094C44Da98b954EedeAC495271d0F"
}
],
"encodedOrder": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000064837e2a0000000000000000000000000000000000000000000000000000000064837e6600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000bd7f9d0239f81c94b728d827a87b9864972661ec00000000000000000000000080becb808bfade4143183e58d18f2080e84e57a18e32c6335b6f657322448399bd12ff5c22b7b1aa770850ff4eed36c750e2de000000000000000000000000000000000000000000000000000000000064837e66000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000004f409bcc7a52b6358000000000000000000000000000000000000000000000004edb2a613726c737c00000000000000000000000080becb808bfade4143183e58d18f2080e84e57a1",
"signature": "0x973882a290778b5c8aae691ef777385259928cde0513d224ea1131538379258d2db7a69804110320b08558380394879a31ab8dea61152c2dba7623acbfa11d0e1b",
"input": {
"endAmount": "100000000",
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"startAmount": "100000000"
},
"orderStatus": "expired",
"createdAt": 1686339087,
"chainId": 1,
"orderHash": "0xa9dd6f05ad6d6c79bee654c31ede4d0d2392862711be0f3bc4a9124af24a6a19",
"type": "Dutch"
}
]
}

View File

@@ -0,0 +1,114 @@
{
"to": "0xbD7F9D0239f81C94b728d827a87b9864972661eC",
"from": "0xa17Fbb0b5a251A7ACA3BD7377e7eCC4F700A2C09",
"contractAddress": null,
"transactionIndex": 61,
"gasUsed": {
"type": "BigNumber",
"hex": "0x03e0c8"
},
"logsBloom":
"0x00000000000000000000008000200100000020000000000000000000000000000000000000000000000000010000000000000000000020000000000001000000000280000000000808000008000000000000000000000000000000000000200010000000100000000008000000000004402000080000000000000010000800000000000000000800000800000000000000000000010000000000000000000000000000000000200000000000005000000000000000000000000000000000000000000002000000000000000000000000040002000000000000000100000000090000000400000000000400000020080000000000000000000000000000000000",
"blockHash": "0x79cf0785f317f984eeaf737c592afff806cabf4fe0c46a84f62a4a0212cfab5c",
"transactionHash": "0x9f8382a94ee80ca119bc690908ab5f69c4c72f7497ee10f37e9ede0ded83cca6",
"logs": [
{
"transactionIndex": 61,
"blockNumber": 17444757,
"transactionHash": "0x9f8382a94ee80ca119bc690908ab5f69c4c72f7497ee10f37e9ede0ded83cca6",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x00000000000000000000000080becb808bfade4143183e58d18f2080e84e57a1",
"0x000000000000000000000000c59938e2d9ff9a0ecccbedf39031b1600d008eaf"
],
"data": "0x0000000000000000000000000000000000000000000000000000000005f5e100",
"logIndex": 103,
"blockHash": "0x79cf0785f317f984eeaf737c592afff806cabf4fe0c46a84f62a4a0212cfab5c"
},
{
"transactionIndex": 61,
"blockNumber": 17444757,
"transactionHash": "0x9f8382a94ee80ca119bc690908ab5f69c4c72f7497ee10f37e9ede0ded83cca6",
"address": "0xbD7F9D0239f81C94b728d827a87b9864972661eC",
"topics": [
"0x78ad7ec0e9f89e74012afa58738b6b661c024cb0fd185ee2f616c0a28924bd66",
"0xd10e1d90145460003d98ba4b788564e9549cc93c65a12c9b297720a9d6a586de",
"0x000000000000000000000000a17fbb0b5a251a7aca3bd7377e7ecc4f700a2c09",
"0x00000000000000000000000080becb808bfade4143183e58d18f2080e84e57a1"
],
"data": "0x8e32c6335b6f657322448399bd12ff5c22b7b1aa770850ff4eed36c750e2de00",
"logIndex": 104,
"blockHash": "0x79cf0785f317f984eeaf737c592afff806cabf4fe0c46a84f62a4a0212cfab5c"
},
{
"transactionIndex": 61,
"blockNumber": 17444757,
"transactionHash": "0x9f8382a94ee80ca119bc690908ab5f69c4c72f7497ee10f37e9ede0ded83cca6",
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000005777d92f208679db4b9778590fa3cab3ac9e2168",
"0x000000000000000000000000c59938e2d9ff9a0ecccbedf39031b1600d008eaf"
],
"data": "0x0000000000000000000000000000000000000000000000056b9a675be430b502",
"logIndex": 105,
"blockHash": "0x79cf0785f317f984eeaf737c592afff806cabf4fe0c46a84f62a4a0212cfab5c"
},
{
"transactionIndex": 61,
"blockNumber": 17444757,
"transactionHash": "0x9f8382a94ee80ca119bc690908ab5f69c4c72f7497ee10f37e9ede0ded83cca6",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000c59938e2d9ff9a0ecccbedf39031b1600d008eaf",
"0x0000000000000000000000005777d92f208679db4b9778590fa3cab3ac9e2168"
],
"data": "0x0000000000000000000000000000000000000000000000000000000005f5e100",
"logIndex": 106,
"blockHash": "0x79cf0785f317f984eeaf737c592afff806cabf4fe0c46a84f62a4a0212cfab5c"
},
{
"transactionIndex": 61,
"blockNumber": 17444757,
"transactionHash": "0x9f8382a94ee80ca119bc690908ab5f69c4c72f7497ee10f37e9ede0ded83cca6",
"address": "0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168",
"topics": [
"0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67",
"0x00000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45",
"0x000000000000000000000000c59938e2d9ff9a0ecccbedf39031b1600d008eaf"
],
"data": "0xfffffffffffffffffffffffffffffffffffffffffffffffa946598a41bcf4afe0000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000010c7063b90a5e90d13830000000000000000000000000000000000000000000071b57cb2bb0b5b28224ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbc89c",
"logIndex": 107,
"blockHash": "0x79cf0785f317f984eeaf737c592afff806cabf4fe0c46a84f62a4a0212cfab5c"
},
{
"transactionIndex": 61,
"blockNumber": 17444757,
"transactionHash": "0x9f8382a94ee80ca119bc690908ab5f69c4c72f7497ee10f37e9ede0ded83cca6",
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000c59938e2d9ff9a0ecccbedf39031b1600d008eaf",
"0x00000000000000000000000080becb808bfade4143183e58d18f2080e84e57a1"
],
"data": "0x000000000000000000000000000000000000000000000004f409bcc7a52b6358",
"logIndex": 108,
"blockHash": "0x79cf0785f317f984eeaf737c592afff806cabf4fe0c46a84f62a4a0212cfab5c"
}
],
"blockNumber": 17444757,
"confirmations": 392238,
"cumulativeGasUsed": {
"type": "BigNumber",
"hex": "0x4065ac"
},
"effectiveGasPrice": {
"type": "BigNumber",
"hex": "0x04aa792df0"
},
"status": 1,
"type": 2,
"byzantium": true
}

View File

@@ -0,0 +1,33 @@
{
"orders": [
{
"outputs": [
{
"recipient": "0x80becb808bfade4143183e58d18f2080e84e57a1",
"startAmount": "91371770080538616664",
"endAmount": "90914911230135923580",
"token": "0x6B175474E89094C44Da98b954EedeAC495271d0F"
}
],
"encodedOrder": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000064837e2a0000000000000000000000000000000000000000000000000000000064837e6600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000bd7f9d0239f81c94b728d827a87b9864972661ec00000000000000000000000080becb808bfade4143183e58d18f2080e84e57a18e32c6335b6f657322448399bd12ff5c22b7b1aa770850ff4eed36c750e2de000000000000000000000000000000000000000000000000000000000064837e66000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000004f409bcc7a52b6358000000000000000000000000000000000000000000000004edb2a613726c737c00000000000000000000000080becb808bfade4143183e58d18f2080e84e57a1",
"signature": "0x973882a290778b5c8aae691ef777385259928cde0513d224ea1131538379258d2db7a69804110320b08558380394879a31ab8dea61152c2dba7623acbfa11d0e1b",
"input": {
"endAmount": "100000000",
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"startAmount": "100000000"
},
"settledAmounts": [
{
"tokenOut": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"amountOut": "91371770080538616664"
}
],
"orderStatus": "filled",
"txHash": "0x9f8382a94ee80ca119bc690908ab5f69c4c72f7497ee10f37e9ede0ded83cca6",
"createdAt": 1686339087,
"chainId": 1,
"orderHash": "0xa9dd6f05ad6d6c79bee654c31ede4d0d2392862711be0f3bc4a9124af24a6a19",
"type": "Dutch"
}
]
}

View File

@@ -0,0 +1,26 @@
{
"orders": [
{
"outputs": [
{
"recipient": "0x80becb808bfade4143183e58d18f2080e84e57a1",
"startAmount": "91371770080538616664",
"endAmount": "90914911230135923580",
"token": "0x6B175474E89094C44Da98b954EedeAC495271d0F"
}
],
"encodedOrder": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000064837e2a0000000000000000000000000000000000000000000000000000000064837e6600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000bd7f9d0239f81c94b728d827a87b9864972661ec00000000000000000000000080becb808bfade4143183e58d18f2080e84e57a18e32c6335b6f657322448399bd12ff5c22b7b1aa770850ff4eed36c750e2de000000000000000000000000000000000000000000000000000000000064837e66000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000004f409bcc7a52b6358000000000000000000000000000000000000000000000004edb2a613726c737c00000000000000000000000080becb808bfade4143183e58d18f2080e84e57a1",
"signature": "0x973882a290778b5c8aae691ef777385259928cde0513d224ea1131538379258d2db7a69804110320b08558380394879a31ab8dea61152c2dba7623acbfa11d0e1b",
"input": {
"endAmount": "100000000",
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"startAmount": "100000000"
},
"orderStatus": "insufficient-funds",
"createdAt": 1686339087,
"chainId": 1,
"orderHash": "0xa9dd6f05ad6d6c79bee654c31ede4d0d2392862711be0f3bc4a9124af24a6a19",
"type": "Dutch"
}
]
}

View File

@@ -0,0 +1,26 @@
{
"orders": [
{
"outputs": [
{
"recipient": "0x80becb808bfade4143183e58d18f2080e84e57a1",
"startAmount": "91371770080538616664",
"endAmount": "90914911230135923580",
"token": "0x6B175474E89094C44Da98b954EedeAC495271d0F"
}
],
"encodedOrder": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000064837e2a0000000000000000000000000000000000000000000000000000000064837e6600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000bd7f9d0239f81c94b728d827a87b9864972661ec00000000000000000000000080becb808bfade4143183e58d18f2080e84e57a18e32c6335b6f657322448399bd12ff5c22b7b1aa770850ff4eed36c750e2de000000000000000000000000000000000000000000000000000000000064837e66000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000004f409bcc7a52b6358000000000000000000000000000000000000000000000004edb2a613726c737c00000000000000000000000080becb808bfade4143183e58d18f2080e84e57a1",
"signature": "0x973882a290778b5c8aae691ef777385259928cde0513d224ea1131538379258d2db7a69804110320b08558380394879a31ab8dea61152c2dba7623acbfa11d0e1b",
"input": {
"endAmount": "100000000",
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"startAmount": "100000000"
},
"orderStatus": "open",
"createdAt": 1686339087,
"chainId": 1,
"orderHash": "0xa9dd6f05ad6d6c79bee654c31ede4d0d2392862711be0f3bc4a9124af24a6a19",
"type": "Dutch"
}
]
}

View File

@@ -0,0 +1 @@
{"hash":"0xa9dd6f05ad6d6c79bee654c31ede4d0d2392862711be0f3bc4a9124af24a6a19"}

View File

@@ -0,0 +1,491 @@
{
"routing": "DUTCH_LIMIT",
"quote": {
"orderInfo": {
"chainId": 1,
"permit2Address": "0x000000000022d473030f116ddee9f6b43ac78ba3",
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x67d615D6bccAA1562B1cca9786384b4840597ecD",
"nonce": "57335948072881703373319552024074512292695687510330025934414357004397546394368",
"deadline": 1690902198,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x",
"decayStartTime": 1690902126,
"decayEndTime": 1690902186,
"exclusiveFiller": "0x0000000000000000000000000000000000000000",
"exclusivityOverrideBps": "0",
"input": {
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"startAmount": "300000000",
"endAmount": "300000000"
},
"outputs": [
{
"token": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"startAmount": "289951120815684452958",
"endAmount": "267060007981523637666",
"recipient": "0x67d615D6bccAA1562B1cca9786384b4840597ecD"
}
]
},
"encodedOrder": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000064c91e6e0000000000000000000000000000000000000000000000000000000064c91eaa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000011e1a3000000000000000000000000000000000000000000000000000000000011e1a30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000006000da47483062a0d734ba3dc7576ce6a0b645c400000000000000000000000067d615d6bccaa1562b1cca9786384b4840597ecd7ec2ff20796a08922e11fd828e3871a6aa9a80e6495e30cd41be24b7e37953000000000000000000000000000000000000000000000000000000000064c91eb6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000fb7e15027ad3e025e00000000000000000000000000000000000000000000000e7a33be508bb395a200000000000000000000000067d615d6bccaa1562b1cca9786384b4840597ecd",
"quoteId": "f9f47cd7-a62c-4622-9ac7-51d0e662245a",
"requestId": "2d16f993-6429-4755-ba50-1383789459dc",
"auctionPeriodSecs": 60,
"deadlineBufferSecs": 12,
"slippageTolerance": "0.5",
"permitData": {
"domain": {
"name": "Permit2",
"chainId": 1,
"verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
},
"types": {
"PermitWitnessTransferFrom": [
{
"name": "permitted",
"type": "TokenPermissions"
},
{
"name": "spender",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "witness",
"type": "ExclusiveDutchOrder"
}
],
"TokenPermissions": [
{
"name": "token",
"type": "address"
},
{
"name": "amount",
"type": "uint256"
}
],
"ExclusiveDutchOrder": [
{
"name": "info",
"type": "OrderInfo"
},
{
"name": "decayStartTime",
"type": "uint256"
},
{
"name": "decayEndTime",
"type": "uint256"
},
{
"name": "exclusiveFiller",
"type": "address"
},
{
"name": "exclusivityOverrideBps",
"type": "uint256"
},
{
"name": "inputToken",
"type": "address"
},
{
"name": "inputStartAmount",
"type": "uint256"
},
{
"name": "inputEndAmount",
"type": "uint256"
},
{
"name": "outputs",
"type": "DutchOutput[]"
}
],
"OrderInfo": [
{
"name": "reactor",
"type": "address"
},
{
"name": "swapper",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "additionalValidationContract",
"type": "address"
},
{
"name": "additionalValidationData",
"type": "bytes"
}
],
"DutchOutput": [
{
"name": "token",
"type": "address"
},
{
"name": "startAmount",
"type": "uint256"
},
{
"name": "endAmount",
"type": "uint256"
},
{
"name": "recipient",
"type": "address"
}
]
},
"values": {
"permitted": {
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"amount": {
"type": "BigNumber",
"hex": "0x11e1a300"
}
},
"spender": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"nonce": {
"type": "BigNumber",
"hex": "0x7ec2ff20796a08922e11fd828e3871a6aa9a80e6495e30cd41be24b7e3795300"
},
"deadline": 1690902198,
"witness": {
"info": {
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x67d615D6bccAA1562B1cca9786384b4840597ecD",
"nonce": {
"type": "BigNumber",
"hex": "0x7ec2ff20796a08922e11fd828e3871a6aa9a80e6495e30cd41be24b7e3795300"
},
"deadline": 1690902198,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x"
},
"decayStartTime": 1690902126,
"decayEndTime": 1690902186,
"exclusiveFiller": "0x0000000000000000000000000000000000000000",
"exclusivityOverrideBps": {
"type": "BigNumber",
"hex": "0x00"
},
"inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"inputStartAmount": {
"type": "BigNumber",
"hex": "0x11e1a300"
},
"inputEndAmount": {
"type": "BigNumber",
"hex": "0x11e1a300"
},
"outputs": [
{
"token": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"startAmount": {
"type": "BigNumber",
"hex": "0x0fb7e15027ad3e025e"
},
"endAmount": {
"type": "BigNumber",
"hex": "0x0e7a33be508bb395a2"
},
"recipient": "0x67d615D6bccAA1562B1cca9786384b4840597ecD"
}
]
}
}
}
},
"requestId": "2d16f993-6429-4755-ba50-1383789459dc",
"allQuotes": [
{
"routing": "DUTCH_LIMIT",
"quote": {
"orderInfo": {
"chainId": 1,
"permit2Address": "0x000000000022d473030f116ddee9f6b43ac78ba3",
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x67d615D6bccAA1562B1cca9786384b4840597ecD",
"nonce": "57335948072881703373319552024074512292695687510330025934414357004397546394368",
"deadline": 1690902198,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x",
"decayStartTime": 1690902126,
"decayEndTime": 1690902186,
"exclusiveFiller": "0x0000000000000000000000000000000000000000",
"exclusivityOverrideBps": "0",
"input": {
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"startAmount": "300000000",
"endAmount": "300000000"
},
"outputs": [
{
"token": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"startAmount": "289951120815684452958",
"endAmount": "267060007981523637666",
"recipient": "0x67d615D6bccAA1562B1cca9786384b4840597ecD"
}
]
},
"encodedOrder": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000064c91e6e0000000000000000000000000000000000000000000000000000000064c91eaa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000011e1a3000000000000000000000000000000000000000000000000000000000011e1a30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000006000da47483062a0d734ba3dc7576ce6a0b645c400000000000000000000000067d615d6bccaa1562b1cca9786384b4840597ecd7ec2ff20796a08922e11fd828e3871a6aa9a80e6495e30cd41be24b7e37953000000000000000000000000000000000000000000000000000000000064c91eb6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000fb7e15027ad3e025e00000000000000000000000000000000000000000000000e7a33be508bb395a200000000000000000000000067d615d6bccaa1562b1cca9786384b4840597ecd",
"quoteId": "f9f47cd7-a62c-4622-9ac7-51d0e662245a",
"requestId": "2d16f993-6429-4755-ba50-1383789459dc",
"auctionPeriodSecs": 60,
"deadlineBufferSecs": 12,
"slippageTolerance": "0.5",
"permitData": {
"domain": {
"name": "Permit2",
"chainId": 1,
"verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
},
"types": {
"PermitWitnessTransferFrom": [
{
"name": "permitted",
"type": "TokenPermissions"
},
{
"name": "spender",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "witness",
"type": "ExclusiveDutchOrder"
}
],
"TokenPermissions": [
{
"name": "token",
"type": "address"
},
{
"name": "amount",
"type": "uint256"
}
],
"ExclusiveDutchOrder": [
{
"name": "info",
"type": "OrderInfo"
},
{
"name": "decayStartTime",
"type": "uint256"
},
{
"name": "decayEndTime",
"type": "uint256"
},
{
"name": "exclusiveFiller",
"type": "address"
},
{
"name": "exclusivityOverrideBps",
"type": "uint256"
},
{
"name": "inputToken",
"type": "address"
},
{
"name": "inputStartAmount",
"type": "uint256"
},
{
"name": "inputEndAmount",
"type": "uint256"
},
{
"name": "outputs",
"type": "DutchOutput[]"
}
],
"OrderInfo": [
{
"name": "reactor",
"type": "address"
},
{
"name": "swapper",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "additionalValidationContract",
"type": "address"
},
{
"name": "additionalValidationData",
"type": "bytes"
}
],
"DutchOutput": [
{
"name": "token",
"type": "address"
},
{
"name": "startAmount",
"type": "uint256"
},
{
"name": "endAmount",
"type": "uint256"
},
{
"name": "recipient",
"type": "address"
}
]
},
"values": {
"permitted": {
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"amount": {
"type": "BigNumber",
"hex": "0x11e1a300"
}
},
"spender": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"nonce": {
"type": "BigNumber",
"hex": "0x7ec2ff20796a08922e11fd828e3871a6aa9a80e6495e30cd41be24b7e3795300"
},
"deadline": 1690902198,
"witness": {
"info": {
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x67d615D6bccAA1562B1cca9786384b4840597ecD",
"nonce": {
"type": "BigNumber",
"hex": "0x7ec2ff20796a08922e11fd828e3871a6aa9a80e6495e30cd41be24b7e3795300"
},
"deadline": 1690902198,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x"
},
"decayStartTime": 1690902126,
"decayEndTime": 1690902186,
"exclusiveFiller": "0x0000000000000000000000000000000000000000",
"exclusivityOverrideBps": {
"type": "BigNumber",
"hex": "0x00"
},
"inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"inputStartAmount": {
"type": "BigNumber",
"hex": "0x11e1a300"
},
"inputEndAmount": {
"type": "BigNumber",
"hex": "0x11e1a300"
},
"outputs": [
{
"token": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"startAmount": {
"type": "BigNumber",
"hex": "0x0fb7e15027ad3e025e"
},
"endAmount": {
"type": "BigNumber",
"hex": "0x0e7a33be508bb395a2"
},
"recipient": "0x67d615D6bccAA1562B1cca9786384b4840597ecD"
}
]
}
}
}
}
},
{
"routing": "CLASSIC",
"quote": {
"blockNumber": "17820918",
"amount": "300000000",
"amountDecimals": "300",
"quote": "299952256425393549464",
"quoteDecimals": "299.952256425393549464",
"quoteGasAdjusted": "289922128602824170541",
"quoteGasAdjustedDecimals": "289.922128602824170541",
"gasUseEstimateQuote": "10030127822569378922",
"gasUseEstimateQuoteDecimals": "10.030127822569378922",
"gasUseEstimate": "128000",
"gasUseEstimateUSD": "10.031724",
"simulationStatus": "UNATTEMPTED",
"simulationError": false,
"gasPriceWei": "42803167855",
"route": [
[
{
"type": "v3-pool",
"address": "0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168",
"tokenIn": {
"chainId": 1,
"decimals": "6",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC"
},
"tokenOut": {
"chainId": 1,
"decimals": "18",
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"symbol": "DAI"
},
"fee": "100",
"liquidity": "534676532046235168447130",
"sqrtRatioX96": "79230505815006815109584",
"tickCurrent": "-276324",
"amountIn": "300000000",
"amountOut": "299952256425393549464"
}
]
],
"routeString": "[V3] 100.00% = USDC -- 0.01% [0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168] --> DAI",
"quoteId": "1dd3bd14-780e-41c6-88e1-30a763f97482",
"requestId": "2d16f993-6429-4755-ba50-1383789459dc",
"tradeType": "EXACT_INPUT",
"slippage": 0.5
}
}
]
}

View File

@@ -0,0 +1,491 @@
{
"routing": "DUTCH_LIMIT",
"quote": {
"orderInfo": {
"chainId": 1,
"permit2Address": "0x000000000022d473030f116ddee9f6b43ac78ba3",
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x0000000000000000000000000000000000000000",
"nonce": "1993350209834725680308575292465150260730647098062962750049345504775310970881",
"deadline": 1691176812,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x",
"decayStartTime": 1691176740,
"decayEndTime": 1691176800,
"exclusiveFiller": "0x165D98de005d2818176B99B1A93b9325dBE58181",
"exclusivityOverrideBps": "100",
"input": {
"token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"startAmount": "1000000000000000000",
"endAmount": "1000000000000000000"
},
"outputs": [
{
"token": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"startAmount": "929502510517534478575",
"endAmount": "919795986077127665276",
"recipient": "0x0000000000000000000000000000000000000000"
}
]
},
"encodedOrder": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000064cd4f240000000000000000000000000000000000000000000000000000000064cd4f60000000000000000000000000165d98de005d2818176b99b1a93b9325dbe581810000000000000000000000000000000000000000000000000000000000000064000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000006000da47483062a0d734ba3dc7576ce6a0b645c400000000000000000000000000000000000000000000000000000000000000000468323c9682990e3dc0646f899b437e62fbfb52a63cc8de721280222d8090010000000000000000000000000000000000000000000000000000000064cd4f6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000092d6c1e31e14520e676a687f0a93788b716beff500000000000000000000000000000000000000000000003263704899af6e50ef000000000000000000000000000000000000000000000031dcbbc80c9555e67c0000000000000000000000000000000000000000000000000000000000000000",
"quoteId": "09ce28b7-1ddf-4317-a28d-d21092be9f84",
"requestId": "f00535d4-461a-4363-afbe-7a5ab7061cd1",
"auctionPeriodSecs": 60,
"deadlineBufferSecs": 12,
"slippageTolerance": "0.5",
"permitData": {
"domain": {
"name": "Permit2",
"chainId": 1,
"verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
},
"types": {
"PermitWitnessTransferFrom": [
{
"name": "permitted",
"type": "TokenPermissions"
},
{
"name": "spender",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "witness",
"type": "ExclusiveDutchOrder"
}
],
"TokenPermissions": [
{
"name": "token",
"type": "address"
},
{
"name": "amount",
"type": "uint256"
}
],
"ExclusiveDutchOrder": [
{
"name": "info",
"type": "OrderInfo"
},
{
"name": "decayStartTime",
"type": "uint256"
},
{
"name": "decayEndTime",
"type": "uint256"
},
{
"name": "exclusiveFiller",
"type": "address"
},
{
"name": "exclusivityOverrideBps",
"type": "uint256"
},
{
"name": "inputToken",
"type": "address"
},
{
"name": "inputStartAmount",
"type": "uint256"
},
{
"name": "inputEndAmount",
"type": "uint256"
},
{
"name": "outputs",
"type": "DutchOutput[]"
}
],
"OrderInfo": [
{
"name": "reactor",
"type": "address"
},
{
"name": "swapper",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "additionalValidationContract",
"type": "address"
},
{
"name": "additionalValidationData",
"type": "bytes"
}
],
"DutchOutput": [
{
"name": "token",
"type": "address"
},
{
"name": "startAmount",
"type": "uint256"
},
{
"name": "endAmount",
"type": "uint256"
},
{
"name": "recipient",
"type": "address"
}
]
},
"values": {
"permitted": {
"token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"amount": {
"type": "BigNumber",
"hex": "0x0de0b6b3a7640000"
}
},
"spender": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"nonce": {
"type": "BigNumber",
"hex": "0x0468323c9682990e3dc0646f899b437e62fbfb52a63cc8de721280222d809001"
},
"deadline": 1691176812,
"witness": {
"info": {
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x0000000000000000000000000000000000000000",
"nonce": {
"type": "BigNumber",
"hex": "0x0468323c9682990e3dc0646f899b437e62fbfb52a63cc8de721280222d809001"
},
"deadline": 1691176812,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x"
},
"decayStartTime": 1691176740,
"decayEndTime": 1691176800,
"exclusiveFiller": "0x165D98de005d2818176B99B1A93b9325dBE58181",
"exclusivityOverrideBps": {
"type": "BigNumber",
"hex": "0x64"
},
"inputToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"inputStartAmount": {
"type": "BigNumber",
"hex": "0x0de0b6b3a7640000"
},
"inputEndAmount": {
"type": "BigNumber",
"hex": "0x0de0b6b3a7640000"
},
"outputs": [
{
"token": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"startAmount": {
"type": "BigNumber",
"hex": "0x3263704899af6e50ef"
},
"endAmount": {
"type": "BigNumber",
"hex": "0x31dcbbc80c9555e67c"
},
"recipient": "0x0000000000000000000000000000000000000000"
}
]
}
}
}
},
"requestId": "f00535d4-461a-4363-afbe-7a5ab7061cd1",
"allQuotes": [
{
"routing": "DUTCH_LIMIT",
"quote": {
"orderInfo": {
"chainId": 1,
"permit2Address": "0x000000000022d473030f116ddee9f6b43ac78ba3",
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x0000000000000000000000000000000000000000",
"nonce": "1993350209834725680308575292465150260730647098062962750049345504775310970881",
"deadline": 1691176812,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x",
"decayStartTime": 1691176740,
"decayEndTime": 1691176800,
"exclusiveFiller": "0x165D98de005d2818176B99B1A93b9325dBE58181",
"exclusivityOverrideBps": "100",
"input": {
"token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"startAmount": "1000000000000000000",
"endAmount": "1000000000000000000"
},
"outputs": [
{
"token": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"startAmount": "929502510517534478575",
"endAmount": "919795986077127665276",
"recipient": "0x0000000000000000000000000000000000000000"
}
]
},
"encodedOrder": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000064cd4f240000000000000000000000000000000000000000000000000000000064cd4f60000000000000000000000000165d98de005d2818176b99b1a93b9325dbe581810000000000000000000000000000000000000000000000000000000000000064000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000006000da47483062a0d734ba3dc7576ce6a0b645c400000000000000000000000000000000000000000000000000000000000000000468323c9682990e3dc0646f899b437e62fbfb52a63cc8de721280222d8090010000000000000000000000000000000000000000000000000000000064cd4f6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000092d6c1e31e14520e676a687f0a93788b716beff500000000000000000000000000000000000000000000003263704899af6e50ef000000000000000000000000000000000000000000000031dcbbc80c9555e67c0000000000000000000000000000000000000000000000000000000000000000",
"quoteId": "09ce28b7-1ddf-4317-a28d-d21092be9f84",
"requestId": "f00535d4-461a-4363-afbe-7a5ab7061cd1",
"auctionPeriodSecs": 60,
"deadlineBufferSecs": 12,
"slippageTolerance": "0.5",
"permitData": {
"domain": {
"name": "Permit2",
"chainId": 1,
"verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
},
"types": {
"PermitWitnessTransferFrom": [
{
"name": "permitted",
"type": "TokenPermissions"
},
{
"name": "spender",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "witness",
"type": "ExclusiveDutchOrder"
}
],
"TokenPermissions": [
{
"name": "token",
"type": "address"
},
{
"name": "amount",
"type": "uint256"
}
],
"ExclusiveDutchOrder": [
{
"name": "info",
"type": "OrderInfo"
},
{
"name": "decayStartTime",
"type": "uint256"
},
{
"name": "decayEndTime",
"type": "uint256"
},
{
"name": "exclusiveFiller",
"type": "address"
},
{
"name": "exclusivityOverrideBps",
"type": "uint256"
},
{
"name": "inputToken",
"type": "address"
},
{
"name": "inputStartAmount",
"type": "uint256"
},
{
"name": "inputEndAmount",
"type": "uint256"
},
{
"name": "outputs",
"type": "DutchOutput[]"
}
],
"OrderInfo": [
{
"name": "reactor",
"type": "address"
},
{
"name": "swapper",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "additionalValidationContract",
"type": "address"
},
{
"name": "additionalValidationData",
"type": "bytes"
}
],
"DutchOutput": [
{
"name": "token",
"type": "address"
},
{
"name": "startAmount",
"type": "uint256"
},
{
"name": "endAmount",
"type": "uint256"
},
{
"name": "recipient",
"type": "address"
}
]
},
"values": {
"permitted": {
"token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"amount": {
"type": "BigNumber",
"hex": "0x0de0b6b3a7640000"
}
},
"spender": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"nonce": {
"type": "BigNumber",
"hex": "0x0468323c9682990e3dc0646f899b437e62fbfb52a63cc8de721280222d809001"
},
"deadline": 1691176812,
"witness": {
"info": {
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x0000000000000000000000000000000000000000",
"nonce": {
"type": "BigNumber",
"hex": "0x0468323c9682990e3dc0646f899b437e62fbfb52a63cc8de721280222d809001"
},
"deadline": 1691176812,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x"
},
"decayStartTime": 1691176740,
"decayEndTime": 1691176800,
"exclusiveFiller": "0x165D98de005d2818176B99B1A93b9325dBE58181",
"exclusivityOverrideBps": {
"type": "BigNumber",
"hex": "0x64"
},
"inputToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"inputStartAmount": {
"type": "BigNumber",
"hex": "0x0de0b6b3a7640000"
},
"inputEndAmount": {
"type": "BigNumber",
"hex": "0x0de0b6b3a7640000"
},
"outputs": [
{
"token": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"startAmount": {
"type": "BigNumber",
"hex": "0x3263704899af6e50ef"
},
"endAmount": {
"type": "BigNumber",
"hex": "0x31dcbbc80c9555e67c"
},
"recipient": "0x0000000000000000000000000000000000000000"
}
]
}
}
}
}
},
{
"routing": "CLASSIC",
"quote": {
"blockNumber": "17843654",
"amount": "1000000000000000000",
"amountDecimals": "1",
"quote": "931181529570145926787",
"quoteDecimals": "931.181529570145926787",
"quoteGasAdjusted": "929033336026294051828",
"quoteGasAdjustedDecimals": "929.033336026294051828",
"gasUseEstimateQuote": "2148193543851874958",
"gasUseEstimateQuoteDecimals": "2.148193543851874958",
"gasUseEstimate": "128000",
"gasUseEstimateUSD": "4.174934",
"simulationStatus": "UNATTEMPTED",
"simulationError": false,
"gasPriceWei": "17811260539",
"route": [
[
{
"type": "v3-pool",
"address": "0xD8de6af55F618a7Bc69835D55DDC6582220c36c0",
"tokenIn": {
"chainId": 1,
"decimals": "18",
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"symbol": "WETH"
},
"tokenOut": {
"chainId": 1,
"decimals": "18",
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"symbol": "DAI"
},
"fee": "3000",
"liquidity": "62287359628325896425115",
"sqrtRatioX96": "2591813593283507889384697884",
"tickCurrent": "-68403",
"amountIn": "1000000000000000000",
"amountOut": "931181529570145926787"
}
]
],
"routeString": "[V3] 100.00% = WETH -- 0.3% [0xD8de6af55F618a7Bc69835D55DDC6582220c36c0] --> DAI",
"quoteId": "414e5f1c-120a-4e35-9760-c54d4b09e91d",
"requestId": "f00535d4-461a-4363-afbe-7a5ab7061cd1",
"tradeType": "EXACT_INPUT",
"slippage": 0.5
}
}
]
}

View File

@@ -12,7 +12,7 @@ describe('translations', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.get(getTestSelector('wallet-language-item')).contains('français').click({ force: true })
cy.location('search').should('match', /\?lng=fr-FR$/)
cy.location('hash').should('match', /\?lng=fr-FR$/)
cy.contains('Échanger')
cy.contains('Uniswap disponible en : English')
})

View File

@@ -3,8 +3,8 @@ 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 { initialState, UserState } from '../../src/state/user/reducer'
import { CONNECTED_WALLET_USER_STATE, setInitialUserState } from '../utils/user-state'
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
@@ -54,14 +54,12 @@ Cypress.Commands.overwrite(
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({ ...CONNECTED_WALLET_USER_STATE, ...(options?.userState ?? {}) })
)
setInitialUserState(win, {
...initialState,
hideUniswapWalletBanner: true,
...CONNECTED_WALLET_USER_STATE,
...(options?.userState ?? {}),
})
// Set feature flags, if configured.
if (options?.featureFlags) {

View File

@@ -4,3 +4,31 @@ import { UserState } from '../../src/state/user/reducer'
export const CONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: ConnectionType.INJECTED }
export const DISCONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: undefined }
/**
* This sets the initial value of the "user" slice in IndexedDB.
* Other persisted slices are not set, so they will be filled with their respective initial values
* when the app runs.
*/
export function setInitialUserState(win: Cypress.AUTWindow, initialUserState: any) {
win.indexedDB.deleteDatabase('redux')
const dbRequest = win.indexedDB.open('redux')
dbRequest.onsuccess = function () {
const db = dbRequest.result
const transaction = db.transaction('keyvaluepairs', 'readwrite')
const store = transaction.objectStore('keyvaluepairs')
store.put(
{
user: initialUserState,
},
'persist:interface'
)
}
dbRequest.onupgradeneeded = function () {
const db = dbRequest.result
db.createObjectStore('keyvaluepairs')
}
}

View File

@@ -25,7 +25,7 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
return new Response('Asset not found.', { status: 404 })
}
const fontData = await getFont()
const fontData = await getFont(origin)
return new ImageResponse(
(

View File

@@ -10,7 +10,7 @@ test.each(assetImageUrl)('assetImageUrl', async (url) => {
})
const invalidAssetImageUrl = [
'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/100000',
'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/10001',
'http://127.0.0.1:3000/api/image/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/44700',
]

View File

@@ -23,10 +23,10 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
)
if (!data) {
return new Response('Asset not found.', { status: 404 })
return new Response('Collection not found.', { status: 404 })
}
const [fontData, palette] = await Promise.all([getFont(), getColor(data.ogImage)])
const [fontData, palette] = await Promise.all([getFont(origin), getColor(data.ogImage)])
// Split name into words to wrap them since satori does not support inline text wrapping
const words = data.name.split(' ')

View File

@@ -25,12 +25,12 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
)
if (!data) {
return new Response('Asset not found.', { status: 404 })
return new Response('Token not found.', { status: 404 })
}
const [fontData, palette] = await Promise.all([getFont(), getColor(data.ogImage)])
const [fontData, palette] = await Promise.all([getFont(origin), getColor(data.ogImage, true)])
const networkLogo = getNetworkLogoUrl(networkName.toUpperCase())
const networkLogo = getNetworkLogoUrl(networkName.toUpperCase(), origin)
// Capitalize name such that each word starts with a capital letter
let words = data.name.split(' ')
@@ -123,7 +123,7 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
style={{
fontFamily: 'Inter',
fontSize: '72px',
lineHeight: '58px',
lineHeight: '72px',
marginLeft: '-5px',
marginTop: '24px',
}}
@@ -145,6 +145,10 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
fontSize: '168px',
lineHeight: '133px',
marginLeft: '-13px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
width: '100%',
}}
>
{data.symbol}

View File

@@ -13,7 +13,6 @@ const invalidTokenImageUrl = [
'http://127.0.0.1:3000/api/image/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb49',
'http://127.0.0.1:3000/api/image/tokens/ethereum',
'http://127.0.0.1:3000/api/image/tokens/ethereun',
'http://127.0.0.1:3000/api/image/tokens/ethereum/0x0',
'http://127.0.0.1:3000/api/image/tokens/potato/?potato=1',
]

View File

@@ -0,0 +1,38 @@
import { MetaTagInjector } from './metaTagInjector'
test('should append meta tag to element', () => {
const element = {
append: jest.fn(),
} as unknown as Element
const property = 'property'
const content = 'content'
const injector = new MetaTagInjector({
title: 'test',
url: 'testUrl',
image: 'testImage',
description: 'testDescription',
})
injector.append(element, property, content)
expect(element.append).toHaveBeenCalledWith(`<meta property="${property}" content="${content}"/>`, { html: true })
injector.element(element)
expect(element.append).toHaveBeenCalledWith(`<meta property="og:title" content="test"/>`, { html: true })
expect(element.append).toHaveBeenCalledWith(`<meta property="og:description" content="testDescription"/>`, {
html: true,
})
expect(element.append).toHaveBeenCalledWith(`<meta property="og:image" content="testImage"/>`, { html: true })
expect(element.append).toHaveBeenCalledWith(`<meta property="og:image:width" content="1200"/>`, { html: true })
expect(element.append).toHaveBeenCalledWith(`<meta property="og:image:height" content="630"/>`, { html: true })
expect(element.append).toHaveBeenCalledWith(`<meta property="og:image:alt" content="test"/>`, { html: true })
expect(element.append).toHaveBeenCalledWith(`<meta property="og:type" content="website"/>`, { html: true })
expect(element.append).toHaveBeenCalledWith(`<meta property="og:url" content="testUrl"/>`, { html: true })
expect(element.append).toHaveBeenCalledWith(`<meta property="twitter:card" content="summary_large_image"/>`, {
html: true,
})
expect(element.append).toHaveBeenCalledWith(`<meta property="twitter:title" content="test"/>`, { html: true })
expect(element.append).toHaveBeenCalledWith(`<meta property="twitter:image" content="testImage"/>`, { html: true })
expect(element.append).toHaveBeenCalledWith(`<meta property="twitter:image:alt" content="test"/>`, { html: true })
expect(element.append).toHaveBeenCalledTimes(13)
})

View File

@@ -4,7 +4,7 @@ module.exports = async function globalSetup() {
globalThis.servers = await setup({
command: `yarn start:cloud`,
port: 3000,
launchTimeout: 80000,
launchTimeout: 120000, // takes ~2m on CI
})
// Wait for wrangler to return a request before running tests
for (let i = 0; i < 3; i++) {

View File

@@ -1,6 +1,7 @@
{
"globalSetup": "<rootDir>/global-setup.ts",
"globalTeardown": "<rootDir>/global-teardown.ts",
"setupFilesAfterEnv": ["<rootDir>/setupAfterEnv.ts"],
"preset": "ts-jest",
"transform": {
"'^.+\\.(ts|tsx)?$'": "ts-jest",

View File

@@ -0,0 +1 @@
jest.retryTimes(3)

View File

@@ -19,6 +19,12 @@ test('should return the average color of a white PNG image', async () => {
expect(color).toEqual([255, 255, 255])
})
test('should return the average color of a white PNG image with whiteness dimmed', async () => {
const image = 'https://www.cac.cornell.edu/wiki/images/4/44/White_square.png'
const color = await getColor(image, true)
expect(color).toEqual(DEFAULT_COLOR)
})
test('should return the average color of a black JPG image', async () => {
const image =
'https://imageio.forbes.com/specials-images/imageserve/5ed6636cdd5d320006caf841/0x0.jpg?format=jpg&width=1200'

View File

@@ -4,7 +4,7 @@ import PNG from 'png-ts'
import { DEFAULT_COLOR, predefinedTokenColors } from '../constants'
export default async function getColor(image: string | undefined) {
export default async function getColor(image: string | undefined, checkDistance = false) {
if (!image) {
return DEFAULT_COLOR
}
@@ -17,13 +17,13 @@ export default async function getColor(image: string | undefined) {
const arrayBuffer = Buffer.from(buffer)
const type = data.headers.get('content-type') ?? ''
return getAverageColor(arrayBuffer, type)
return getAverageColor(arrayBuffer, type, checkDistance)
} catch (e) {
return DEFAULT_COLOR
}
}
function getAverageColor(arrayBuffer: Uint8Array, type?: string) {
function getAverageColor(arrayBuffer: Uint8Array, type: string, checkDistance: boolean) {
let pixels
switch (type) {
case 'image/png': {
@@ -63,5 +63,13 @@ function getAverageColor(arrayBuffer: Uint8Array, type?: string) {
g = Math.floor(g / (pixelCount - transparentPixels))
b = Math.floor(b / (pixelCount - transparentPixels))
if (checkDistance) {
const distance = Math.sqrt(Math.pow(r - 255, 2) + Math.pow(g - 255, 2) + Math.pow(b - 255, 2))
if (distance < 50) {
return DEFAULT_COLOR
}
}
return [r, g, b]
}

View File

@@ -1,6 +1,5 @@
const FONT_URL = 'https://fonts.gstatic.com/s/inter/v12/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fAZFhjQ.ttf'
export default async function getFont() {
const font = await fetch(FONT_URL)
export default async function getFont(origin: string) {
const url = origin + '/fonts/Inter-normal.var.ttf'
const font = await fetch(url)
return font.arrayBuffer()
}

View File

@@ -1,15 +1,15 @@
import { Chain } from '../../src/graphql/data/__generated__/types-and-hooks'
export default function getNetworkLogoUrl(network: string) {
export default function getNetworkLogoUrl(network: string, origin: string) {
switch (network) {
case Chain.Polygon:
return 'https://assets.coingecko.com/coins/images/4713/small/matic-token-icon.png?1624446912'
return origin + '/images/logos/Polygon_Logo.png'
case Chain.Arbitrum:
return 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/arbitrum/assets/0x912CE59144191C1204E64559FE8253a0e49E6548/logo.png'
return origin + '/images/logos/Arbitrum_Logo.png'
case Chain.Optimism:
return 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/optimism/assets/0x4200000000000000000000000000000000000042/logo.png'
return origin + '/images/logos/Optimism_Logo.png'
case Chain.Celo:
return 'https://assets.coingecko.com/coins/images/11090/small/InjXBNx9_400x400.jpg?1674707499'
return origin + '/images/logos/Celo_Logo.png'
default:
return ''
}

View File

@@ -33,6 +33,16 @@
"deduplicate": "yarn-deduplicate --strategy=highest",
"postinstall": "yarn patch-package"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"yarn.lock": [
"yarn deduplicate"
]
},
"jest": {
"collectCoverageFrom": [
"src/**/*.ts*",
@@ -84,7 +94,7 @@
"@types/array.prototype.flat": "^1.2.1",
"@types/array.prototype.flatmap": "^1.2.2",
"@types/d3": "^6.7.1",
"@types/jest": "^25.2.1",
"@types/jest": "^27.0.1",
"@types/lingui__core": "^2.7.1",
"@types/lingui__macro": "^2.7.4",
"@types/lingui__react": "^2.8.3",
@@ -95,6 +105,7 @@
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/react-redux": "^7.1.24",
"@types/react-router-dom": "^5.3.3",
"@types/react-table": "^7.7.12",
"@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/react-window": "^1.8.2",
@@ -121,6 +132,7 @@
"eslint-plugin-import": "^2.27",
"eslint-plugin-rulesdir": "^0.2.2",
"hardhat": "^2.14.0",
"husky": "^8.0.3",
"jest": "^29.6.1",
"jest-dev-server": "^9.0.0",
"jest-extended": "^4.0.1",
@@ -128,6 +140,7 @@
"jest-fetch-mock": "^3.0.3",
"jest-styled-components": "^7.0.8",
"jpeg-js": "^0.4.4",
"lint-staged": "^14.0.0",
"mini-css-extract-plugin": "^2.7.6",
"patch-package": "^7.0.0",
"path-browserify": "^1.0.1",
@@ -143,6 +156,7 @@
"terser-webpack-plugin": "^5.3.9",
"ts-jest": "^29.1.1",
"ts-transform-graphql-tag": "^0.2.1",
"tsafe": "^1.6.4",
"typechain": "^5.0.0",
"typescript": "^4.9.4",
"webpack": "^5.88.2",
@@ -177,8 +191,7 @@
"@sentry/types": "^7.45.0",
"@types/react-window-infinite-loader": "^1.0.6",
"@uniswap/analytics": "^1.4.0",
"@uniswap/analytics-events": "^2.15.0",
"@uniswap/conedison": "^1.8.0",
"@uniswap/analytics-events": "^2.17.0",
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "^1.0.1",
@@ -188,7 +201,7 @@
"@uniswap/sdk-core": "^4.0.3",
"@uniswap/smart-order-router": "^3.15.0",
"@uniswap/token-lists": "^1.0.0-beta.33",
"@uniswap/uniswapx-sdk": "^1.2.0",
"@uniswap/uniswapx-sdk": "^1.3.0",
"@uniswap/universal-router-sdk": "^1.5.6",
"@uniswap/v2-core": "^1.0.1",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
@@ -207,16 +220,16 @@
"@visx/react-spring": "^2.12.2",
"@visx/responsive": "^2.10.0",
"@visx/shape": "^2.11.1",
"@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.1",
"@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-v2": "^8.3.7",
"@web3-react/coinbase-wallet": "^8.2.2",
"@web3-react/core": "^8.2.2",
"@web3-react/eip1193": "^8.2.2",
"@web3-react/empty": "^8.2.2",
"@web3-react/gnosis-safe": "^8.2.3",
"@web3-react/metamask": "^8.2.3",
"@web3-react/network": "^8.2.2",
"@web3-react/types": "^8.2.2",
"@web3-react/url": "^8.2.2",
"@web3-react/walletconnect-v2": "^8.5.0",
"ajv": "^8.11.0",
"ajv-formats": "^2.1.1",
"array.prototype.flat": "^1.2.4",
@@ -235,6 +248,7 @@
"inter-ui": "^3.13.1",
"jotai": "^1.3.7",
"jsbi": "^3.1.4",
"localforage": "^1.10.0",
"make-plural": "^7.0.0",
"ms": "^2.1.3",
"multicodec": "^3.0.1",
@@ -265,7 +279,7 @@
"react-window-infinite-loader": "^1.0.8",
"rebass": "^4.0.7",
"redux": "^4.1.2",
"redux-localstorage-simple": "^2.3.1",
"redux-persist": "^6.0.0",
"statsig-react": "^1.22.0",
"styled-components": "^5.3.5",
"tiny-invariant": "^1.2.0",

View File

@@ -23,7 +23,7 @@
"namespace": "android_app",
"package_name": "com.uniswap.dev",
"sha256_cert_fingerprints":
["A8:A7:D4:DE:46:8E:BE:F6:DE:3B:62:2B:A7:26:60:F2:9A:4C:CD:AF:A6:96:C9:E5:7C:91:68:A1:29:2A:48:D3", "02:E6:1C:76:8C:75:C3:78:C8:8C:FE:7B:2E:8F:4B:E1:FA:47:F2:F6:1A:DB:57:69:4A:41:99:C6:71:2C:AB:E3", "FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C"]
["5A:6D:23:50:2F:1E:0D:01:DC:96:65:F3:3A:18:4C:4C:8C:67:E0:09:99:9B:B1:9B:BF:44:99:D0:D1:D0:FC:5E", "02:E6:1C:76:8C:75:C3:78:C8:8C:FE:7B:2E:8F:4B:E1:FA:47:F2:F6:1A:DB:57:69:4A:41:99:C6:71:2C:AB:E3", "FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C"]
}
}
]

View File

@@ -22,6 +22,22 @@
{
"#": "/address/*",
"comment": "Wallet address"
},
{
"/": "/nfts/asset/*",
"comment": "NFT Item"
},
{
"/": "/nfts/collection/*",
"comment": "NFT Collection"
},
{
"/": "/tokens/*",
"comment": "Token address"
},
{
"/": "/address/*",
"comment": "Wallet address"
}
]
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -6,7 +6,14 @@ import {
import { atomWithStorage, useAtomValue } from 'jotai/utils'
import { memo } from 'react'
export { getDeviceId, initializeAnalytics, OriginApplication, user, useTrace } from '@uniswap/analytics'
export {
type ITraceContext,
getDeviceId,
initializeAnalytics,
OriginApplication,
user,
useTrace,
} from '@uniswap/analytics'
const allowAnalyticsAtomKey = 'allow_analytics'
export const allowAnalyticsAtom = atomWithStorage<boolean>(allowAnalyticsAtomKey, true)

View File

@@ -5,6 +5,7 @@ import { useCallback, useEffect, useState } from 'react'
import styled from 'styled-components'
import AuthenticatedHeader from './AuthenticatedHeader'
import LanguageMenu from './LanguageMenu'
import SettingsMenu from './SettingsMenu'
const DefaultMenuWrap = styled(Column)`
@@ -15,6 +16,7 @@ const DefaultMenuWrap = styled(Column)`
enum MenuState {
DEFAULT,
SETTINGS,
LANGUAGE_SETTINGS,
}
function DefaultMenu({ drawerOpen }: { drawerOpen: boolean }) {
@@ -24,9 +26,10 @@ function DefaultMenu({ drawerOpen }: { drawerOpen: boolean }) {
const [menu, setMenu] = useState<MenuState>(MenuState.DEFAULT)
const openSettings = useCallback(() => setMenu(MenuState.SETTINGS), [])
const closeSettings = useCallback(() => setMenu(MenuState.DEFAULT), [])
const openLanguageSettings = useCallback(() => setMenu(MenuState.LANGUAGE_SETTINGS), [])
useEffect(() => {
if (!drawerOpen && menu === MenuState.SETTINGS) {
if (!drawerOpen && menu !== MenuState.DEFAULT) {
// wait for the drawer to close before resetting the menu
const timer = setTimeout(() => {
closeSettings()
@@ -44,7 +47,10 @@ function DefaultMenu({ drawerOpen }: { drawerOpen: boolean }) {
) : (
<WalletModal openSettings={openSettings} />
))}
{menu === MenuState.SETTINGS && <SettingsMenu onClose={closeSettings} />}
{menu === MenuState.SETTINGS && (
<SettingsMenu onClose={closeSettings} openLanguageSettings={openLanguageSettings} />
)}
{menu === MenuState.LANGUAGE_SETTINGS && <LanguageMenu onClose={openSettings} />}
</DefaultMenuWrap>
)
}

View File

@@ -0,0 +1,56 @@
import { Trans } from '@lingui/macro'
import { LOCALE_LABEL, SUPPORTED_LOCALES, SupportedLocale } from 'constants/locales'
import { useActiveLocale } from 'hooks/useActiveLocale'
import { useLocationLinkProps } from 'hooks/useLocationLinkProps'
import { Check } from 'react-feather'
import { Link } from 'react-router-dom'
import styled, { useTheme } from 'styled-components'
import { ClickableStyle, ThemedText } from 'theme'
import { SlideOutMenu } from './SlideOutMenu'
const InternalLinkMenuItem = styled(Link)`
${ClickableStyle}
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
padding: 12px 0;
justify-content: space-between;
text-decoration: none;
color: ${({ theme }) => theme.textPrimary};
`
function LanguageMenuItem({ locale, isActive }: { locale: SupportedLocale; isActive: boolean }) {
const { to, onClick } = useLocationLinkProps(locale)
const theme = useTheme()
if (!to) return null
return (
<InternalLinkMenuItem onClick={onClick} to={to}>
<ThemedText.BodySmall data-testid="wallet-language-item">{LOCALE_LABEL[locale]}</ThemedText.BodySmall>
{isActive && <Check color={theme.accentActive} opacity={1} size={20} />}
</InternalLinkMenuItem>
)
}
export function LanguageMenuItems() {
const activeLocale = useActiveLocale()
return (
<>
{SUPPORTED_LOCALES.map((locale) => (
<LanguageMenuItem locale={locale} isActive={activeLocale === locale} key={locale} />
))}
</>
)
}
export default function LanguageMenu({ onClose }: { onClose: () => void }) {
return (
<SlideOutMenu title={<Trans>Language</Trans>} onClose={onClose}>
<LanguageMenuItems />
</SlideOutMenu>
)
}

View File

@@ -249,7 +249,7 @@ export function OffchainActivityModal() {
return (
<Modal isOpen={!!syncedSelectedOrder?.modalOpen} onDismiss={reset}>
<Wrapper>
<Wrapper data-testid="offchain-activity-modal">
<StyledXButton onClick={reset} />
{syncedSelectedOrder && <OrderContent order={syncedSelectedOrder} />}
</Wrapper>

View File

@@ -86,7 +86,7 @@ export function useAllActivities(account: string) {
return { loading, activities: combinedActivities, refetch }
}
export function useHasPendingActivity() {
export function usePendingActivity() {
const pendingTransactions = usePendingTransactions()
const pendingOrders = usePendingOrders()

View File

@@ -119,7 +119,7 @@ export function ActivityTab({ account }: { account: string }) {
<ThemedText.SubHeader color="textSecondary" marginLeft="16px">
{activityGroup.title}
</ThemedText.SubHeader>
<Column>
<Column data-testid="activity-content">
{activityGroup.transactions.map((activity) => (
<ActivityRow key={activity.hash} activity={activity} />
))}

View File

@@ -3,7 +3,7 @@ import { TraceEvent } from 'analytics'
import { useCachedPortfolioBalancesQuery } from 'components/AccountDrawer/PrefetchBalancesWrapper'
import Row from 'components/Row'
import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart'
import { PortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks'
import { TokenBalance } from 'graphql/data/__generated__/types-and-hooks'
import { getTokenDetailsURL, gqlToCurrency, logSentryErrorForUnsupportedChain } from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils'
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
@@ -12,6 +12,7 @@ import { useNavigate } from 'react-router-dom'
import styled from 'styled-components'
import { EllipsisStyle, ThemedText } from 'theme'
import { formatNumber, NumberType } from 'utils/formatNumbers'
import { splitHiddenTokens } from 'utils/splitHiddenTokens'
import { useToggleAccountDrawer } from '../..'
import { PortfolioArrow } from '../../AuthenticatedHeader'
@@ -20,12 +21,6 @@ import { ExpandoRow } from '../ExpandoRow'
import { PortfolioLogo } from '../PortfolioLogo'
import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
const HIDE_SMALL_USD_BALANCES_THRESHOLD = 1
function meetsThreshold(tokenBalance: TokenBalance, hideSmallBalances: boolean) {
return !hideSmallBalances || (tokenBalance.denominatedValue?.value ?? 0) > HIDE_SMALL_USD_BALANCES_THRESHOLD
}
export default function Tokens({ account }: { account: string }) {
const toggleWalletDrawer = useToggleAccountDrawer()
const hideSmallBalances = useAtomValue(hideSmallBalancesAtom)
@@ -33,27 +28,18 @@ export default function Tokens({ account }: { account: string }) {
const { data } = useCachedPortfolioBalancesQuery({ account })
const visibleTokens = useMemo(() => {
return !hideSmallBalances
? data?.portfolios?.[0].tokenBalances ?? []
: data?.portfolios?.[0].tokenBalances?.filter((tokenBalance) =>
meetsThreshold(tokenBalance, hideSmallBalances)
) ?? []
}, [data?.portfolios, hideSmallBalances])
const tokenBalances = data?.portfolios?.[0].tokenBalances as TokenBalance[] | undefined
const hiddenTokens = useMemo(() => {
return !hideSmallBalances
? []
: data?.portfolios?.[0].tokenBalances?.filter(
(tokenBalance) => !meetsThreshold(tokenBalance, hideSmallBalances)
) ?? []
}, [data?.portfolios, hideSmallBalances])
const { visibleTokens, hiddenTokens } = useMemo(
() => splitHiddenTokens(tokenBalances ?? [], { hideSmallBalances }),
[hideSmallBalances, tokenBalances]
)
if (!data) {
return <PortfolioSkeleton />
}
if (data?.portfolios?.[0].tokenBalances?.length === 0) {
if (tokenBalances?.length === 0) {
// TODO: consider launching moonpay here instead of just closing the drawer
return <EmptyWalletModule type="token" onNavigateClick={toggleWalletDrawer} />
}
@@ -64,10 +50,7 @@ export default function Tokens({ account }: { account: string }) {
<PortfolioTabWrapper>
{visibleTokens.map(
(tokenBalance) =>
tokenBalance.token &&
meetsThreshold(tokenBalance, hideSmallBalances) && (
<TokenRow key={tokenBalance.id} {...tokenBalance} token={tokenBalance.token} />
)
tokenBalance.token && <TokenRow key={tokenBalance.id} {...tokenBalance} token={tokenBalance.token} />
)}
<ExpandoRow isExpanded={showHiddenTokens} toggle={toggleHiddenTokens} numItems={hiddenTokens.length}>
{hiddenTokens.map(
@@ -86,10 +69,6 @@ const TokenNameText = styled(ThemedText.SubHeader)`
${EllipsisStyle}
`
type TokenBalance = NonNullable<
NonNullable<NonNullable<PortfolioBalancesQuery['portfolios']>[number]>['tokenBalances']
>[number]
type PortfolioToken = NonNullable<TokenBalance['token']>
function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: TokenBalance & { token: PortfolioToken }) {

View File

@@ -11,7 +11,7 @@ import styled, { useTheme } from 'styled-components'
import { BREAKPOINTS, ThemedText } from 'theme'
import { ActivityTab } from './Activity'
import { useHasPendingActivity } from './Activity/hooks'
import { usePendingActivity } from './Activity/hooks'
import NFTs from './NFTs'
import Pools from './Pools'
import { PortfolioRowWrapper } from './PortfolioRow'
@@ -103,7 +103,7 @@ export default function MiniPortfolio({ account }: { account: string }) {
const { component: Page, key: currentKey } = Pages[currentPage]
const { hasPendingActivity } = useHasPendingActivity()
const { hasPendingActivity } = usePendingActivity()
useEffect(() => {
if (hasPendingActivity && currentKey !== 'activity') setActivityUnread(true)

View File

@@ -3,36 +3,16 @@ import { usePortfolioBalancesLazyQuery, usePortfolioBalancesQuery } from 'graphq
import { GQL_MAINNET_CHAINS } from 'graphql/data/util'
import usePrevious from 'hooks/usePrevious'
import { atom, useAtom } from 'jotai'
import { PropsWithChildren, useCallback, useEffect, useMemo } from 'react'
import { useAllTransactions } from 'state/transactions/hooks'
import { TransactionDetails } from 'state/transactions/types'
import { PropsWithChildren, useCallback, useEffect } from 'react'
const isTxPending = (tx: TransactionDetails) => !tx.receipt
function wasPending(previousTxs: { [hash: string]: TransactionDetails | undefined }, current: TransactionDetails) {
const previousTx = previousTxs[current.hash]
return previousTx && isTxPending(previousTx)
}
import { usePendingActivity } from './MiniPortfolio/Activity/hooks'
function useHasUpdatedTx(account: string | undefined) {
// TODO: consider monitoring tx's on chains other than the wallet's current chain
const currentChainTxs = useAllTransactions()
/** Returns true if the number of pending activities has decreased */
function useHasUpdatedTx() {
const { pendingActivityCount } = usePendingActivity()
const prevPendingActivityCount = usePrevious(pendingActivityCount)
const pendingTxs = useMemo(() => {
return Object.entries(currentChainTxs).reduce((acc: { [hash: string]: TransactionDetails }, [hash, tx]) => {
if (!tx.receipt) acc[hash] = tx
return acc
}, {})
}, [currentChainTxs])
const previousPendingTxs = usePrevious(pendingTxs)
return useMemo(() => {
if (!previousPendingTxs || !account) return false
return Object.values(currentChainTxs).some(
(tx) => tx.from === account && !isTxPending(tx) && wasPending(previousPendingTxs, tx),
[currentChainTxs, previousPendingTxs]
)
}, [account, currentChainTxs, previousPendingTxs])
return !!prevPendingActivityCount && pendingActivityCount < prevPendingActivityCount
}
export function useCachedPortfolioBalancesQuery({ account }: { account?: string }) {
@@ -65,7 +45,7 @@ export default function PrefetchBalancesWrapper({
const prevAccount = usePrevious(account)
const hasUpdatedTx = useHasUpdatedTx(account)
const hasUpdatedTx = useHasUpdatedTx()
// Listens for account changes & recently updated transactions to keep portfolio balances fresh in apollo cache
useEffect(() => {
const accountChanged = prevAccount !== undefined && prevAccount !== account

View File

@@ -1,46 +1,27 @@
import { Trans } from '@lingui/macro'
import { LOCALE_LABEL, SUPPORTED_LOCALES, SupportedLocale } from 'constants/locales'
import Column from 'components/Column'
import Row from 'components/Row'
import { LOCALE_LABEL } from 'constants/locales'
import { useCurrencyConversionFlagEnabled } from 'featureFlags/flags/currencyConversion'
import { useActiveLocale } from 'hooks/useActiveLocale'
import { useLocationLinkProps } from 'hooks/useLocationLinkProps'
import { Check } from 'react-feather'
import { Link } from 'react-router-dom'
import styled, { useTheme } from 'styled-components'
import { ReactNode } from 'react'
import { ChevronRight } from 'react-feather'
import styled from 'styled-components'
import { ClickableStyle, ThemedText } from 'theme'
import ThemeToggle from 'theme/components/ThemeToggle'
import { AnalyticsToggle } from './AnalyticsToggle'
import { GitVersionRow } from './GitVersionRow'
import { LanguageMenuItems } from './LanguageMenu'
import { SlideOutMenu } from './SlideOutMenu'
import { SmallBalanceToggle } from './SmallBalanceToggle'
import { TestnetsToggle } from './TestnetsToggle'
const InternalLinkMenuItem = styled(Link)`
${ClickableStyle}
flex: 1;
color: ${({ theme }) => theme.textTertiary};
display: flex;
flex-direction: row;
align-items: center;
padding: 12px 0;
const Container = styled(Column)`
height: 100%;
justify-content: space-between;
text-decoration: none;
color: ${({ theme }) => theme.textPrimary};
`
function LanguageMenuItem({ locale, isActive }: { locale: SupportedLocale; isActive: boolean }) {
const { to, onClick } = useLocationLinkProps(locale)
const theme = useTheme()
if (!to) return null
return (
<InternalLinkMenuItem onClick={onClick} to={to}>
<ThemedText.BodySmall data-testid="wallet-language-item">{LOCALE_LABEL[locale]}</ThemedText.BodySmall>
{isActive && <Check color={theme.accentActive} opacity={1} size={20} />}
</InternalLinkMenuItem>
)
}
const SectionTitle = styled(ThemedText.SubHeader)`
color: ${({ theme }) => theme.textSecondary};
padding-bottom: 24px;
@@ -53,28 +34,81 @@ const ToggleWrapper = styled.div`
margin-bottom: 24px;
`
export default function SettingsMenu({ onClose }: { onClose: () => void }) {
const SettingsButtonWrapper = styled(Row)`
${ClickableStyle}
`
const StyledChevron = styled(ChevronRight)`
color: ${({ theme }) => theme.textSecondary};
`
const LanguageLabel = styled(Row)`
white-space: nowrap;
`
const SettingsButton = ({
title,
currentState,
onClick,
testId,
}: {
title: ReactNode
currentState: ReactNode
onClick: () => void
testId?: string
}) => (
<SettingsButtonWrapper data-testid={testId} align="center" justify="space-between" onClick={onClick}>
<ThemedText.SubHeaderSmall color="textPrimary">{title}</ThemedText.SubHeaderSmall>
<LanguageLabel gap="xs" align="center" width="min-content">
<ThemedText.LabelMedium color="textPrimary">{currentState}</ThemedText.LabelMedium>
<StyledChevron size={20} />
</LanguageLabel>
</SettingsButtonWrapper>
)
export default function SettingsMenu({
onClose,
openLanguageSettings,
}: {
onClose: () => void
openLanguageSettings: () => void
}) {
const currencyConversionEnabled = useCurrencyConversionFlagEnabled()
const activeLocale = useActiveLocale()
return (
<SlideOutMenu title={<Trans>Settings</Trans>} onClose={onClose}>
<SectionTitle>
<Trans>Preferences</Trans>
</SectionTitle>
<ToggleWrapper>
<ThemeToggle />
<SmallBalanceToggle />
<AnalyticsToggle />
<TestnetsToggle />
</ToggleWrapper>
<Container>
<div>
<SectionTitle data-testid="wallet-header">
<Trans>Preferences</Trans>
</SectionTitle>
<ToggleWrapper>
<ThemeToggle />
<SmallBalanceToggle />
<AnalyticsToggle />
<TestnetsToggle />
</ToggleWrapper>
{!currencyConversionEnabled && (
<>
<SectionTitle data-testid="wallet-header">
<Trans>Language</Trans>
</SectionTitle>
<LanguageMenuItems />
</>
)}
<SectionTitle data-testid="wallet-header">
<Trans>Language</Trans>
</SectionTitle>
{SUPPORTED_LOCALES.map((locale) => (
<LanguageMenuItem locale={locale} isActive={activeLocale === locale} key={locale} />
))}
<GitVersionRow />
{currencyConversionEnabled && (
<SettingsButton
title={<Trans>Language</Trans>}
currentState={LOCALE_LABEL[activeLocale]}
onClick={openLanguageSettings}
testId="language-settings-button"
/>
)}
</div>
<GitVersionRow />
</Container>
</SlideOutMenu>
)
}

View File

@@ -1,9 +1,10 @@
import Column from 'components/Column'
import { ScrollBarStyles } from 'components/Common'
import { ArrowLeft } from 'react-feather'
import styled from 'styled-components'
import { ClickableStyle, ThemedText } from 'theme'
const Menu = styled.div`
const Menu = styled(Column)`
width: 100%;
overflow: auto;
margin-top: 4px;

View File

@@ -2,7 +2,6 @@ import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'fe
import { useCurrencyConversionFlag } from 'featureFlags/flags/currencyConversion'
import { useForceUniswapXOnFlag } from 'featureFlags/flags/forceUniswapXOn'
import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx'
import { useRoutingAPIForPriceFlag } from 'featureFlags/flags/priceRoutingApi'
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
import { UniswapXVariant, useUniswapXFlag } from 'featureFlags/flags/uniswapx'
import { useUniswapXEthOutputFlag } from 'featureFlags/flags/uniswapXEthOutput'
@@ -231,12 +230,6 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.uniswapXEthOutputEnabled}
label="Enable eth output for UniswapX orders"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useRoutingAPIForPriceFlag()}
featureFlag={FeatureFlag.routingAPIPrice}
label="Use the routing-api v2 for price fetches"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useCurrencyConversionFlag()}

View File

@@ -1,7 +1,6 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { useCallback, useEffect, useState } from 'react'
import { useHref } from 'react-router-dom'
import { useCloseModal, useModalIsOpen } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import styled, { useTheme } from 'styled-components'
@@ -80,8 +79,6 @@ export default function FiatOnrampModal() {
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
const swapUrl = useHref('/swap')
const fetchSignedIframeUrl = useCallback(async () => {
if (!account) {
setError('Please connect an account before making a purchase.')
@@ -101,7 +98,7 @@ export default function FiatOnrampModal() {
theme: isDarkMode ? 'dark' : 'light',
colorCode: theme.accentAction,
defaultCurrencyCode: 'eth',
redirectUrl: swapUrl,
redirectUrl: 'https://app.uniswap.org/#/swap',
walletAddresses: JSON.stringify(
MOONPAY_SUPPORTED_CURRENCY_CODES.reduce(
(acc, currencyCode) => ({
@@ -121,7 +118,7 @@ export default function FiatOnrampModal() {
} finally {
setLoading(false)
}
}, [account, isDarkMode, swapUrl, theme.accentAction])
}, [account, isDarkMode, theme.accentAction])
useEffect(() => {
fetchSignedIframeUrl()

View File

@@ -35,7 +35,8 @@ export const LoadingRows = styled.div`
export const loadingOpacityMixin = css<{ $loading: boolean }>`
filter: ${({ $loading }) => ($loading ? 'grayscale(1)' : 'none')};
opacity: ${({ $loading }) => ($loading ? '0.4' : '1')};
transition: opacity 0.2s ease-in-out;
transition: ${({ $loading, theme }) =>
$loading ? 'none' : `opacity ${theme.transition.duration.medium} ${theme.transition.timing.inOut}`};
`
export const LoadingOpacityContainer = styled.div<{ $loading: boolean }>`

View File

@@ -362,9 +362,6 @@ function ComingSoonText({ chainId }: { chainId: ChainId }) {
return <Trans>Coming soon: search and explore tokens on BNB Chain</Trans>
case ChainId.AVALANCHE:
return <Trans>Coming soon: search and explore tokens on Avalanche Chain</Trans>
case ChainId.BASE:
case ChainId.BASE_GOERLI:
return <Trans>Coming soon: search and explore tokens on Base</Trans>
default:
return null
}

View File

@@ -160,7 +160,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
return (
<Link
data-testid={`searchbar-token-row-${token.symbol}`}
data-testid={`searchbar-token-row-${token.chain}-${token.address ?? 'NATIVE'}`}
to={tokenDetailsPath}
onClick={handleClick}
onMouseEnter={() => !isHovered && setHoveredIndex(index)}
@@ -174,7 +174,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
symbol={token.symbol}
size="36px"
backupImg={token.project?.logoUrl}
style={{ paddingRight: '8px' }}
style={{ marginRight: '8px' }}
/>
<Column className={styles.suggestionPrimaryContainer}>
<Row gap="4" width="full">

View File

@@ -60,8 +60,9 @@ const baseMenuItem = style([
subhead,
sprinkles({
paddingY: '8',
paddingX: '14',
paddingX: { sm: '6', md: '14' },
marginY: '4',
marginX: { sm: '4', md: '0' },
borderRadius: '12',
transition: '250',
height: 'min',
@@ -69,7 +70,6 @@ const baseMenuItem = style([
textAlign: 'center',
display: 'flex',
alignItems: 'center',
gap: '4',
}),
{
lineHeight: '24px',

View File

@@ -21,12 +21,12 @@ export { Gradient as UniswapXGradient }
// Uniswap X SVG icon with gradient, copied from Figma.
// In order for gradient to work, we must give its definition a unique ID that does not collide
// with other occurences of this component on the page.
export const UniswapXRouterIcon = () => {
export const UniswapXRouterIcon = ({ testId }: { testId?: string }) => {
const componentIdRef = useRef(uuid())
const componentId = `AutoRouterIconGradient${componentIdRef.current}`
return (
<svg width="10" height="14" viewBox="0 0 10 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="10" height="14" viewBox="0 0 10 14" fill="none" xmlns="http://www.w3.org/2000/svg" data-testid={testId}>
<defs>
<linearGradient
id={componentId}
@@ -50,12 +50,18 @@ export const UniswapXRouterIcon = () => {
export type UnswapXRouterLabelProps = BoxProps & {
disableTextGradient?: boolean
testId?: string
}
export default function UniswapXRouterLabel({ children, disableTextGradient, ...rest }: UnswapXRouterLabelProps) {
export default function UniswapXRouterLabel({
children,
disableTextGradient,
testId,
...rest
}: UnswapXRouterLabelProps) {
return (
<Row gap="xs" width="auto" {...rest} style={{ display: 'inline-flex', ...rest.style }}>
<UniswapXRouterIcon />
<UniswapXRouterIcon testId={testId} />
{disableTextGradient ? children : <Gradient>{children}</Gradient>}
</Row>
)

View File

@@ -0,0 +1,47 @@
import { TimePeriod } from 'graphql/data/util'
import { render } from 'test-utils/render'
import { PriceChart } from './PriceChart'
jest.mock('components/Charts/AnimatedInLineChart', () => ({
__esModule: true,
default: jest.fn(() => null),
}))
jest.mock('components/Charts/FadeInLineChart', () => ({
__esModule: true,
default: jest.fn(() => null),
}))
describe('PriceChart', () => {
it('renders correctly with all prices filled', () => {
const mockPrices = Array.from({ length: 13 }, (_, i) => ({
value: 1,
timestamp: i * 3600,
}))
const { asFragment } = render(
<PriceChart prices={mockPrices} width={780} height={436} timePeriod={TimePeriod.HOUR} />
)
expect(asFragment()).toMatchSnapshot()
expect(asFragment().textContent).toContain('$1.00')
expect(asFragment().textContent).toContain('0.00%')
})
it('renders correctly with some prices filled', () => {
const mockPrices = Array.from({ length: 13 }, (_, i) => ({
value: i < 10 ? 1 : 0,
timestamp: i * 3600,
}))
const { asFragment } = render(
<PriceChart prices={mockPrices} width={780} height={436} timePeriod={TimePeriod.HOUR} />
)
expect(asFragment()).toMatchSnapshot()
expect(asFragment().textContent).toContain('$1.00')
expect(asFragment().textContent).toContain('0.00%')
})
it('renders correctly with no prices filled', () => {
const { asFragment } = render(<PriceChart prices={[]} width={780} height={436} timePeriod={TimePeriod.HOUR} />)
expect(asFragment()).toMatchSnapshot()
expect(asFragment().textContent).toContain('Price Unavailable')
})
})

View File

@@ -6,12 +6,13 @@ import { GlyphCircle } from '@visx/glyph'
import { Line } from '@visx/shape'
import AnimatedInLineChart from 'components/Charts/AnimatedInLineChart'
import FadedInLineChart from 'components/Charts/FadeInLineChart'
import { MouseoverTooltip } from 'components/Tooltip'
import { bisect, curveCardinal, NumberValue, scaleLinear, timeDay, timeHour, timeMinute, timeMonth } from 'd3'
import { PricePoint } from 'graphql/data/util'
import { TimePeriod } from 'graphql/data/util'
import { useActiveLocale } from 'hooks/useActiveLocale'
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import { ArrowDownRight, ArrowUpRight, TrendingUp } from 'react-feather'
import { ArrowDownRight, ArrowUpRight, Info, TrendingUp } from 'react-feather'
import styled, { useTheme } from 'styled-components'
import { ThemedText } from 'theme'
import { textFadeIn } from 'theme/styles'
@@ -41,18 +42,33 @@ const StyledDownArrow = styled(ArrowDownRight)`
color: ${({ theme }) => theme.accentFailure};
`
const DefaultUpArrow = styled(ArrowUpRight)`
color: ${({ theme }) => theme.textTertiary};
`
const DefaultDownArrow = styled(ArrowDownRight)`
color: ${({ theme }) => theme.textTertiary};
`
function calculateDelta(start: number, current: number) {
return (current / start - 1) * 100
}
export function getDeltaArrow(delta: number | null | undefined, iconSize = 20) {
export function getDeltaArrow(delta: number | null | undefined, iconSize = 20, styled = true) {
// Null-check not including zero
if (delta === null || delta === undefined) {
return null
} else if (Math.sign(delta) < 0) {
return <StyledDownArrow size={iconSize} key="arrow-down" aria-label="down" />
return styled ? (
<StyledDownArrow size={iconSize} key="arrow-down" aria-label="down" />
) : (
<DefaultDownArrow size={iconSize} key="arrow-down" aria-label="down" />
)
}
return <StyledUpArrow size={iconSize} key="arrow-up" aria-label="up" />
return styled ? (
<StyledUpArrow size={iconSize} key="arrow-up" aria-label="up" />
) : (
<DefaultUpArrow size={iconSize} key="arrow-up" aria-label="up" />
)
}
export function formatDelta(delta: number | null | undefined) {
@@ -84,6 +100,10 @@ const MissingPrice = styled(TokenPrice)`
color: ${({ theme }) => theme.textTertiary};
`
const OutdatedContainer = styled.div`
color: ${({ theme }) => theme.textSecondary};
`
const DeltaContainer = styled.div`
height: 16px;
display: flex;
@@ -95,6 +115,13 @@ export const ArrowCell = styled.div`
display: flex;
`
const OutdatedPriceContainer = styled.div`
display: flex;
gap: 6px;
font-size: 24px;
line-height: 44px;
`
function fixChart(prices: PricePoint[] | undefined | null) {
if (!prices) return { prices: null, blanks: [] }
@@ -148,6 +175,34 @@ export function PriceChart({ width, height, prices: originalPrices, timePeriod }
)
) : null
const tooltipMessage = (
<>
<Trans>This price may not be up-to-date due to low trading volume.</Trans>
</>
)
//get the last non-zero price point
const lastPrice = useMemo(() => {
if (!prices) return DATA_EMPTY
for (let i = prices.length - 1; i >= 0; i--) {
if (prices[i].value !== 0) return prices[i]
}
return DATA_EMPTY
}, [prices])
//get the first non-zero price point
const firstPrice = useMemo(() => {
if (!prices) return DATA_EMPTY
for (let i = 0; i < prices.length; i++) {
if (prices[i].value !== 0) return prices[i]
}
return DATA_EMPTY
}, [prices])
const totalDelta = calculateDelta(firstPrice.value, lastPrice.value)
const formattedTotalDelta = formatDelta(totalDelta)
const defaultArrow = getDeltaArrow(totalDelta, 20, false)
// first price point on the x-axis of the current time period's chart
const startingPrice = originalPrices?.[0] ?? DATA_EMPTY
// last price point on the x-axis of the current time period's chart
@@ -302,6 +357,19 @@ export function PriceChart({ width, height, prices: originalPrices, timePeriod }
<ArrowCell>{arrow}</ArrowCell>
</DeltaContainer>
</>
) : lastPrice.value ? (
<OutdatedContainer>
<OutdatedPriceContainer>
<TokenPrice>{formatUSDPrice(lastPrice.value)}</TokenPrice>
<MouseoverTooltip text={tooltipMessage}>
<Info size={16} />
</MouseoverTooltip>
</OutdatedPriceContainer>
<DeltaContainer>
{formattedTotalDelta}
<ArrowCell>{defaultArrow}</ArrowCell>
</DeltaContainer>
</OutdatedContainer>
) : (
<>
<MissingPrice>Price Unavailable</MissingPrice>

View File

@@ -0,0 +1,844 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PriceChart renders correctly with all prices filled 1`] = `
<DocumentFragment>
.c4 {
color: #40B66B;
}
.c0 {
position: absolute;
-webkit-animation: iAjNNh 125ms ease-in;
animation: iAjNNh 125ms ease-in;
-webkit-animation-duration: 250ms;
animation-duration: 250ms;
}
.c1 {
font-size: 36px;
line-height: 44px;
}
.c2 {
height: 16px;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-top: 4px;
}
.c3 {
padding-right: 3px;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
<div
class="c0"
data-cy="chart-header"
>
<span
class="c1"
>
$1.00
</span>
<div
class="c2"
>
0.00%
<div
class="c3"
>
<svg
aria-label="up"
class="c4"
fill="none"
height="20"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<line
x1="7"
x2="17"
y1="17"
y2="7"
/>
<polyline
points="7 7 17 7 17 17"
/>
</svg>
</div>
</div>
</div>
<svg
data-cy="price-chart"
height="392"
style="min-width: 100%;"
width="780"
>
<g
class="visx-group visx-axis visx-axis-bottom"
transform="translate(0, 391)"
>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="0"
y="18"
>
<tspan
dy="0em"
x="0"
>
0
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="90.27777777777777"
y="18"
>
<tspan
dy="0em"
x="90.27777777777777"
>
5,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="180.55555555555554"
y="18"
>
<tspan
dy="0em"
x="180.55555555555554"
>
10,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="270.8333333333333"
y="18"
>
<tspan
dy="0em"
x="270.8333333333333"
>
15,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="361.1111111111111"
y="18"
>
<tspan
dy="0em"
x="361.1111111111111"
>
20,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="451.3888888888889"
y="18"
>
<tspan
dy="0em"
x="451.3888888888889"
>
25,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="541.6666666666666"
y="18"
>
<tspan
dy="0em"
x="541.6666666666666"
>
30,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="631.9444444444445"
y="18"
>
<tspan
dy="0em"
x="631.9444444444445"
>
35,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="722.2222222222222"
y="18"
>
<tspan
dy="0em"
x="722.2222222222222"
>
40,000
</tspan>
</text>
</svg>
</g>
</g>
<rect
fill="transparent"
height="392"
width="780"
x="0"
y="0"
/>
</svg>
</DocumentFragment>
`;
exports[`PriceChart renders correctly with no prices filled 1`] = `
<DocumentFragment>
.c3 {
color: #0D111C;
}
.c0 {
position: absolute;
-webkit-animation: iAjNNh 125ms ease-in;
animation: iAjNNh 125ms ease-in;
-webkit-animation-duration: 250ms;
animation-duration: 250ms;
}
.c1 {
font-size: 36px;
line-height: 44px;
}
.c2 {
font-size: 24px;
line-height: 44px;
color: #98A1C0;
}
<div
class="c0"
data-cy="chart-header"
>
<span
class="c1 c2"
>
Price Unavailable
</span>
<div
class="c3 css-4u0e4f"
style="color: rgb(152, 161, 192);"
>
Missing chart data
</div>
</div>
.c0 text {
font-size: 12px;
font-weight: 400;
}
<svg
class="c0"
data-cy="missing-chart"
height="392"
style="min-width: 100%;"
width="780"
>
<path
d="M 0 241 Q 104 171, 208 241 T 416 241
M 416 241 Q 520 171, 624 241 T 832 241"
fill="transparent"
stroke="#D2D9EE"
stroke-width="2"
/>
<text
fill="#98A1C0"
x="20"
y="377"
/>
</svg>
</DocumentFragment>
`;
exports[`PriceChart renders correctly with some prices filled 1`] = `
<DocumentFragment>
.c4 {
display: inline-block;
height: inherit;
}
.c7 {
color: #98A1C0;
}
.c0 {
position: absolute;
-webkit-animation: iAjNNh 125ms ease-in;
animation: iAjNNh 125ms ease-in;
-webkit-animation-duration: 250ms;
animation-duration: 250ms;
}
.c3 {
font-size: 36px;
line-height: 44px;
}
.c1 {
color: #7780A0;
}
.c5 {
height: 16px;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-top: 4px;
}
.c6 {
padding-right: 3px;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.c2 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
gap: 6px;
font-size: 24px;
line-height: 44px;
}
<div
class="c0"
data-cy="chart-header"
>
<div
class="c1"
>
<div
class="c2"
>
<span
class="c3"
>
$1.00
</span>
<div
class="c4"
>
<div>
<svg
fill="none"
height="16"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
/>
<line
x1="12"
x2="12"
y1="16"
y2="12"
/>
<line
x1="12"
x2="12.01"
y1="8"
y2="8"
/>
</svg>
</div>
</div>
</div>
<div
class="c5"
>
0.00%
<div
class="c6"
>
<svg
aria-label="up"
class="c7"
fill="none"
height="20"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<line
x1="7"
x2="17"
y1="17"
y2="7"
/>
<polyline
points="7 7 17 7 17 17"
/>
</svg>
</div>
</div>
</div>
</div>
<svg
data-cy="price-chart"
height="392"
style="min-width: 100%;"
width="780"
>
<g
class="visx-group visx-axis visx-axis-bottom"
transform="translate(0, 391)"
>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="0"
y="18"
>
<tspan
dy="0em"
x="0"
>
0
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="90.27777777777777"
y="18"
>
<tspan
dy="0em"
x="90.27777777777777"
>
5,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="180.55555555555554"
y="18"
>
<tspan
dy="0em"
x="180.55555555555554"
>
10,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="270.8333333333333"
y="18"
>
<tspan
dy="0em"
x="270.8333333333333"
>
15,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="361.1111111111111"
y="18"
>
<tspan
dy="0em"
x="361.1111111111111"
>
20,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="451.3888888888889"
y="18"
>
<tspan
dy="0em"
x="451.3888888888889"
>
25,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="541.6666666666666"
y="18"
>
<tspan
dy="0em"
x="541.6666666666666"
>
30,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="631.9444444444445"
y="18"
>
<tspan
dy="0em"
x="631.9444444444445"
>
35,000
</tspan>
</text>
</svg>
</g>
<g
class="visx-group visx-axis-tick"
transform="translate(0, 0)"
>
<svg
font-size="10"
style="overflow: visible;"
x="0"
y="0.25em"
>
<text
fill="#222"
font-family="Arial"
font-size="10"
text-anchor="middle"
transform=""
x="722.2222222222222"
y="18"
>
<tspan
dy="0em"
x="722.2222222222222"
>
40,000
</tspan>
</text>
</svg>
</g>
</g>
<rect
fill="transparent"
height="392"
width="780"
x="0"
y="0"
/>
</svg>
</DocumentFragment>
`;

View File

@@ -343,7 +343,7 @@ exports[`LoadedRow.tsx renders a row 1`] = `
>
<a
class="c0"
href="/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
href="#/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
>
<div
class="c1"

View File

@@ -8,4 +8,4 @@ export const SMALL_MEDIA_BREAKPOINT = '540px'
export const MOBILE_MEDIA_BREAKPOINT = '420px'
// includes chains that the backend does not current source off-chain metadata for
export const UNSUPPORTED_METADATA_CHAINS = [ChainId.BNB, ChainId.AVALANCHE, ChainId.BASE_GOERLI, ChainId.BASE]
export const UNSUPPORTED_METADATA_CHAINS = [ChainId.BNB, ChainId.AVALANCHE]

View File

@@ -1,5 +1,4 @@
import { CustomUserProperties, InterfaceEventName, WalletConnectionResult } from '@uniswap/analytics-events'
import { getWalletMeta } from '@uniswap/conedison/provider/meta'
import { useWeb3React, Web3ReactHooks, Web3ReactProvider } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { sendAnalyticsEvent, user } from 'analytics'
@@ -13,6 +12,7 @@ import { ReactNode, useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import { useConnectedWallets } from 'state/wallets/hooks'
import { getCurrentPageFromLocation } from 'utils/urlRoutes'
import { getWalletMeta } from 'utils/walletMeta'
export default function Web3Provider({ children }: { children: ReactNode }) {
useEagerlyConnect()

View File

@@ -3,7 +3,7 @@ import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap
import { useWeb3React } from '@web3-react/core'
import { sendAnalyticsEvent, TraceEvent } from 'analytics'
import PortfolioDrawer, { useAccountDrawer } from 'components/AccountDrawer'
import { useHasPendingActivity } from 'components/AccountDrawer/MiniPortfolio/Activity/hooks'
import { usePendingActivity } from 'components/AccountDrawer/MiniPortfolio/Activity/hooks'
import PrefetchBalancesWrapper from 'components/AccountDrawer/PrefetchBalancesWrapper'
import Loader from 'components/Icons/LoadingSpinner'
import { IconWrapper } from 'components/Identicon/StatusIcon'
@@ -140,7 +140,7 @@ function Web3StatusInner() {
}, [toggleAccountDrawer])
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
const { hasPendingActivity, pendingActivityCount } = useHasPendingActivity()
const { hasPendingActivity, pendingActivityCount } = usePendingActivity()
if (account) {
return (

View File

@@ -267,7 +267,6 @@ export default function ConfirmSwapModal({
onCurrencySelection,
swapError,
swapResult,
swapQuoteReceivedDate,
fiatValueInput,
fiatValueOutput,
}: {
@@ -282,7 +281,6 @@ export default function ConfirmSwapModal({
swapError?: Error
onDismiss: () => void
onCurrencySelection: (field: Field, currency: Currency) => void
swapQuoteReceivedDate?: Date
fiatValueInput: { data?: number; isLoading: boolean }
fiatValueOutput: { data?: number; isLoading: boolean }
}) {
@@ -356,7 +354,6 @@ export default function ConfirmSwapModal({
swapResult={swapResult}
allowedSlippage={allowedSlippage}
disabledConfirm={showAcceptChanges}
swapQuoteReceivedDate={swapQuoteReceivedDate}
fiatValueInput={fiatValueInput}
fiatValueOutput={fiatValueOutput}
showAcceptChanges={showAcceptChanges}
@@ -386,7 +383,6 @@ export default function ConfirmSwapModal({
wrapTxHash,
allowance,
allowedSlippage,
swapQuoteReceivedDate,
fiatValueInput,
fiatValueOutput,
onAcceptChanges,

View File

@@ -44,7 +44,7 @@ export default function GasEstimateTooltip({ trade, loading }: { trade?: Interfa
>
<LoadingOpacityContainer $loading={loading}>
<RowFixed gap="xs">
{isUniswapXTrade(trade) ? <UniswapXRouterIcon /> : <StyledGasIcon />}
{isUniswapXTrade(trade) ? <UniswapXRouterIcon testId="gas-estimate-uniswapx-icon" /> : <StyledGasIcon />}
<ThemedText.BodySmall color="textSecondary">
<Row gap="xs">
<div>{formatNumber(trade.totalGasUseEstimateUSD, NumberType.FiatGasPrice)}</div>

View File

@@ -13,7 +13,6 @@ describe('SwapModalFooter.tsx', () => {
onConfirm={jest.fn()}
swapErrorMessage={undefined}
disabledConfirm={false}
swapQuoteReceivedDate={undefined}
fiatValueInput={{
data: undefined,
isLoading: false,
@@ -49,7 +48,6 @@ describe('SwapModalFooter.tsx', () => {
onConfirm={jest.fn()}
swapErrorMessage={undefined}
disabledConfirm={false}
swapQuoteReceivedDate={undefined}
fiatValueInput={{
data: undefined,
isLoading: false,
@@ -77,7 +75,6 @@ describe('SwapModalFooter.tsx', () => {
onConfirm={jest.fn()}
swapErrorMessage={undefined}
disabledConfirm={false}
swapQuoteReceivedDate={undefined}
fiatValueInput={{
data: undefined,
isLoading: false,

View File

@@ -53,7 +53,6 @@ export default function SwapModalFooter({
onConfirm,
swapErrorMessage,
disabledConfirm,
swapQuoteReceivedDate,
fiatValueInput,
fiatValueOutput,
showAcceptChanges,
@@ -65,7 +64,6 @@ export default function SwapModalFooter({
onConfirm: () => void
swapErrorMessage?: ReactNode
disabledConfirm: boolean
swapQuoteReceivedDate?: Date
fiatValueInput: { data?: number; isLoading: boolean }
fiatValueOutput: { data?: number; isLoading: boolean }
showAcceptChanges: boolean
@@ -187,7 +185,6 @@ export default function SwapModalFooter({
transactionDeadlineSecondsSinceEpoch,
isAutoSlippage,
isAutoRouterApi: routerPreference === RouterPreference.API,
swapQuoteReceivedDate,
routes,
fiatValueInput: fiatValueInput.data,
fiatValueOutput: fiatValueOutput.data,

View File

@@ -110,8 +110,8 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
-webkit-filter: none;
filter: none;
opacity: 1;
-webkit-transition: opacity 0.2s ease-in-out;
transition: opacity 0.2s ease-in-out;
-webkit-transition: opacity 250ms ease-in-out;
transition: opacity 250ms ease-in-out;
}
.c10 {

View File

@@ -86,6 +86,7 @@ const UniswapXShineInner = styled.div`
// overflow hidden to hide the SwapMustacheShadow
export const SwapOptInSmallContainer = styled.div<{ visible: boolean; shouldAnimate: boolean }>`
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
overflow: hidden;
margin-top: -14px;
transform: translateY(${({ visible }) => (visible ? 0 : -80)}px);

View File

@@ -7,16 +7,18 @@ const chainPriorityTestCases: [ChainId, number][] = [
[ChainId.MAINNET, 0],
[ChainId.GOERLI, 0],
[ChainId.SEPOLIA, 0],
[ChainId.POLYGON, 1],
[ChainId.POLYGON_MUMBAI, 1],
[ChainId.ARBITRUM_ONE, 2],
[ChainId.ARBITRUM_GOERLI, 2],
[ChainId.OPTIMISM, 3],
[ChainId.OPTIMISM_GOERLI, 3],
[ChainId.BNB, 4],
[ChainId.AVALANCHE, 5],
[ChainId.CELO, 6],
[ChainId.CELO_ALFAJORES, 6],
[ChainId.ARBITRUM_ONE, 1],
[ChainId.ARBITRUM_GOERLI, 1],
[ChainId.OPTIMISM, 2],
[ChainId.OPTIMISM_GOERLI, 2],
[ChainId.POLYGON, 3],
[ChainId.POLYGON_MUMBAI, 3],
[ChainId.BASE, 4],
[ChainId.BASE_GOERLI, 4],
[ChainId.BNB, 5],
[ChainId.AVALANCHE, 6],
[ChainId.CELO, 7],
[ChainId.CELO_ALFAJORES, 7],
]
test.each(chainPriorityTestCases)(

View File

@@ -1,6 +1,12 @@
import { ChainId, SUPPORTED_CHAINS, SupportedChainsType } from '@uniswap/sdk-core'
export const UniWalletSupportedChains = [ChainId.MAINNET, ChainId.ARBITRUM_ONE, ChainId.OPTIMISM, ChainId.POLYGON]
export const UniWalletSupportedChains = [
ChainId.MAINNET,
ChainId.ARBITRUM_ONE,
ChainId.OPTIMISM,
ChainId.POLYGON,
ChainId.BASE,
]
export const CHAIN_IDS_TO_NAMES = {
[ChainId.MAINNET]: 'mainnet',
@@ -113,24 +119,27 @@ export function getChainPriority(chainId: ChainId): number {
case ChainId.GOERLI:
case ChainId.SEPOLIA:
return 0
case ChainId.POLYGON:
case ChainId.POLYGON_MUMBAI:
return 1
case ChainId.ARBITRUM_ONE:
case ChainId.ARBITRUM_GOERLI:
return 2
return 1
case ChainId.OPTIMISM:
case ChainId.OPTIMISM_GOERLI:
return 2
case ChainId.POLYGON:
case ChainId.POLYGON_MUMBAI:
return 3
case ChainId.BNB:
case ChainId.BASE:
case ChainId.BASE_GOERLI:
return 4
case ChainId.AVALANCHE:
case ChainId.BNB:
return 5
case ChainId.AVALANCHE:
return 6
case ChainId.CELO:
case ChainId.CELO_ALFAJORES:
return 6
default:
return 7
default:
return 8
}
}

View File

@@ -3,3 +3,7 @@ import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useCurrencyConversionFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.currencyConversion)
}
export function useCurrencyConversionFlagEnabled(): boolean {
return useCurrencyConversionFlag() === BaseVariant.Enabled
}

View File

@@ -1,9 +0,0 @@
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useRoutingAPIForPriceFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.routingAPIPrice)
}
export function useRoutingAPIForPrice(): boolean {
return useRoutingAPIForPriceFlag() === BaseVariant.Enabled
}

View File

@@ -12,7 +12,6 @@ export enum FeatureFlag {
debounceSwapQuote = 'debounce_swap_quote',
uniswapXEnabled = 'uniswapx_enabled', // enables sending dutch_limit config to routing-api
uniswapXSyntheticQuote = 'uniswapx_synthetic_quote',
routingAPIPrice = 'routing_api_price',
forceUniswapXOn = 'uniswapx_force_on', // forces routing-api's feature flag for uniswapx to turn on as well
uniswapXEthOutputEnabled = 'uniswapx_eth_output_enabled',
multichainUX = 'multichain_ux',

View File

@@ -4,7 +4,7 @@ import { useMemo } from 'react'
import invariant from 'tiny-invariant'
import { Chain, SearchTokensQuery, useSearchTokensQuery } from './__generated__/types-and-hooks'
import { chainIdToBackendName } from './util'
import { BACKEND_SUPPORTED_CHAINS, chainIdToBackendName } from './util'
gql`
query SearchTokens($searchQuery: String!) {
@@ -96,7 +96,10 @@ export function useSearchTokens(searchQuery: string, chainId: number) {
const searchChain = chainIdToBackendName(chainId)
// Stores results, allowing overwriting cross-chain tokens w/ more 'relevant token'
const selectionMap: { [projectId: string]: SearchToken } = {}
data?.searchTokens?.forEach((token) => {
const filteredTokens = data?.searchTokens?.filter((token) =>
(BACKEND_SUPPORTED_CHAINS as ReadonlyArray<Chain>).includes(token.chain)
)
filteredTokens?.forEach((token) => {
if (token.project?.id) {
const existing = selectionMap[token.project.id]
selectionMap[token.project.id] = dedupeCrosschainTokens(token, existing, searchChain)

View File

@@ -35,6 +35,7 @@ gql`
tokenProject {
id
logoUrl
isSpam
}
}
token {

View File

@@ -87,6 +87,7 @@ export const CHAIN_ID_TO_BACKEND_NAME: { [key: number]: InterfaceGqlChain } = {
[ChainId.OPTIMISM_GOERLI]: Chain.Optimism,
[ChainId.BNB]: Chain.Bnb,
[ChainId.AVALANCHE]: Chain.Avalanche,
[ChainId.BASE]: Chain.Base,
}
export function chainIdToBackendName(chainId: number | undefined) {
@@ -128,6 +129,7 @@ const URL_CHAIN_PARAM_TO_BACKEND: { [key: string]: InterfaceGqlChain } = {
optimism: Chain.Optimism,
bnb: Chain.Bnb,
avalanche: Chain.Avalanche,
base: Chain.Base,
}
/**

View File

@@ -26,6 +26,7 @@ export function useDebouncedTrade(
): {
state: TradeState
trade?: InterfaceTrade
swapQuoteLatency?: number
}
export function useDebouncedTrade(
@@ -37,6 +38,7 @@ export function useDebouncedTrade(
): {
state: TradeState
trade?: ClassicTrade
swapQuoteLatency?: number
}
/**
* Returns the debounced v2+v3 trade for a desired swap.
@@ -57,47 +59,38 @@ export function useDebouncedTrade(
state: TradeState
trade?: InterfaceTrade
method?: QuoteMethod
swapQuoteLatency?: number
} {
const { chainId } = useWeb3React()
const autoRouterSupported = useAutoRouterSupported()
const isWindowVisible = useIsWindowVisible()
const debouncedSwapQuoteFlagEnabled = useDebounceSwapQuoteFlag() === DebounceSwapQuoteVariant.Enabled
const [debouncedAmount, debouncedOtherCurrency] = useDebounce(
useMemo(() => [amountSpecified, otherCurrency], [amountSpecified, otherCurrency]),
debouncedSwapQuoteFlagEnabled ? DEBOUNCE_TIME_INCREASED : DEBOUNCE_TIME
const inputs = useMemo<[CurrencyAmount<Currency> | undefined, Currency | undefined]>(
() => [amountSpecified, otherCurrency],
[amountSpecified, otherCurrency]
)
const debouncedSwapQuoteFlagEnabled = useDebounceSwapQuoteFlag() === DebounceSwapQuoteVariant.Enabled
const isDebouncing =
useDebounce(inputs, debouncedSwapQuoteFlagEnabled ? DEBOUNCE_TIME_INCREASED : DEBOUNCE_TIME) !== inputs
const isAWrapTransaction = useMemo(() => {
if (!chainId || !amountSpecified || !debouncedOtherCurrency) return false
const isWrap = useMemo(() => {
if (!chainId || !amountSpecified || !otherCurrency) return false
const weth = WRAPPED_NATIVE_CURRENCY[chainId]
return (
(amountSpecified.currency.isNative && weth?.equals(debouncedOtherCurrency)) ||
(debouncedOtherCurrency.isNative && weth?.equals(amountSpecified.currency))
(amountSpecified.currency.isNative && weth?.equals(otherCurrency)) ||
(otherCurrency.isNative && weth?.equals(amountSpecified.currency))
)
}, [amountSpecified, chainId, debouncedOtherCurrency])
}, [amountSpecified, chainId, otherCurrency])
const shouldGetTrade = !isAWrapTransaction && isWindowVisible
const skipFetch = isDebouncing || !autoRouterSupported || !isWindowVisible || isWrap
const [routerPreference] = useRouterPreference()
const routingAPITrade = useRoutingAPITrade(
return useRoutingAPITrade(
tradeType,
amountSpecified ? debouncedAmount : undefined,
debouncedOtherCurrency,
amountSpecified,
otherCurrency,
routerPreferenceOverride ?? routerPreference,
!(autoRouterSupported && shouldGetTrade), // skip fetching
skipFetch,
account
)
const inDebounce =
(!debouncedAmount && Boolean(amountSpecified)) || (!debouncedOtherCurrency && Boolean(otherCurrency))
const isLoading = routingAPITrade.state === TradeState.LOADING || inDebounce
return useMemo(
() => ({
...routingAPITrade,
...(isLoading ? { state: TradeState.LOADING } : {}),
}),
[isLoading, routingAPITrade]
)
}

View File

@@ -1,7 +1,6 @@
import { Connector } from '@web3-react/types'
import { gnosisSafeConnection, networkConnection } from 'connection'
import { getConnection } from 'connection'
import { Connection } from 'connection/types'
import { useEffect } from 'react'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer'
@@ -22,22 +21,25 @@ export default function useEagerlyConnect() {
const dispatch = useAppDispatch()
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
let selectedConnection: Connection | undefined
if (selectedWallet) {
try {
selectedConnection = getConnection(selectedWallet)
} catch {
dispatch(updateSelectedWallet({ wallet: undefined }))
}
}
const rehydrated = useAppSelector((state) => state._persist.rehydrated)
useEffect(() => {
connect(gnosisSafeConnection.connector)
connect(networkConnection.connector)
try {
connect(gnosisSafeConnection.connector)
connect(networkConnection.connector)
if (selectedConnection) {
connect(selectedConnection.connector)
} // The dependency list is empty so this is only run once on mount
}, []) // eslint-disable-line react-hooks/exhaustive-deps
if (!selectedWallet) return
const selectedConnection = getConnection(selectedWallet)
if (selectedConnection) {
connect(selectedConnection.connector)
}
} catch {
// only clear the persisted wallet type if it failed to connect.
if (rehydrated) {
dispatch(updateSelectedWallet({ wallet: undefined }))
}
return
}
}, [dispatch, rehydrated, selectedWallet])
}

View File

@@ -1,4 +1,3 @@
import { signTypedData } from '@uniswap/conedison/provider/signing'
import { AllowanceTransfer, MaxAllowanceTransferAmount, PERMIT2_ADDRESS, PermitSingle } from '@uniswap/permit2-sdk'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
@@ -9,6 +8,7 @@ import { useSingleCallResult } from 'lib/hooks/multicall'
import ms from 'ms'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { toReadableError, UserRejectedRequestError } from 'utils/errors'
import { signTypedData } from 'utils/signing'
import { didUserReject } from 'utils/swapErrorToUserReadableMessage'
const PERMIT_EXPIRATION = ms(`30d`)
@@ -77,7 +77,6 @@ export function useUpdatePermitAllowance(
}
const { domain, types, values } = AllowanceTransfer.getPermitData(permit, PERMIT2_ADDRESS, chainId)
// Use conedison's signTypedData for better x-wallet compatibility.
const signature = await signTypedData(provider.getSigner(account), domain, types, values)
onPermitSignature?.({ ...permit, signature })
return

View File

@@ -3,6 +3,7 @@ import { ChainId, Currency, CurrencyAmount, Price, TradeType } from '@uniswap/sd
import { nativeOnChain } from 'constants/tokens'
import { Chain, useTokenSpotPriceQuery } from 'graphql/data/__generated__/types-and-hooks'
import { chainIdToBackendName, isGqlSupportedChain, PollingInterval } from 'graphql/data/util'
import { useMemo } from 'react'
import { INTERNAL_ROUTER_PREFERENCE_PRICE, TradeState } from 'state/routing/types'
import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade'
import { getNativeTokenDBAddress } from 'utils/nativeTokens'
@@ -19,66 +20,89 @@ const ETH_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Currency> } = {
[ChainId.CELO]: CurrencyAmount.fromRawAmount(nativeOnChain(ChainId.CELO), 10e18),
}
function useETHValue(currencyAmount?: CurrencyAmount<Currency>): {
data?: CurrencyAmount<Currency>
function useETHPrice(currency?: Currency): {
data?: Price<Currency, Currency>
isLoading: boolean
} {
const chainId = currencyAmount?.currency?.chainId
const amountOut = isGqlSupportedChain(chainId) ? ETH_AMOUNT_OUT[chainId] : undefined
const chainId = currency?.chainId
const isSupported = currency && isGqlSupportedChain(chainId)
const amountOut = isSupported ? ETH_AMOUNT_OUT[chainId] : undefined
const { trade, state } = useRoutingAPITrade(
TradeType.EXACT_OUTPUT,
amountOut,
currencyAmount?.currency,
INTERNAL_ROUTER_PREFERENCE_PRICE
currency,
INTERNAL_ROUTER_PREFERENCE_PRICE,
!isSupported
)
// Get ETH value of ETH or WETH
if (chainId && currencyAmount && currencyAmount.currency.wrapped.equals(nativeOnChain(chainId).wrapped)) {
return {
data: new Price(currencyAmount.currency, currencyAmount.currency, '1', '1').quote(currencyAmount),
isLoading: false,
return useMemo(() => {
if (!isSupported) {
return { data: undefined, isLoading: false }
}
}
if (!trade || state === TradeState.LOADING || !currencyAmount?.currency || !isGqlSupportedChain(chainId)) {
return { data: undefined, isLoading: state === TradeState.LOADING }
}
if (currency?.wrapped.equals(nativeOnChain(chainId).wrapped)) {
return {
data: new Price(currency, currency, '1', '1'),
isLoading: false,
}
}
const { numerator, denominator } = trade.routes[0].midPrice
const price = new Price(currencyAmount?.currency, nativeOnChain(chainId), denominator, numerator)
return { data: price.quote(currencyAmount), isLoading: false }
if (!trade || state === TradeState.LOADING) {
return { data: undefined, isLoading: state === TradeState.LOADING }
}
const { numerator, denominator } = trade.routes[0].midPrice
const price = new Price(currency, nativeOnChain(chainId), denominator, numerator)
return { data: price, isLoading: false }
}, [chainId, currency, isSupported, state, trade])
}
// TODO(WEB-2095): This hook should early return `null` when `currencyAmount` is undefined. Otherwise,
// it is not possible to differentiate between a loading state and a state where `currencyAmount`
// is undefined
export function useUSDPrice(currencyAmount?: CurrencyAmount<Currency>): {
export function useUSDPrice(
currencyAmount?: CurrencyAmount<Currency>,
prefetchCurrency?: Currency
): {
data?: number
isLoading: boolean
} {
const chain = currencyAmount?.currency.chainId ? chainIdToBackendName(currencyAmount?.currency.chainId) : undefined
const currency = currencyAmount?.currency
const { data: ethValue, isLoading: isEthValueLoading } = useETHValue(currencyAmount)
const currency = currencyAmount?.currency ?? prefetchCurrency
const chainId = currency?.chainId
const chain = chainId ? chainIdToBackendName(chainId) : undefined
// Use ETH-based pricing if available.
const { data: tokenEthPrice, isLoading: isTokenEthPriceLoading } = useETHPrice(currency)
const isTokenEthPriced = Boolean(tokenEthPrice || isTokenEthPriceLoading)
const { data, networkStatus } = useTokenSpotPriceQuery({
variables: { chain: chain ?? Chain.Ethereum, address: getNativeTokenDBAddress(chain ?? Chain.Ethereum) },
skip: !chain || !isGqlSupportedChain(currency?.chainId),
skip: !isTokenEthPriced,
pollInterval: PollingInterval.Normal,
notifyOnNetworkStatusChange: true,
fetchPolicy: 'cache-first',
})
// Use USDC price for chains not supported by backend yet
const stablecoinPrice = useStablecoinPrice(!isGqlSupportedChain(currency?.chainId) ? currency : undefined)
if (!isGqlSupportedChain(currency?.chainId) && currencyAmount && stablecoinPrice) {
return { data: parseFloat(stablecoinPrice.quote(currencyAmount).toSignificant()), isLoading: false }
}
// Use USDC-based pricing for chains not yet supported by backend (for ETH-based pricing).
const stablecoinPrice = useStablecoinPrice(isTokenEthPriced ? undefined : currency)
const isFirstLoad = networkStatus === NetworkStatus.loading
// Otherwise, get the price of the token in ETH, and then multiple by the price of ETH
const ethUSDPrice = data?.token?.project?.markets?.[0]?.price?.value
if (!ethUSDPrice || !ethValue) return { data: undefined, isLoading: isEthValueLoading || isFirstLoad }
return { data: parseFloat(ethValue.toExact()) * ethUSDPrice, isLoading: false }
return useMemo(() => {
if (!currencyAmount) {
return { data: undefined, isLoading: false }
} else if (stablecoinPrice) {
return { data: parseFloat(stablecoinPrice.quote(currencyAmount).toSignificant()), isLoading: false }
} else {
// Otherwise, get the price of the token in ETH, and then multiply by the price of ETH.
const ethUSDPrice = data?.token?.project?.markets?.[0]?.price?.value
if (ethUSDPrice && tokenEthPrice) {
return { data: parseFloat(tokenEthPrice.quote(currencyAmount).toExact()) * ethUSDPrice, isLoading: false }
} else {
return { data: undefined, isLoading: isTokenEthPriceLoading || networkStatus === NetworkStatus.loading }
}
}
}, [
currencyAmount,
data?.token?.project?.markets,
tokenEthPrice,
isTokenEthPriceLoading,
networkStatus,
stablecoinPrice,
])
}

View File

@@ -1,7 +1,6 @@
import { BigNumber } from '@ethersproject/bignumber'
import * as Sentry from '@sentry/react'
import { SwapEventName } from '@uniswap/analytics-events'
import { signTypedData } from '@uniswap/conedison/provider/signing'
import { Percent } from '@uniswap/sdk-core'
import { DutchOrder, DutchOrderBuilder } from '@uniswap/uniswapx-sdk'
import { useWeb3React } from '@web3-react/core'
@@ -11,6 +10,7 @@ import { useCallback } from 'react'
import { DutchOrderTrade, TradeFillType } from 'state/routing/types'
import { trace } from 'tracing/trace'
import { UserRejectedRequestError } from 'utils/errors'
import { signTypedData } from 'utils/signing'
import { didUserReject, swapErrorToUserReadableMessage } from 'utils/swapErrorToUserReadableMessage'
const DEFAULT_START_TIME_PADDING_SECONDS = 30
@@ -105,6 +105,7 @@ export function useUniswapXSwapCallback({
}
}
const beforeSign = Date.now()
const { signature, updatedOrder } = await signDutchOrder()
sendAnalyticsEvent(SwapEventName.SWAP_SIGNED, {
@@ -112,6 +113,7 @@ export function useUniswapXSwapCallback({
trade,
allowedSlippage,
fiatValues,
timeToSignSinceRequestMs: Date.now() - beforeSign,
}),
...analyticsContext,
})

View File

@@ -94,6 +94,7 @@ export function useUniversalRouterSwapCallback(
}
const gasLimit = calculateGasMargin(gasEstimate)
setTraceData('gasLimit', gasLimit.toNumber())
const beforeSign = Date.now()
const response = await provider
.getSigner()
.sendTransaction({ ...tx, gasLimit })
@@ -101,6 +102,7 @@ export function useUniversalRouterSwapCallback(
sendAnalyticsEvent(SwapEventName.SWAP_SIGNED, {
...formatSwapSignedAnalyticsEventProperties({
trade,
timeToSignSinceRequestMs: Date.now() - beforeSign,
allowedSlippage: options.slippageTolerance,
fiatValues,
txHash: response.hash,

View File

@@ -12,9 +12,8 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from 'react-query'
import { Provider } from 'react-redux'
import { BrowserRouter, HashRouter } from 'react-router-dom'
import { HashRouter } from 'react-router-dom'
import { SystemThemeUpdater } from 'theme/components/ThemeToggle'
import { isBrowserRouterEnabled } from 'utils/env'
import Web3Provider from './components/Web3Provider'
import { LanguageProvider } from './i18n'
@@ -52,14 +51,12 @@ const queryClient = new QueryClient()
const container = document.getElementById('root') as HTMLElement
const Router = isBrowserRouterEnabled() ? BrowserRouter : HashRouter
createRoot(container).render(
<StrictMode>
<Provider store={store}>
<FeatureFlagsProvider>
<QueryClientProvider client={queryClient}>
<Router>
<HashRouter>
<LanguageProvider>
<Web3Provider>
<ApolloProvider client={apolloClient}>
@@ -73,7 +70,7 @@ createRoot(container).render(
</ApolloProvider>
</Web3Provider>
</LanguageProvider>
</Router>
</HashRouter>
</QueryClientProvider>
</FeatureFlagsProvider>
</Provider>

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