Compare commits

...

86 Commits

Author SHA1 Message Date
Jordan Frankfurt
686f24b98e fix: ensure lists are present before running update (#7206) (#7208)
* fix: ensure lists are present before running update

* pr input from zzmp
2023-08-22 15:00:06 -05:00
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
Tina
cc52da9fc4 fix: Remove double client side quoting (#7121)
* remove double client side quoting

* remove additional file

* remove file

* remove file

* MOVE YET ANOTHER FILE

* remove unused tokens

* fix export

* remove unused token constant and swap out for wbtc
2023-08-11 19:25:57 -04:00
Brendan Wong
b98e62cac8 fix: remove description from custom rich link previews (#7142)
* fix: remove description from custom rich link previews

* add static assets to prepare for prep

* remove added assets
2023-08-11 18:53:48 -04:00
Brendan Wong
6cd1f04584 fix: remove lowering opacity for token previews (#7145)
Update [[index]].tsx
2023-08-11 18:32:16 -04:00
cartcrom
8ffe4e991b fix: properly display FOT swaps (#7146) 2023-08-11 18:19:28 -04:00
eddie
2b85852a48 fix: fail UR transaction if chain IDs don't match (#7150)
* fix: verify UR deployed

* fix: dont fallback to 1

* fix: dont instantiate contract

* fix: use chainId instead

* fix: compare chainIds
2023-08-11 18:15:35 -04:00
Nate Wienert
94015b279c fix: fix jest breaking with new swc setting (#7149) 2023-08-11 13:53:48 -07:00
Jack Short
1291887049 chore: add to blocked nft infringing collections (#7125) 2023-08-11 14:53:10 -04:00
Nate Wienert
d4a26a5610 fix: make swc not break WalletConnect by targeting a newer version of es (#7139) 2023-08-11 07:57:55 -10:00
Jack Short
1249371397 feat: currency conversion feature flag (#7123)
* feat: currency conversion feature flag

* removing eslint
2023-08-11 13:13:17 -04:00
Brendan Wong
2ce7b08244 feat: custom colors for rich link previews (#7141)
* add custom color logic

* increase timeout
2023-08-11 12:27:46 -04:00
Tina
156254afa9 feat: Upgrade uniswapx-sdk for eth output (#7135)
upgrade sdk for eth output
2023-08-11 10:59:03 -04:00
Tina
e31f4e549b feat: Fetch updated nonce before a UniswapX swap (#7120)
* add method to fetch updated gouda nonce

* remove console log
2023-08-11 10:33:07 -04:00
Brendan Wong
f47e1f16d7 fix: color extraction for rich link previews (#7138)
* feat: add token and nft injection

* feat: basic tests

* fix: get jest configured properly

* fix: change timeout

* fix: uninstall port ready

* fix: readd port ready

* fix: local tests work

* Update yarn.lock

* add lint disable for setup files

* fix: update dependencies

* fix: basic test suite for nfts/tokens

* feat: collection data

* fix: make tests more comprehensive

* fix: change matches to contains

* fix: tests for twitter alt image tag

* fix: image gen

* fix: add patch-package

* fix: update yarn install

* feat: basic image gen for nfts and collections

* fix: remove vibrant attempt

* use watermark asset

* dynamically grab color

* modularize code and prototype for token preview

* refactor code

* finalize css

* fix color grabber

* update tests

* fix up css

* refactor code a bit more

* remove console logs

* tests

* update tests

* update images based on design feedback

* network logos

* update lint

* slight refactoring

* more refactoring

* fix packages

* Update yarn.lock

* remove dynamically generated image stuff

* Revert "remove dynamically generated image stuff"

This reverts commit a80241edb3.

* change image reference and revamp tests

* cleanup return values

* Create README.md

* Revert "Create README.md"

This reverts commit 7a91c98d38.

* First round of feedback

* comments

* feat: cache

* Update test.yml

* Update test.yml

* Update test.yml

* feedback round 2

* final feedback

* final final feedback

* add coverage and other options

* Update test.yml

* start typecheck

* update cache

* update snapshots?

* Update jest.config.json

* Update jest.config.json

* give timeout some buffer

* update import

* upgrade ts

* fix typing for apollo deps

* finalize typechecks

* downgrade typescript to original version

* add cache directory to jest

* remove coverage

* remove google analytics from tests

* merge main

* remove timeout

* update tests

* update graphql queries

* review changes

* try cache setup

* Update cache.test.ts

* make cache helper function

* cache test

* remove unneeded test causing issues

* feat: parallelize cache (#6930)

* feat: parallelize cache?

* remove graph query from concurrency await

* most of feedback

* move tests

* update token tests

* singleton cache

* restructuring res and cache promise

* abstract away repeated graph logic

* update tests and functions

* refactor

* update typing, parallelize, and start tests

* fix one tsc issue

* final feedback

* Update yarn.lock

* final final feedback

* add svgs

* try and setup svg

* stashing changes

* cleanup!

* prepare for start of feedback?

* LESS GOO

* modify versioning

* fix: update wrangler version

* Update yarn.lock

* downgrade wrangler

* Update yarn.lock

* Update yarn.lock

* fix type error

* update github test

* cleanup tests

* Delete custom.d.ts

* fix: cloudfunctions

* update tests

* final touchups

* lint

* change github action

* Update yarn.lock

* styling updates

* nate's feedback

* feedback p1

* typing feedback

* update yarn

* Create wrangler.toml

* move wrangler.toml location

* last try

* Delete wrangler.toml

* use 2.20?

* remove comment

* Update yarn.lock

* change compatibility date

* update wrangler and fix bugs

* Update colorthief+2.4.0.patch

* build: cleanup flags

* cleaner patches

* update compatibility date

* quick tweeks

* cleanup rendering and lint

* patch things up

* fix: color extraction

* DONE!

* tests and other qol updates

* lint

* add more tests

* feedback

* simplify getcolors

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-08-10 18:32:08 -04:00
Brendan Wong
9954f9502d feat: dynamically generated images for rich link previews (#6902)
* feat: add token and nft injection

* feat: basic tests

* fix: get jest configured properly

* fix: change timeout

* fix: uninstall port ready

* fix: readd port ready

* fix: local tests work

* Update yarn.lock

* add lint disable for setup files

* fix: update dependencies

* fix: basic test suite for nfts/tokens

* feat: collection data

* fix: make tests more comprehensive

* fix: change matches to contains

* fix: tests for twitter alt image tag

* fix: image gen

* fix: add patch-package

* fix: update yarn install

* feat: basic image gen for nfts and collections

* fix: remove vibrant attempt

* use watermark asset

* dynamically grab color

* modularize code and prototype for token preview

* refactor code

* finalize css

* fix color grabber

* update tests

* fix up css

* refactor code a bit more

* remove console logs

* tests

* update tests

* update images based on design feedback

* network logos

* update lint

* slight refactoring

* more refactoring

* fix packages

* Update yarn.lock

* remove dynamically generated image stuff

* Revert "remove dynamically generated image stuff"

This reverts commit a80241edb3.

* change image reference and revamp tests

* cleanup return values

* Create README.md

* Revert "Create README.md"

This reverts commit 7a91c98d38.

* First round of feedback

* comments

* feat: cache

* Update test.yml

* Update test.yml

* Update test.yml

* feedback round 2

* final feedback

* final final feedback

* add coverage and other options

* Update test.yml

* start typecheck

* update cache

* update snapshots?

* Update jest.config.json

* Update jest.config.json

* give timeout some buffer

* update import

* upgrade ts

* fix typing for apollo deps

* finalize typechecks

* downgrade typescript to original version

* add cache directory to jest

* remove coverage

* remove google analytics from tests

* merge main

* remove timeout

* update tests

* update graphql queries

* review changes

* try cache setup

* Update cache.test.ts

* make cache helper function

* cache test

* remove unneeded test causing issues

* feat: parallelize cache (#6930)

* feat: parallelize cache?

* remove graph query from concurrency await

* most of feedback

* move tests

* update token tests

* singleton cache

* restructuring res and cache promise

* abstract away repeated graph logic

* update tests and functions

* refactor

* update typing, parallelize, and start tests

* fix one tsc issue

* final feedback

* Update yarn.lock

* final final feedback

* add svgs

* try and setup svg

* stashing changes

* cleanup!

* prepare for start of feedback?

* LESS GOO

* modify versioning

* fix: update wrangler version

* Update yarn.lock

* downgrade wrangler

* Update yarn.lock

* Update yarn.lock

* fix type error

* update github test

* cleanup tests

* Delete custom.d.ts

* fix: cloudfunctions

* update tests

* final touchups

* lint

* change github action

* Update yarn.lock

* styling updates

* nate's feedback

* feedback p1

* typing feedback

* update yarn

* Create wrangler.toml

* move wrangler.toml location

* last try

* Delete wrangler.toml

* use 2.20?

* remove comment

* Update yarn.lock

* change compatibility date

* update wrangler and fix bugs

* Update colorthief+2.4.0.patch

* build: cleanup flags

* cleaner patches

* update compatibility date

* quick tweeks

* cleanup rendering and lint

* final color update

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-08-10 15:29:37 -04:00
eddie
9fbdc3cab1 feat: base flag cleanup (#7134)
feat: remove base feature flag
2023-08-10 11:05:48 -07:00
eddie
e04be0711f fix: unknown token logo styling (#7132)
* fix: unknown token logo styling

* fix: tests
2023-08-10 11:05:32 -07:00
eddie
5c3caa7143 feat: query base backend (#7133) 2023-08-10 11:05:23 -07:00
Jack Short
1ea724609f feat: bottom sheet for mobile swap settings (#7072)
* feat: bottom sheet for settings dialogue

* renaming to settings

* renmaing

* cypress tests

* fixing the cypress tests

* chore: e2e insufficient liquidity test

* Revert "chore: e2e insufficient liquidity test"

This reverts commit 8250730f35.
2023-08-10 12:20:31 -04:00
Jordan Frankfurt
2b6948db94 fix: standardize styled component file names and ignore them in codecov (#7113)
* fix: standardize styled component file names and ignore them in codecov

* Update codecov.yml

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

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-08-10 11:15:33 -05:00
Jordan Frankfurt
0b8026e6fe feat: add base promotional advert (#7124)
* feat: add base promotional advert

add mobile layout

tweak text container size

only show new banner on Base

fix import

update to new state structure

* Update src/components/Banner/BaseAnnouncementBanner/index.tsx

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

* pr feedback

---------

Co-authored-by: Charles Bachmeier <charles@bachmeier.io>
2023-08-10 11:03:21 -05:00
Brendan Wong
b14831be12 fix: downgrade wrangler (#7128)
* downgrade wrangler

* remove report

* comment and update test

* update comment
2023-08-09 20:20:02 -04:00
Charles Bachmeier
b12a61e2fb feat: add multichain_ux flag (#7122)
* feat: add multichain_ux flag

* update flag text

* add new flag file
2023-08-09 16:47:38 -07:00
Jordan Frankfurt
6f6c9d7174 chore: remove e2e coverage from aggregate calculation in codecov (#7108)
* chore: remove e2e coverage from aggregate calculation in codecov

* pr feedback
2023-08-09 14:48:59 -05:00
Jordan Frankfurt
89c3ec7526 chore: reduce patch coverage requirement to 50% (#7109) 2023-08-09 14:40:48 -05:00
Charles Bachmeier
06c81d9304 feat: add amplitude logging for gas estimate failures (#7095)
* send gas estimate failure

* update event name

* update analytics events package

* add error and tx to analytics event
2023-08-09 12:28:52 -07:00
Thomas Thachil
15c59e8149 fix: fixing deeplinks json nesting (#7126) 2023-08-09 15:28:26 -04:00
Nate Wienert
f6b66c759a fix: make loading indicator for pools nft card use a shine rather than spinner (#7071)
fix: make loading indicator for pools nft card use a shine rather than spinner and fill the full height
2023-08-09 08:14:08 -10:00
Jack Short
b1c99d4b37 chore: moving coned formatting to interface (#7114) 2023-08-09 14:01:37 -04:00
Thomas Thachil
add6df46e6 feat: adding more fingerprints for deeplink apps (#7107) 2023-08-09 11:49:10 -04:00
Zach Pomerantz
b2dfb29f51 build: simplify/fix test caching (#7084)
* build: rm unnecessary cache-on-main

* build: hardhat fan-in cache
2023-08-08 16:48:27 -07:00
Brendan Wong
097ef6a3df feat: fallback metadata injection (#7070)
* feat: fallback metadata

* fix lint

* Update tsconfig.json

* feedback

* fix: cloudfunctions

* update test
2023-08-08 15:50:52 -04:00
Brendan Wong
984cf81911 fix: cleanup cloud function environment (#7085)
* cleanup!

* update comments

* fix: cloudfunctions
2023-08-08 15:21:30 -04:00
Zach Pomerantz
08501234a8 build: replace babel with swc (#7056)
* chore: refactor ms.macro to ms

* build: upgrade lingui@4

* chore: avoid styled-components/macro

* build: replace babel loader with swc

* build: upgrade vanilla-extract

* build: simplify e2e config

* build: migrate jest to swc

* build: dedup

* whoops

* fix: initial translations

* build: coverage

* build: ignore static extract warning

* build: rm old babel pkg

* test: rm e2e codecov

* build: fix swc cache

* cleanup

* fix visible t9ns

* cleanup

* cleanup setup

* crank it

* actual merge conflict resolution

* fix: restore locale initialization
2023-08-08 10:38:37 -07:00
Jordan Frankfurt
3afe7fe9f2 fix: add back correct fellback setter (#7102) 2023-08-08 12:37:57 -05:00
cartcrom
bd573724b9 fix: uniswapx review modal success display (#7097)
* fix: update swapConfirmed logic

* fix: success icon display
2023-08-08 10:15:32 -07:00
Zach Pomerantz
684258dc17 fix: keep a referentially stable connectors list (#7090) 2023-08-08 08:46:01 -07:00
Charles Bachmeier
a53e773e5d refactor: shelve Details redesign for now (#7077)
* shelve Details redesign for now

* remove feature flag enum

* no longer used utils
2023-08-08 08:28:43 -07:00
cartcrom
5307d113a8 feat: eth output feature flag for uniswapx orders (#7078) 2023-08-08 11:23:46 -04:00
Nate Wienert
9e7d59f1fe chore: fix typecheck error due to jest and cypress overlapping types (#7101) 2023-08-07 18:40:43 -07:00
Brendan Wong
2ccd228f23 fix: cloud tests broken by path based (#7100)
fix: cloudfunctions
2023-08-07 20:18:14 -04:00
Nate Wienert
a726f67453 fix: bring back flake retries for flaky test (only flakes ~33% of time) (#7063) 2023-08-07 14:14:41 -10:00
Mike Grabowski
c9b4016b78 feat: use router depending on the origin (#6982)
* feat: initial commit

* chore: whitelist vercel

* chore: fix e2e test

* chore: update all occurences of #

* chore: tweaks

* chore: remove todo

* chore: revert changes

* chore: bring back old tweet logic and fix nested URLs

* chore: fix merge

* improve check

* fix: cypress

* chore: fix tests

* chore: update snapshot

* chore: update readme

* address review

* satisfy eslint

* chore: fix ui issue

* fix: tests

* fix: e2e test
2023-08-07 15:01:45 -07:00
eddie
a7135c9ab1 fix: check chainId in findCancelTx (#7098)
* fix: check chainId in findCancelTx

* fix: add comment explaining fix

---------

Co-authored-by: cartcrom <cartergcromer@gmail.com>
2023-08-07 13:58:33 -07:00
Jordan Frankfurt
a1b3776686 feat: vertical LP (#7000)
* Re-organize add liquidity page

* fix title alignment

* Update styles

* Update logic for disable/enable input on creation

* lint and clean up a couple small things

* lint and clean up a couple small things

* Tweak UI

* clean up code

* add back range selector

* remove inline styles

---------

Co-authored-by: Callil Capuozzo <callil@uniswap.org>
2023-08-07 14:22:45 -05:00
Brendan Wong
dc2225a2bc fix: update wrangler version (#7093)
* fix: update wrangler version

* yarn deduplicate

* Update .gitignore

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

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-08-07 12:56:59 -04:00
Nate Wienert
41571169c6 chore: align @reduxjs/toolkit dep versions with wallet (#7038) 2023-08-07 06:47:26 -10:00
Nate Wienert
7506dbb29c chore: update typescript to 4.9.4 to align with wallet repo (#7036)
* chore: update typescript to 4.9.4 to align with wallet repo

* fix: update mainnet block (#7042)

* fix: update mainnet block

* fix: typecheck

* fix: use deeplink instead of universal link (#7017)

* fix: use deeplink instead of universal link

* docs: added comment

* test: mock quotes to avoid errors logged (#7031)

* fix: change style of mobile pool buttons and menu (#7020)

* fix: menu flyout alignment overridden on mobile

* fix: change button order, sizing

* Replace deprecated media queries and text components

* fix: remove flakey token test (#7029)

* fix: reverted swap state (#7044)

* fix: wait for transaction status in ConfirmSwapModal

* fix: use actual swap status in ConfirmSwapModal

* feat: add test

* fix: shared hook

* fix: fix test

* fix: dont create Activity instance

* fix: clearing input on connect wallet (#6928)

* fix: clearing input on connect wallet

* update e2e tests

* fix: text wrap and spacing on mobile TDP (#6909)

fix text wrap and spacing on mobile TDP

* fix: top token charts on mobile (#6967)

* fix: top token charts on mobile

* Update TokenRow.test.tsx.snap

* Update src/components/Tokens/TokenTable/TokenRow.tsx

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

* Update src/components/Tokens/TokenTable/__snapshots__/TokenRow.test.tsx.snap

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

* update

* Update TokenRow.test.tsx.snap

* Update TokenRow.test.tsx.snap

---------

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

* test: deflake and clean universal search (#7034)

* fix: improve v2 network support (#7012)

* fix: improve v2 network support

* add an unsupported message to all v2 pages

* test: add v2 pool tests

* add guard on transaction callbacks

* fix: dep array

---------

Co-authored-by: eddie <66155195+just-toby@users.noreply.github.com>

* fix: failing nft buy test (#7049)

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
Co-authored-by: cartcrom <39385577+cartcrom@users.noreply.github.com>
Co-authored-by: gnewfield <18626088+gnewfield@users.noreply.github.com>
Co-authored-by: Brendan Wong <35351983+LunrEclipse@users.noreply.github.com>
Co-authored-by: eddie <66155195+just-toby@users.noreply.github.com>
Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
Co-authored-by: Jack Short <john.short.tj@gmail.com>
2023-08-04 10:52:49 -10:00
Nate Wienert
4f25c4a2bb chore: align @graphql-codegen dep versions with wallet (#7041) 2023-08-04 10:52:17 -10:00
570 changed files with 128843 additions and 12562 deletions

View File

@@ -1,5 +1,6 @@
/* eslint-env node */
const { node: restrictedImports } = require('@uniswap/eslint-config/restrictedImports')
require('@uniswap/eslint-config/load')
const rulesDirPlugin = require('eslint-plugin-rulesdir')
@@ -27,6 +28,7 @@ module.exports = {
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
'@typescript-eslint/no-restricted-imports': ['error', restrictedImports],
'import/no-restricted-paths': [
'error',
{

View File

@@ -1,32 +0,0 @@
name: Cache on main
description: caches node_modules/.cache, but only saves from main
inputs:
path:
description: 'A list of files, directories, and wildcard patterns to cache and store'
required: true
key:
description: 'An explicit key for restoring and saving the cache'
required: true
restore-keys:
description: 'An ordered list of keys to use for restoring stale cache if no cache hit occured for key. Note `cache-hit` returns false in this case.'
required: false
# Many build steps have their own caches to improve subsequent build times.
# Build tools are configured to cache to node_modules/.cache, so they are cached independently of node_modules.
# Caches are saved every run *on main* (by keying on github.run_id), and the most recent available cache is loaded.
# Caches are not saved on feature branches because they have limited utility, and extend the runtime of the workflow.
# See https://jongleberry.medium.com/speed-up-your-ci-and-dx-with-node-modules-cache-ac8df82b7bb0.
runs:
using: composite
steps:
- uses: actions/cache/restore@v3
with:
path: ${{ inputs.path }}
key: ${{ inputs.key }}
restore-keys: ${{ inputs.restore-keys }}
- if: github.ref_name == 'main'
uses: actions/cache/save@v3
with:
path: ${{ inputs.path }}
key: ${{ inputs.key }}

View File

@@ -23,9 +23,9 @@ runs:
- if: steps.install-cache.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile --ignore-scripts
shell: bash
# Validators compile quickly, so caching can be omitted.
- run: yarn ajv
# Run patch-package to apply patches to dependencies.
- run: yarn patch-package
shell: bash
# Contracts are compiled from source. If source hasn't changed, the contracts do not need to be re-compiled.
@@ -40,30 +40,10 @@ runs:
run: yarn contracts
shell: bash
# GraphQL is generated from schema and client-side graphql queries. The schema is always fetched and changes to
# client-side queries are hard to detect, so it is always re-generated.
# TODO(WEB-2498): Cache based on both fetched schema and client-side graphql queries.
# This will require some processing: cp all literal graphql tags into a separate file and hash it?
- run: yarn graphql
shell: bash
# Messages are extracted from source.
# A record of source file content hashes and catalogs is maintained in node_modules/.cache/lingui.
# Messages are always extracted, but extraction may short-circuit from the custom extractor's cache.
- uses: ./.github/actions/cache-on-main
with:
path: node_modules/.cache
key: ${{ runner.os }}-i18n-extract-${{ github.run_id }}
restore-keys: ${{ runner.os }}-i18n-extract-
- run: yarn i18n:extract
shell: bash
# Translations are compiled from messages. If messages haven't changed, the translations do not need to be re-compiled.
- uses: actions/cache@v3
id: i18n-compile-cache
with:
path: src/locales/*.js
key: ${{ runner.os }}-i18n-compile-${{ hashFiles('src/locales/*.po') }}
- if: steps.i18n-compile-cache.outputs.cache-hit !='true'
run: yarn i18n:compile
# These operations cannot be cached, so they are run concurrently
# - ajv: Validators compile quickly, so caching can be omitted.
# - graphql: GraphQL is generated from schema and client-side graphql queries. The schema is always fetched and
# changes to client-side queries are hard to detect, so it is always re-generated.
# - i18n: Messages are extracted from source and compiled. No caching extractor is available (out-of-the-box).
- run: yarn concurrently --max-processes=100% npm:ajv npm:graphql npm:i18n
shell: bash

View File

@@ -18,7 +18,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-eslint-${{ github.run_id }}
@@ -35,7 +35,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-tsc-${{ github.run_id }}
@@ -64,7 +64,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-jest-${{ github.run_id }}
@@ -81,22 +81,20 @@ jobs:
name: Unit tests
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
build-e2e:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-build-e2e-${{ github.run_id }}
restore-keys: ${{ runner.os }}-build-e2e-
- run: yarn build:e2e
env:
NODE_OPTIONS: "--max_old_space_size=4096"
path: node_modules/.swc
key: ${{ runner.os }}-swc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-swc-
- run: yarn build
- uses: actions/upload-artifact@v3
with:
name: build-e2e
name: build
path: build
if-no-files-found: error
@@ -105,7 +103,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-cypress-tsc-${{ github.run_id }}
@@ -124,7 +122,7 @@ jobs:
- run: exit 0
cypress-test-matrix:
needs: [build-e2e, cypress-rerun]
needs: [build, cypress-rerun]
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -133,7 +131,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
with:
path: /root/.cache/Cypress
key: ${{ runner.os }}-cypress-${{ hashFiles('**/node_modules/cypress/package.json') }}
@@ -143,10 +141,10 @@ jobs:
- uses: actions/download-artifact@v3
with:
name: build-e2e
name: build
path: build
- uses: ./.github/actions/cache-on-main
- uses: actions/cache/restore@v3
with:
path: cache
key: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-${{ github.run_id }}
@@ -175,23 +173,36 @@ jobs:
COMMIT_INFO_TIMESTAMP: ${{ github.event.pull_request.updated_at || github.event.head_commit.timestamp }}
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number }}
CYPRESS_PULL_REQUEST_URL: ${{ github.event.pull_request.html_url }}
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
flags: e2e-tests
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
with:
name: Cypress tests
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
- uses: actions/upload-artifact@v3
with:
name: hardhat-cache
path: cache
hardhat-cache:
needs: [cypress-test-matrix]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: hardhat-cache
path: cache
- uses: actions/cache/save@v3
with:
path: cache
key: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-${{ github.run_id }}
cloud-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
with:
path: node_modules/.cache
key: ${{ runner.os }}-cloud-tsc-${{ github.run_id }}
@@ -203,23 +214,23 @@ jobs:
name: Cloud typecheck
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
# TODO(WEB-2537): Setup CodeCOV
cloud-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: ./.github/actions/cache-on-main
- uses: actions/cache@v3
with:
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%
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
# 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:
name: Cloud tests
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
flags: cloud-tests
pre:
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}

3
.gitignore vendored
View File

@@ -20,6 +20,7 @@ schema.graphql
/coverage
/cache
/functions/coverage
/.swc
# builds
/build
@@ -52,3 +53,5 @@ cypress/videos
cypress/screenshots
.vercel
.wrangler

4
.husky/pre-commit Normal file
View File

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

36
.swcrc Normal file
View File

@@ -0,0 +1,36 @@
{
"$schema": "https://json.schemastore.org/swcrc",
// has to duplicate from package.json, see swc issue: https://swc.rs/docs/configuration/compilation#env
// this breaks jest because jest is setting target for some reason
// "env": {
// "targets": "> 0.5%, not dead"
// },
"jsc": {
// without this swc breaks WalletConnect class super() call
"target": "es2020",
"keepClassNames": true,
"experimental": {
"plugins": [
[
"@lingui/swc-plugin",
{}
],
[
"@swc/plugin-styled-components",
{
"displayName": true
}
]
]
},
"parser": {
"syntax": "typescript",
"tsx": true
},
"transform": {
"react": {
"runtime": "automatic"
}
}
}
}

1
CODEOWNERS Normal file
View File

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

View File

@@ -1,10 +0,0 @@
/* eslint-env node */
const isDev = process.env.NODE_ENV === 'development'
module.exports = {
styledComponents: {
fileName: isDev,
displayName: isDev,
},
}

View File

@@ -6,12 +6,12 @@ ignore:
- "**/instrumented/**/*"
- "**/styles/**/*"
- "styles/**/*"
- "**/styled.tsx"
- "**/constants/**/*"
- "constants/**/*"
coverage:
status:
# Omit merging unit/e2e reports into the defaults, as it is nonsensical.
project: off
patch: off
@@ -25,20 +25,16 @@ flag_management:
removed_code_behavior: adjust_base
if_ci_failed: error
- type: patch
target: 80%
target: 50%
individual_flags:
- name: unit-tests
- name: e2e-tests
# Wait until all machines have reported coverage - e2e tests run across 4 machines.
after_n_builds: 4
- name: cloud-tests
statuses:
- type: patch
target: 0%
- type: project
target: 80%
comment:
layout: flags
# Wait until all machines have reported coverage - e2e tests run across 4 machines + unit tests across 1.
after_n_builds: 5
hide_comment_details: false
github_checks:

View File

@@ -5,12 +5,15 @@ const { execSync } = require('child_process')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const path = require('path')
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
const { DefinePlugin, IgnorePlugin, ProvidePlugin } = require('webpack')
const TerserPlugin = require('terser-webpack-plugin')
const { IgnorePlugin, ProvidePlugin } = require('webpack')
const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin')
const commitHash = execSync('git rev-parse HEAD').toString().trim()
const isProduction = process.env.NODE_ENV === 'production'
process.env.REACT_APP_GIT_COMMIT_HASH = commitHash
// Linting and type checking are only necessary as part of development and testing.
// Omit them from production builds, as they slow down the feedback loop.
const shouldLintOrTypeCheck = !isProduction
@@ -21,32 +24,6 @@ function getCacheDirectory(cacheName) {
}
module.exports = {
babel: {
plugins: [
'@vanilla-extract/babel-plugin',
...(process.env.REACT_APP_ADD_COVERAGE_INSTRUMENTATION
? [
[
'istanbul',
{
all: true,
include: ['src/**/*.tsx', 'src/**/*.ts'],
exclude: [
'src/**/*.css',
'src/**/*.css.ts',
'src/**/*.test.ts',
'src/**/*.test.tsx',
'src/**/*.spec.ts',
'src/**/*.spec.tsx',
'src/**/graphql/**/*',
'src/**/*.d.ts',
],
},
],
]
: []),
],
},
eslint: {
enable: shouldLintOrTypeCheck,
pluginOptions(eslintConfig) {
@@ -69,16 +46,20 @@ module.exports = {
configure(jestConfig) {
return Object.assign(jestConfig, {
cacheDirectory: getCacheDirectory('jest'),
transform: Object.assign(jestConfig.transform, {
transform: {
...Object.entries(jestConfig.transform).reduce((transform, [key, value]) => {
if (value.match(/babel/)) return transform
return { ...transform, [key]: value }
}, {}),
// Transform vanilla-extract using its own transformer.
// See https://sandroroth.com/blog/vanilla-extract-cra#jest-transform.
'\\.css\\.ts$': '@vanilla-extract/jest-transform',
}),
// Use @uniswap/conedison's build directly, as jest does not support its exports.
transformIgnorePatterns: ['@uniswap/conedison/format', '@uniswap/conedison/provider'],
'\\.(t|j)sx?$': '@swc/jest',
},
// Use d3-arrays's build directly, as jest does not support its exports.
transformIgnorePatterns: ['d3-array'],
moduleNameMapper: {
'@uniswap/conedison/format': '@uniswap/conedison/dist/format',
'@uniswap/conedison/provider': '@uniswap/conedison/dist/provider',
'd3-array': 'd3-array/dist/d3-array.min.js',
},
})
},
@@ -88,12 +69,9 @@ module.exports = {
// Webpack 5 does not polyfill node globals, so we do so for those necessary:
new ProvidePlugin({
// - react-markdown requires process.cwd
process: 'process/browser',
process: 'process/browser.js',
}),
// vanilla-extract has poor performance on M1 machines with 'debug' identifiers, so we use 'short' instead.
// See https://vanilla-extract.style/documentation/integrations/webpack/#identifiers for docs.
// See https://github.com/vanilla-extract-css/vanilla-extract/issues/771#issuecomment-1249524366.
new VanillaExtractPlugin({ identifiers: 'short' }),
new VanillaExtractPlugin(),
new RetryChunkLoadPlugin({
cacheBust: `function() {
return 'cache-bust=' + Date.now();
@@ -109,14 +87,6 @@ module.exports = {
// Configure webpack plugins:
webpackConfig.plugins = webpackConfig.plugins
.map((plugin) => {
// Extend process.env with dynamic values (eg commit hash).
// This will make dynamic values available to JavaScript only, not to interpolated HTML (ie index.html).
if (plugin instanceof DefinePlugin) {
Object.assign(plugin.definitions['process.env'], {
REACT_APP_GIT_COMMIT_HASH: JSON.stringify(commitHash),
})
}
// CSS ordering is mitigated through scoping / naming conventions, so we can ignore order warnings.
// See https://webpack.js.org/plugins/mini-css-extract-plugin/#remove-order-warnings.
if (plugin instanceof MiniCssExtractPlugin) {
@@ -163,13 +133,9 @@ module.exports = {
// Configure webpack transpilation (create-react-app specifies transpilation rules in a oneOf):
webpackConfig.module.rules[1].oneOf = webpackConfig.module.rules[1].oneOf.map((rule) => {
// The fallback rule (eg for dependencies).
if (rule.loader && rule.loader.match(/babel-loader/) && !rule.include) {
// Allow not-fully-specified modules so that legacy packages are still able to build.
rule.resolve = { fullySpecified: false }
// The class properties transform is required for @uniswap/analytics to build.
rule.options.plugins.push('@babel/plugin-proposal-class-properties')
if (rule.loader && rule.loader.match(/babel-loader/)) {
rule.loader = 'swc-loader'
delete rule.options
}
return rule
})
@@ -177,6 +143,15 @@ module.exports = {
// Configure webpack optimization:
webpackConfig.optimization = Object.assign(
webpackConfig.optimization,
{
minimize: isProduction,
minimizer: [
new TerserPlugin({
minify: TerserPlugin.swcMinify,
parallel: require('os').cpus().length,
}),
],
},
isProduction
? {
splitChunks: {
@@ -192,16 +167,15 @@ module.exports = {
: {}
)
// Configure webpack caching:
webpackConfig.cache = Object.assign(webpackConfig.cache, {
cacheDirectory: getCacheDirectory('webpack'),
})
// Configure webpack resolution. webpackConfig.cache is unused with swc-loader, but the resolver can still cache:
webpackConfig.resolve = Object.assign(webpackConfig.resolve, { unsafeCache: true })
// Ignore failed source mappings to avoid spamming the console.
// Source mappings for a package will fail if the package does not provide them, but the build will still succeed,
// so it is unnecessary (and bothersome) to log it. This should be turned off when debugging missing sourcemaps.
// See https://webpack.js.org/loaders/source-map-loader#ignoring-warnings.
webpackConfig.ignoreWarnings = [/Failed to parse source map/]
webpackConfig.ignoreWarnings = [
// Source mappings for a package will fail if the package does not provide them, but the build will still succeed,
// so it is unnecessary (and bothersome) to log it. This should be turned off when debugging missing sourcemaps.
// See https://webpack.js.org/loaders/source-map-loader#ignoring-warnings.
/Failed to parse source map/,
]
return webpackConfig
},

View File

@@ -1,4 +1,3 @@
import codeCoverageTask from '@cypress/code-coverage/task'
import { defineConfig } from 'cypress'
import { setupHardhatEvents } from 'cypress-hardhat'
@@ -12,7 +11,6 @@ export default defineConfig({
e2e: {
async setupNodeEvents(on, config) {
await setupHardhatEvents(on, config)
codeCoverageTask(on, config)
return config
},
baseUrl: 'http://localhost:3000',

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

@@ -6,6 +6,7 @@ describe('Swap settings', () => {
cy.visit('/swap', { featureFlags: [FeatureFlag.uniswapXEnabled] })
cy.contains('Settings').should('not.exist')
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.get(getTestSelector('mobile-settings-menu')).should('not.exist')
cy.contains('Max slippage').should('exist')
cy.contains('Transaction deadline').should('exist')
cy.contains('UniswapX').should('exist')
@@ -13,4 +14,16 @@ describe('Swap settings', () => {
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.contains('Settings').should('not.exist')
})
it('should open the mobile settings menu', () => {
cy.viewport('iphone-6')
cy.visit('/swap', { featureFlags: [FeatureFlag.uniswapXEnabled] })
cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.get(getTestSelector('mobile-settings-menu')).should('exist')
cy.contains('Max slippage').should('exist')
cy.contains('Transaction deadline').should('exist')
cy.contains('UniswapX').should('exist')
cy.contains('Local routing').should('exist')
cy.get(getTestSelector('mobile-settings-scrim')).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

@@ -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,7 +24,7 @@ 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', '$')
@@ -30,24 +35,38 @@ describe('Universal search bar', () => {
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')
})
it('should go to the selected result when recent results are shown', () => {
// Seed recent results with UNI.
openSearch()
getSearchBar().type('uni')
cy.get(getTestSelector('searchbar-token-row-UNI'))
getSearchBar().clear().type('{esc}')
it(
'should go to the selected result when recent results are shown',
// this test is experiencing flake despite being correct, i can see the right value in DOM
// but for some reason cypress doesn't find it, so adding retries for now :/
{
// @ts-ignore see https://uniswapteam.slack.com/archives/C047U65H422/p1691455547556309
// basically cypress has bad types due to overlap with jest and you just have to deal with it
// i tried removing jest types but still happens
retries: {
runMode: 3,
openMode: 3,
},
},
() => {
// Seed recent results with UNI.
openSearch()
getSearchBar().type('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'))
// Search a different token by name.
openSearch()
getSearchBar().type('eth')
cy.get(getTestSelector('searchbar-token-row-ETHEREUM-NATIVE'))
// Validate that we go to the searched/selected result.
getSearchBar().type('{enter}')
cy.url().should('contain', 'tokens/ethereum/NATIVE')
})
// Validate that we go to the searched/selected result.
getSearchBar().type('{enter}')
cy.url().should('contain', 'tokens/ethereum/NATIVE')
}
)
})

View File

@@ -1,3 +1,5 @@
import { FeatureFlag } from 'featureFlags'
import { getTestSelector } from '../utils'
describe('Wallet Dropdown', () => {
@@ -21,10 +23,14 @@ 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('hash').should('match', /\?lng=af-ZA$/)
cy.contains('Uniswap available in: English')
@@ -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

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

@@ -5,7 +5,6 @@
// https://on.cypress.io/configuration
// ***********************************************************
import '@cypress/code-coverage/support'
import './commands'
import './setupTests'

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

18
functions/[[index]].ts Normal file
View File

@@ -0,0 +1,18 @@
/* eslint-disable import/no-unused-modules */
import { MetaTagInjector } from './components/metaTagInjector'
export const onRequest: PagesFunction = async ({ request, next }) => {
const imageUri = new URL(request.url).origin + '/images/1200x630_Rich_Link_Preview_Image.png'
const data = {
title: 'Uniswap Interface',
image: imageUri,
url: request.url,
description: 'Swap or provide liquidity on the Uniswap Protocol',
}
const res = next()
try {
return new HTMLRewriter().on('head', new MetaTagInjector(data)).transform(await res)
} catch (e) {
return res
}
}

View File

@@ -0,0 +1,71 @@
/* eslint-disable import/no-unused-modules */
import { ImageResponse } from '@vercel/og'
import React from 'react'
import { WATERMARK_URL } from '../../../../constants'
import getAsset from '../../../../utils/getAsset'
import getFont from '../../../../utils/getFont'
import { getRequest } from '../../../../utils/getRequest'
export const onRequest: PagesFunction = async ({ params, request }) => {
try {
const origin = new URL(request.url).origin
const { index } = params
const collectionAddress = index[0]?.toString()
const tokenId = index[1]?.toString()
const cacheUrl = origin + '/nfts/asset/' + collectionAddress + '/' + tokenId
const data = await getRequest(
cacheUrl,
() => getAsset(collectionAddress, tokenId, cacheUrl),
(data): data is NonNullable<Awaited<ReturnType<typeof getAsset>>> => Boolean(data.ogImage)
)
if (!data) {
return new Response('Asset not found.', { status: 404 })
}
const fontData = await getFont(origin)
return new ImageResponse(
(
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
width: '1200px',
height: '630px',
}}
>
<img src={data.ogImage} alt={data.title} width="1200px" />
<div
style={{
position: 'absolute',
bottom: '72px',
right: '72px',
display: 'flex',
gap: '24px',
}}
>
<img src={WATERMARK_URL} alt="Uniswap" height="72px" width="324px" />
</div>
</div>
),
{
width: 1200,
height: 630,
fonts: [
{
name: 'Inter',
data: fontData,
style: 'normal',
},
],
}
) as Response
} catch (error: any) {
return new Response(error.message || error.toString(), { status: 500 })
}
}

View File

@@ -0,0 +1,20 @@
const assetImageUrl = [
'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/804',
'http://127.0.0.1:3000/api/image/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947',
]
test.each(assetImageUrl)('assetImageUrl', async (url) => {
const response = await fetch(new Request(url))
expect(response.status).toBe(200)
expect(response.headers.get('content-type')).toBe('image/png')
})
const invalidAssetImageUrl = [
'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/10001',
'http://127.0.0.1:3000/api/image/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/44700',
]
test.each(invalidAssetImageUrl)('invalidAssetImageUrl', async (url) => {
const response = await fetch(new Request(url))
expect(response.status).toBe(404)
})

View File

@@ -0,0 +1,117 @@
/* eslint-disable import/no-unused-modules */
import { ImageResponse } from '@vercel/og'
import React from 'react'
import { CHECK_URL, WATERMARK_URL } from '../../../../constants'
import getCollection from '../../../../utils/getCollection'
import getColor from '../../../../utils/getColor'
import getFont from '../../../../utils/getFont'
import { getRequest } from '../../../../utils/getRequest'
export const onRequest: PagesFunction = async ({ params, request }) => {
try {
const origin = new URL(request.url).origin
const { index } = params
const collectionAddress = index?.toString()
const cacheUrl = origin + '/nfts/collection/' + collectionAddress
const data = await getRequest(
cacheUrl,
() => getCollection(collectionAddress, cacheUrl),
(data): data is NonNullable<Awaited<ReturnType<typeof getCollection>>> =>
Boolean(data.ogImage && data.name && data.isVerified)
)
if (!data) {
return new Response('Collection not found.', { status: 404 })
}
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(' ')
return new ImageResponse(
(
<div
style={{
backgroundColor: 'black',
display: 'flex',
width: '1200px',
height: '630px',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
backgroundColor: `rgba(${palette[0]}, ${palette[1]}, ${palette[2]}, 0.75)`,
padding: '72px',
}}
>
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-end',
gap: '48px',
width: '100%',
}}
>
<img
src={data.ogImage}
alt={data.name}
width="500px"
height="500px"
style={{
borderRadius: '60px',
objectFit: 'cover',
}}
/>
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '32px',
width: '45%',
}}
>
<div
style={{
gap: '12px',
fontSize: '72px',
fontFamily: 'Inter',
color: 'white',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
}}
>
{words.map((word: string) => (
<text key={word + index}>{word}</text>
))}
{data.isVerified && <img src={CHECK_URL} height="54px" />}
</div>
<img src={WATERMARK_URL} alt="Uniswap" height="72px" width="324px" />
</div>
</div>
</div>
</div>
),
{
width: 1200,
height: 630,
fonts: [
{
name: 'Inter',
data: fontData,
style: 'normal',
},
],
}
) as Response
} catch (error: any) {
return new Response(error.message || error.toString(), { status: 500 })
}
}

View File

@@ -0,0 +1,20 @@
const collectionImageUrl = [
'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544',
'http://127.0.0.1:3000/api/image/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
'http://127.0.0.1:3000/api/image/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b',
]
test.each(collectionImageUrl)('collectionImageUrl', async (url) => {
const response = await fetch(new Request(url))
expect(response.status).toBe(200)
expect(response.headers.get('content-type')).toBe('image/png')
})
const invalidCollectionImageUrl = [
'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545',
]
test.each(invalidCollectionImageUrl)('invalidAssetImageUrl', async (url) => {
const response = await fetch(new Request(url))
expect(response.status).toBe(404)
})

View File

@@ -0,0 +1,177 @@
/* eslint-disable import/no-unused-modules */
import { ImageResponse } from '@vercel/og'
import React from 'react'
import { WATERMARK_URL } from '../../../constants'
import getColor from '../../../utils/getColor'
import getFont from '../../../utils/getFont'
import getNetworkLogoUrl from '../../../utils/getNetworkLogoURL'
import { getRequest } from '../../../utils/getRequest'
import getToken from '../../../utils/getToken'
export const onRequest: PagesFunction = async ({ params, request }) => {
try {
const origin = new URL(request.url).origin
const { index } = params
const networkName = String(index[0])
const tokenAddress = String(index[1])
const cacheUrl = origin + '/tokens/' + networkName + '/' + tokenAddress
const data = await getRequest(
cacheUrl,
() => getToken(networkName, tokenAddress, cacheUrl),
(data): data is NonNullable<Awaited<ReturnType<typeof getToken>>> => Boolean(data.symbol && data.name)
)
if (!data) {
return new Response('Token not found.', { status: 404 })
}
const [fontData, palette] = await Promise.all([getFont(origin), getColor(data.ogImage, true)])
const networkLogo = getNetworkLogoUrl(networkName.toUpperCase(), origin)
// Capitalize name such that each word starts with a capital letter
let words = data.name.split(' ')
words = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
let name = words.join(' ')
name = name.trim()
return new ImageResponse(
(
<div
style={{
backgroundColor: 'black',
display: 'flex',
width: '1200px',
height: '630px',
}}
>
<div
style={{
display: 'flex',
backgroundColor: `rgba(${palette[0]}, ${palette[1]}, ${palette[2]})`,
alignItems: 'center',
height: '100%',
padding: '72px',
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
alignItems: 'flex-start',
width: '100%',
height: '100%',
color: 'white',
}}
>
{data.ogImage ? (
<img src={data.ogImage} width="144px" style={{ borderRadius: '100%' }}>
{networkLogo != '' && (
<img
src={networkLogo}
width="48px"
style={{
position: 'absolute',
right: '2px',
bottom: '0px',
borderRadius: '100%',
}}
/>
)}
</img>
) : (
<div
style={{
width: '144px',
height: '144px',
borderRadius: '100%',
backgroundColor: 'rgba(255, 255, 255, 0.12)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div
style={{
fontFamily: 'Inter',
fontSize: '48px',
lineHeight: '58px',
color: 'white',
}}
>
{data.name.slice(0, 3).toUpperCase()}
</div>
{networkLogo != '' && (
<img
src={networkLogo}
width="48px"
style={{
position: 'absolute',
right: '2px',
bottom: '0px',
borderRadius: '100%',
}}
/>
)}
</div>
)}
<div
style={{
fontFamily: 'Inter',
fontSize: '72px',
lineHeight: '72px',
marginLeft: '-5px',
marginTop: '24px',
}}
>
{name}
</div>
<div
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-end',
width: '100%',
}}
>
<div
style={{
fontFamily: 'Inter',
fontSize: '168px',
lineHeight: '133px',
marginLeft: '-13px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
width: '100%',
}}
>
{data.symbol}
</div>
<img src={WATERMARK_URL} alt="Uniswap" height="72px" width="324px" />
</div>
</div>
</div>
</div>
),
{
width: 1200,
height: 630,
fonts: [
{
name: 'Inter',
data: fontData,
style: 'normal',
},
],
}
) as Response
} catch (error: any) {
return new Response(error.message || error.toString(), { status: 500 })
}
}

View File

@@ -0,0 +1,22 @@
const tokenImageUrl = [
'http://127.0.0.1:3000/api/image/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
'http://127.0.0.1:3000/api/image/tokens/ethereum/NATIVE',
]
test.each(tokenImageUrl)('tokenImageUrl', async (url) => {
const response = await fetch(new Request(url))
expect(response.status).toBe(200)
expect(response.headers.get('content-type')).toBe('image/png')
})
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/potato/?potato=1',
]
test.each(invalidTokenImageUrl)('invalidAssetImageUrl', async (url) => {
const response = await fetch(new Request(url))
expect(response.status).toBe(404)
})

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

@@ -2,6 +2,7 @@ type MetaTagInjectorInput = {
title: string
image?: string
url: string
description?: string
}
/**
@@ -18,6 +19,9 @@ export class MetaTagInjector implements HTMLRewriterElementContentHandlers {
element(element: Element) {
//Open Graph Tags
this.append(element, 'og:title', this.input.title)
if (this.input.description) {
this.append(element, 'og:description', this.input.description)
}
if (this.input.image) {
this.append(element, 'og:image', this.input.image)
this.append(element, 'og:image:width', '1200')

76
functions/constants.ts Normal file
View File

@@ -0,0 +1,76 @@
export const WATERMARK_URL = 'https://app.uniswap.org/images/324x74_App_Watermark.png'
export const CHECK_URL = 'https://app.uniswap.org/images/54x54_Verified_Check.svg'
export const DEFAULT_COLOR = [35, 43, 43]
export const predefinedTokenColors: { [key: string]: number[] } = {
// old WBTC
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599/logo.png':
[240, 146, 65],
// new WBTC
'https://assets.coingecko.com/coins/images/7598/large/wrapped_bitcoin_wbtc.png?1548822744': [240, 146, 65],
// DAI
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png':
[250, 176, 27],
// UNI
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/logo.png':
[230, 53, 140],
// BUSD
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x4Fabb145d64652a948d72533023f6E7A623C7C53/logo.png':
[239, 186, 9],
// AI-X
'https://s2.coinmarketcap.com/static/img/coins/64x64/26984.png': [41, 161, 241],
// ETH
'https://token-icons.s3.amazonaws.com/eth.png': [73, 112, 213],
// HARRYPOTTERSHIBAINUBITCOIN
'https://assets.coingecko.com/coins/images/30323/large/hpos10i_logo_casino_night-dexview.png?1684117567': [
222, 49, 16,
],
// PEPE
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6982508145454Ce325dDbE47a25d4ec3d2311933/logo.png':
[62, 174, 20],
// Unibot V2
'https://s2.coinmarketcap.com/static/img/coins/64x64/25436.png': [74, 10, 79],
// UNIBOT v1
'https://assets.coingecko.com/coins/images/30462/small/logonoline_%281%29.png?1687510315': [74, 10, 79],
// USDC
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png':
[0, 102, 217],
// HEX
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png':
[249, 63, 140],
// MONG
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x1ce270557C1f68Cfb577b856766310Bf8B47FD9C/logo.png':
[169, 109, 255],
// ARB
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1/logo.png':
[41, 161, 241],
// PSYOP
'https://s2.coinmarketcap.com/static/img/coins/64x64/25422.png': [232, 143, 0],
// MATIC
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png':
[169, 109, 255],
// TURBO
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA35923162C49cF95e6BF26623385eb431ad920D3/logo.png':
[189, 110, 41],
// AIDOGE
'https://assets.coingecko.com/coins/images/29852/large/photo_2023-04-18_14-25-28.jpg?1681799160': [41, 161, 241],
// SIMPSON
'https://assets.coingecko.com/coins/images/30243/large/1111.png?1683692033': [232, 143, 0],
// OX
'https://assets.coingecko.com/coins/images/30604/large/Logo2.png?1685522119': [41, 89, 217],
// ANGLE
'https://assets.coingecko.com/coins/images/19060/large/ANGLE_Token-light.png?1666774221': [255, 85, 85],
// APE
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x4d224452801ACEd8B2F0aebE155379bb5D594381/logo.png':
[5, 74, 169],
// GUSD
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd/logo.png':
[0, 164, 189],
// OGN
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x8207c1FfC5B6804F6024322CcF34F29c3541Ae26/logo.png':
[5, 74, 169],
// RPL
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xD33526068D116cE69F19A9ee46F0bd304F21A51f/logo.png':
[255, 123, 79],
}

22
functions/default.test.ts Normal file
View File

@@ -0,0 +1,22 @@
const defaultUrls = ['http://127.0.0.1:3000/', 'http://127.0.0.1:3000/swap', 'http://127.0.0.1:3000/pools']
test.each(defaultUrls)('should inject metadata for valid collections', async (defaultUrl) => {
const body = await fetch(new Request(defaultUrl)).then((res) => res.text())
expect(body).toContain(`<meta property="og:title" content="Uniswap Interface"/>`)
expect(body).toContain(
`<meta property="og:description" content="Swap or provide liquidity on the Uniswap Protocol"/>`
)
expect(body).toContain(
`<meta property="og:image" content="http://127.0.0.1:3000/images/1200x630_Rich_Link_Preview_Image.png"/>`
)
expect(body).toContain(`<meta property="og:image:width" content="1200"/>`)
expect(body).toContain(`<meta property="og:image:height" content="630"/>`)
expect(body).toContain(`<meta property="og:type" content="website"/>`)
expect(body).toContain(`<meta property="og:image:alt" content="Uniswap Interface"/>`)
expect(body).toContain(`<meta property="twitter:card" content="summary_large_image"/>`)
expect(body).toContain(`<meta property="twitter:title" content="Uniswap Interface"/>`)
expect(body).toContain(
`<meta property="twitter:image" content="http://127.0.0.1:3000/images/1200x630_Rich_Link_Preview_Image.png"/>`
)
expect(body).toContain(`<meta property="twitter:image:alt" content="Uniswap Interface"/>`)
})

View File

@@ -4,6 +4,16 @@ module.exports = async function globalSetup() {
globalThis.servers = await setup({
command: `yarn start:cloud`,
port: 3000,
launchTimeout: 50000,
launchTimeout: 120000, // takes ~2m on CI
})
// Wait for wrangler to return a request before running tests
for (let i = 0; i < 3; i++) {
const res = await fetch(new Request('http://127.0.0.1:3000/tokens/ethereum/NATIVE'))
if (res.ok) {
return
}
// Set timeout to make sure the server isn't flooded with requests if wrangler is not running
await new Promise((resolve) => setTimeout(resolve, 500 * (i + 1)))
}
throw new Error('Failed to start server')
}

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

@@ -1,6 +1,6 @@
/* eslint-disable import/no-unused-modules */
import getAsset from '../../utils/getAsset'
import getRequest from '../../utils/getRequest'
import { getMetadataRequest } from '../../utils/getRequest'
export const onRequest: PagesFunction = async ({ params, request, next }) => {
const res = next()
@@ -8,7 +8,7 @@ export const onRequest: PagesFunction = async ({ params, request, next }) => {
const { index } = params
const collectionAddress = index[0]?.toString()
const tokenId = index[1]?.toString()
return getRequest(res, request.url, () => getAsset(collectionAddress, tokenId, request.url))
return getMetadataRequest(res, request.url, () => getAsset(collectionAddress, tokenId, request.url))
} catch (e) {
return res
}

View File

@@ -7,15 +7,14 @@ exports[`should inject metadata for valid assets 1`] = `
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
@@ -36,9 +35,9 @@ exports[`should inject metadata for valid assets 1`] = `
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="manifest" href="/manifest.json" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@@ -55,9 +54,9 @@ exports[`should inject metadata for valid assets 1`] = `
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
src: url(/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(/fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
@@ -116,7 +115,7 @@ exports[`should inject metadata for valid assets 1`] = `
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Azuki #2550"/><meta property="og:image" content="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/2550/d268b7f60a56306ced68b9762709ceaff4f1ee939f3150e7363fae300a59da12.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Azuki #2550"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/2550"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Azuki #2550"/><meta property="twitter:image" content="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/2550/d268b7f60a56306ced68b9762709ceaff4f1ee939f3150e7363fae300a59da12.png"/><meta property="twitter:image:alt" content="Azuki #2550"/></head>
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Azuki #2550"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/2550"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Azuki #2550"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/2550"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Azuki #2550"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/2550"/><meta property="twitter:image:alt" content="Azuki #2550"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@@ -139,15 +138,14 @@ exports[`should inject metadata for valid assets 2`] = `
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
@@ -168,9 +166,9 @@ exports[`should inject metadata for valid assets 2`] = `
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="manifest" href="/manifest.json" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@@ -187,9 +185,9 @@ exports[`should inject metadata for valid assets 2`] = `
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
src: url(/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(/fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
@@ -248,7 +246,7 @@ exports[`should inject metadata for valid assets 2`] = `
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Bored Ape Yacht Club #3735"/><meta property="og:image" content="https://cdn.center.app/v2/1/697f69bb495aaa24c66638cae921977354f0b8274fc2e2814e455f355e67f01d/88c2ac6b73288e41051d3fd58ff3cef1f4908403f05f4a7d2a8435d003758529.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Bored Ape Yacht Club #3735"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/3735"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Bored Ape Yacht Club #3735"/><meta property="twitter:image" content="https://cdn.center.app/v2/1/697f69bb495aaa24c66638cae921977354f0b8274fc2e2814e455f355e67f01d/88c2ac6b73288e41051d3fd58ff3cef1f4908403f05f4a7d2a8435d003758529.png"/><meta property="twitter:image:alt" content="Bored Ape Yacht Club #3735"/></head>
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Bored Ape Yacht Club #3735"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/3735"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Bored Ape Yacht Club #3735"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/3735"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Bored Ape Yacht Club #3735"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/3735"/><meta property="twitter:image:alt" content="Bored Ape Yacht Club #3735"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@@ -271,15 +269,14 @@ exports[`should inject metadata for valid assets 3`] = `
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
@@ -300,9 +297,9 @@ exports[`should inject metadata for valid assets 3`] = `
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="manifest" href="/manifest.json" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@@ -319,9 +316,9 @@ exports[`should inject metadata for valid assets 3`] = `
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
src: url(/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(/fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
@@ -380,7 +377,7 @@ exports[`should inject metadata for valid assets 3`] = `
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="CryptoPunk #3947"/><meta property="og:image" content="https://cdn.center.app/1/0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/3947/62319d784e7a816d190aa184ffe58550d6ed8eb2e117b218e2ac02f126538ee6.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="CryptoPunk #3947"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="CryptoPunk #3947"/><meta property="twitter:image" content="https://cdn.center.app/1/0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/3947/62319d784e7a816d190aa184ffe58550d6ed8eb2e117b218e2ac02f126538ee6.png"/><meta property="twitter:image:alt" content="CryptoPunk #3947"/></head>
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="CryptoPunk #3947"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="CryptoPunk #3947"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="CryptoPunk #3947"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947"/><meta property="twitter:image:alt" content="CryptoPunk #3947"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -3,22 +3,19 @@ const assets = [
address: '0xed5af388653567af2f388e6224dc7c4b3241c544',
assetId: '2550',
collectionName: 'Azuki',
image:
'https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/2550/d268b7f60a56306ced68b9762709ceaff4f1ee939f3150e7363fae300a59da12.png',
image: 'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/2550',
},
{
address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
assetId: '3735',
collectionName: 'Bored Ape Yacht Club',
image:
'https://cdn.center.app/v2/1/697f69bb495aaa24c66638cae921977354f0b8274fc2e2814e455f355e67f01d/88c2ac6b73288e41051d3fd58ff3cef1f4908403f05f4a7d2a8435d003758529.png',
image: 'http://127.0.0.1:3000/api/image/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/3735',
},
{
address: '0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb',
assetId: '3947',
collectionName: 'CryptoPunk',
image:
'https://cdn.center.app/1/0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/3947/62319d784e7a816d190aa184ffe58550d6ed8eb2e117b218e2ac02f126538ee6.png',
image: 'http://127.0.0.1:3000/api/image/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947',
},
]
@@ -27,6 +24,7 @@ test.each(assets)('should inject metadata for valid assets', async (nft) => {
const body = await fetch(new Request(url)).then((res) => res.text())
expect(body).toMatchSnapshot()
expect(body).toContain(`<meta property="og:title" content="${nft.collectionName} #${nft.assetId}"/>`)
expect(body).not.toContain(`<meta property="og:description"`)
expect(body).toContain(`<meta property="og:image" content="${nft.image}"/>`)
expect(body).toContain(`<meta property="og:image:width" content="1200"/>`)
expect(body).toContain(`<meta property="og:image:height" content="630"/>`)

View File

@@ -1,13 +1,13 @@
/* eslint-disable import/no-unused-modules */
import getCollection from '../../utils/getCollection'
import getRequest from '../../utils/getRequest'
import { getMetadataRequest } from '../../utils/getRequest'
export const onRequest: PagesFunction = async ({ params, request, next }) => {
const res = next()
try {
const { index } = params
const collectionAddress = index?.toString()
return getRequest(res, request.url, () => getCollection(collectionAddress, request.url))
return getMetadataRequest(res, request.url, () => getCollection(collectionAddress, request.url))
} catch (e) {
return res
}

View File

@@ -7,15 +7,14 @@ exports[`should inject metadata for valid collections 1`] = `
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
@@ -36,9 +35,9 @@ exports[`should inject metadata for valid collections 1`] = `
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="manifest" href="/manifest.json" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@@ -55,9 +54,9 @@ exports[`should inject metadata for valid collections 1`] = `
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
src: url(/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(/fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
@@ -116,7 +115,7 @@ exports[`should inject metadata for valid collections 1`] = `
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Azuki on Uniswap"/><meta property="og:image" content="https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Azuki on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Azuki on Uniswap"/><meta property="twitter:image" content="https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format"/><meta property="twitter:image:alt" content="Azuki on Uniswap"/></head>
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Azuki on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Azuki on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Azuki on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544"/><meta property="twitter:image:alt" content="Azuki on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@@ -139,15 +138,14 @@ exports[`should inject metadata for valid collections 2`] = `
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
@@ -168,9 +166,9 @@ exports[`should inject metadata for valid collections 2`] = `
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="manifest" href="/manifest.json" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@@ -187,9 +185,9 @@ exports[`should inject metadata for valid collections 2`] = `
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
src: url(/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(/fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
@@ -248,7 +246,7 @@ exports[`should inject metadata for valid collections 2`] = `
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Bored Ape Yacht Club on Uniswap"/><meta property="og:image" content="https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Bored Ape Yacht Club on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Bored Ape Yacht Club on Uniswap"/><meta property="twitter:image" content="https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format"/><meta property="twitter:image:alt" content="Bored Ape Yacht Club on Uniswap"/></head>
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Bored Ape Yacht Club on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Bored Ape Yacht Club on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Bored Ape Yacht Club on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"/><meta property="twitter:image:alt" content="Bored Ape Yacht Club on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@@ -271,15 +269,14 @@ exports[`should inject metadata for valid collections 3`] = `
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
@@ -300,9 +297,9 @@ exports[`should inject metadata for valid collections 3`] = `
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="manifest" href="/manifest.json" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@@ -319,9 +316,9 @@ exports[`should inject metadata for valid collections 3`] = `
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
src: url(/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(/fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
@@ -380,7 +377,7 @@ exports[`should inject metadata for valid collections 3`] = `
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="og:image" content="https://i.seadn.io/gae/XN0XuD8Uh3jyRWNtPTFeXJg_ht8m5ofDx6aHklOiy4amhFuWUa0JaR6It49AH8tlnYS386Q0TW_-Lmedn0UET_ko1a3CbJGeu5iHMg?w=500&auto=format"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="twitter:image" content="https://i.seadn.io/gae/XN0XuD8Uh3jyRWNtPTFeXJg_ht8m5ofDx6aHklOiy4amhFuWUa0JaR6It49AH8tlnYS386Q0TW_-Lmedn0UET_ko1a3CbJGeu5iHMg?w=500&auto=format"/><meta property="twitter:image:alt" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/></head>
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b"/><meta property="twitter:image:alt" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -2,20 +2,17 @@ const collections = [
{
address: '0xed5af388653567af2f388e6224dc7c4b3241c544',
collectionName: 'Azuki',
image:
'https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format',
image: 'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544',
},
{
address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
collectionName: 'Bored Ape Yacht Club',
image:
'https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format',
image: 'http://127.0.0.1:3000/api/image/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
},
{
address: '0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b',
collectionName: 'CLONE X - X TAKASHI MURAKAMI',
image:
'https://i.seadn.io/gae/XN0XuD8Uh3jyRWNtPTFeXJg_ht8m5ofDx6aHklOiy4amhFuWUa0JaR6It49AH8tlnYS386Q0TW_-Lmedn0UET_ko1a3CbJGeu5iHMg?w=500&auto=format',
image: 'http://127.0.0.1:3000/api/image/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b',
},
]
@@ -24,6 +21,7 @@ test.each(collections)('should inject metadata for valid collections', async (co
const body = await fetch(new Request(url)).then((res) => res.text())
expect(body).toMatchSnapshot()
expect(body).toContain(`<meta property="og:title" content="${collection.collectionName} on Uniswap"/>`)
expect(body).not.toContain(`<meta property="og:description"`)
expect(body).toContain(`<meta property="og:image" content="${collection.image}"/>`)
expect(body).toContain(`<meta property="og:image:width" content="1200"/>`)
expect(body).toContain(`<meta property="og:image:height" content="630"/>`)
@@ -38,9 +36,7 @@ test.each(collections)('should inject metadata for valid collections', async (co
const invalidCollections = [
'http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545',
'http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545/10',
'http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545//',
'http://127.0.0.1:3000/nfts/collection',
]
test.each(invalidCollections)(

View File

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

View File

@@ -1,33 +1,17 @@
/* eslint-disable import/no-unused-modules */
import { Chain } from '../../src/graphql/data/__generated__/types-and-hooks'
import getRequest from '../utils/getRequest'
import { getMetadataRequest } from '../utils/getRequest'
import getToken from '../utils/getToken'
const convertTokenAddress = (tokenAddress: string, networkName: string) => {
if (tokenAddress === 'NATIVE') {
switch (networkName) {
case Chain.Celo:
return '0x471EcE3750Da237f93B8E339c536989b8978a438'
case Chain.Polygon:
return '0x0000000000000000000000000000000000001010'
default:
return undefined
}
}
return tokenAddress
}
export const onRequest: PagesFunction = async ({ params, request, next }) => {
const res = next()
try {
const { index } = params
const networkName = index[0]?.toString().toUpperCase()
const tokenString = index[1]?.toString()
if (!tokenString) {
const networkName = index[0]?.toString()
const tokenAddress = index[1]?.toString()
if (!tokenAddress) {
return res
}
const tokenAddress = convertTokenAddress(tokenString, networkName)
return getRequest(res, request.url, () => getToken(networkName, tokenAddress, request.url))
return getMetadataRequest(res, request.url, () => getToken(networkName, tokenAddress, request.url))
} catch (e) {
return res
}

View File

@@ -7,15 +7,14 @@ exports[`should inject metadata for valid tokens 1`] = `
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
@@ -36,9 +35,9 @@ exports[`should inject metadata for valid tokens 1`] = `
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="manifest" href="/manifest.json" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@@ -55,9 +54,9 @@ exports[`should inject metadata for valid tokens 1`] = `
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
src: url(/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(/fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
@@ -116,7 +115,7 @@ exports[`should inject metadata for valid tokens 1`] = `
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get USDC on Uniswap"/><meta property="og:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get USDC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get USDC on Uniswap"/><meta property="twitter:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"/><meta property="twitter:image:alt" content="Get USDC on Uniswap"/></head>
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get USDC on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get USDC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get USDC on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"/><meta property="twitter:image:alt" content="Get USDC on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@@ -139,15 +138,14 @@ exports[`should inject metadata for valid tokens 2`] = `
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
@@ -168,9 +166,9 @@ exports[`should inject metadata for valid tokens 2`] = `
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="manifest" href="/manifest.json" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@@ -187,9 +185,9 @@ exports[`should inject metadata for valid tokens 2`] = `
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
src: url(/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(/fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
@@ -248,7 +246,7 @@ exports[`should inject metadata for valid tokens 2`] = `
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get ETH on Uniswap"/><meta property="og:image" content="https://token-icons.s3.amazonaws.com/eth.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get ETH on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/NATIVE"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get ETH on Uniswap"/><meta property="twitter:image" content="https://token-icons.s3.amazonaws.com/eth.png"/><meta property="twitter:image:alt" content="Get ETH on Uniswap"/></head>
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get ETH on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/NATIVE"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get ETH on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/NATIVE"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get ETH on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/NATIVE"/><meta property="twitter:image:alt" content="Get ETH on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@@ -271,15 +269,14 @@ exports[`should inject metadata for valid tokens 3`] = `
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
@@ -300,9 +297,9 @@ exports[`should inject metadata for valid tokens 3`] = `
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="manifest" href="/manifest.json" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@@ -319,9 +316,9 @@ exports[`should inject metadata for valid tokens 3`] = `
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
src: url(/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(/fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
@@ -380,7 +377,7 @@ exports[`should inject metadata for valid tokens 3`] = `
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get MATIC on Uniswap"/><meta property="og:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get MATIC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/polygon/NATIVE"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get MATIC on Uniswap"/><meta property="twitter:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"/><meta property="twitter:image:alt" content="Get MATIC on Uniswap"/></head>
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get MATIC on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/tokens/polygon/NATIVE"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get MATIC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/polygon/NATIVE"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get MATIC on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/tokens/polygon/NATIVE"/><meta property="twitter:image:alt" content="Get MATIC on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@@ -403,15 +400,14 @@ exports[`should inject metadata for valid tokens 4`] = `
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
. will be replaced with the URL of the \`public\` folder during build.
will be replaced with the URL of the \`public\` folder during build.
Only files inside the \`public\` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="./favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="./images/512x512_App_Icon.png" />
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/images/512x512_App_Icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#FC72FF" />
@@ -432,9 +428,9 @@ exports[`should inject metadata for valid tokens 4`] = `
manifest.json provides metadata used when the app is installed as a PWA.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="manifest" href="/manifest.json" />
<link rel="preload" href="./fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
@@ -451,9 +447,9 @@ exports[`should inject metadata for valid tokens 4`] = `
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(./fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(./fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(./fonts/Inter-roman.var.woff2) format('woff2');
src: url(/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(/fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
@@ -512,7 +508,7 @@ exports[`should inject metadata for valid tokens 4`] = `
}
}
</style>
<script defer src="./static/js/bundle.js"></script><meta property="og:title" content="Get PEPE on Uniswap"/><meta property="og:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6982508145454Ce325dDbE47a25d4ec3d2311933/logo.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get PEPE on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get PEPE on Uniswap"/><meta property="twitter:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6982508145454Ce325dDbE47a25d4ec3d2311933/logo.png"/><meta property="twitter:image:alt" content="Get PEPE on Uniswap"/></head>
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get PEPE on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get PEPE on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get PEPE on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933"/><meta property="twitter:image:alt" content="Get PEPE on Uniswap"/></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -3,28 +3,25 @@ const tokens = [
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
network: 'ethereum',
symbol: 'USDC',
image:
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png',
image: 'http://127.0.0.1:3000/api/image/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
},
{
address: 'NATIVE',
network: 'ethereum',
symbol: 'ETH',
image: 'https://token-icons.s3.amazonaws.com/eth.png',
image: 'http://127.0.0.1:3000/api/image/tokens/ethereum/NATIVE',
},
{
address: 'NATIVE',
network: 'polygon',
symbol: 'MATIC',
image:
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png',
image: 'http://127.0.0.1:3000/api/image/tokens/polygon/NATIVE',
},
{
address: '0x6982508145454ce325ddbe47a25d4ec3d2311933',
network: 'ethereum',
symbol: 'PEPE',
image:
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6982508145454Ce325dDbE47a25d4ec3d2311933/logo.png',
image: 'http://127.0.0.1:3000/api/image/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933',
},
]
@@ -33,6 +30,7 @@ test.each(tokens)('should inject metadata for valid tokens', async (token) => {
const body = await fetch(new Request(url)).then((res) => res.text())
expect(body).toMatchSnapshot()
expect(body).toContain(`<meta property="og:title" content="Get ${token.symbol} on Uniswap"/>`)
expect(body).not.toContain(`<meta property="og:description"`)
expect(body).toContain(`<meta property="og:image" content="${token.image}"/>`)
expect(body).toContain(`<meta property="og:image:width" content="1200"/>`)
expect(body).toContain(`<meta property="og:image:height" content="630"/>`)

View File

@@ -1,19 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"esModuleInterop": true,
"baseUrl": "functions",
"composite": false,
"incremental": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"moduleResolution": "node",
"module": "esnext",
"noEmit": true,
"strict": true,
"target": "ESNext",
"isolatedModules": false,
"jsx": "react",
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/functions", // avoid clobbering the build tsbuildinfo
"types": ["jest", "node", "@cloudflare/workers-types"],
"jsx": "react",
"skipLibCheck": true,
"baseUrl": "functions"
},
"exclude": ["../node_modules", "../src"],
"include": ["**/*.ts"],
"include": ["**/*.ts", ".ts", "**/*.tsx"],
}

1
functions/types.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module 'colorthief/src/color-thief-node'

View File

@@ -1,7 +1,11 @@
interface Data {
export interface Data {
title: string
image: string
url: string
name?: string
ogImage?: string
isVerified?: boolean
symbol?: string
}
const CACHE_NAME = 'functions-cache' as const

View File

@@ -1,7 +1,7 @@
import { AssetDocument } from '../../src/graphql/data/__generated__/types-and-hooks'
import { AssetDocument, AssetQuery } from '../../src/graphql/data/__generated__/types-and-hooks'
import client from '../client'
function formatTitleName(name: string, collectionName: string, tokenId: string) {
function formatTitleName(name: string | undefined, collectionName: string | undefined, tokenId: string) {
if (name) {
return name
}
@@ -15,7 +15,9 @@ function formatTitleName(name: string, collectionName: string, tokenId: string)
}
export default async function getAsset(collectionAddress: string, tokenId: string, url: string) {
const { data } = await client.query({
const origin = new URL(url).origin
const image = origin + '/api/image/nfts/asset/' + collectionAddress + '/' + tokenId
const { data } = await client.query<AssetQuery>({
query: AssetDocument,
variables: {
address: collectionAddress,
@@ -31,8 +33,9 @@ export default async function getAsset(collectionAddress: string, tokenId: strin
const title = formatTitleName(asset.name, asset.collection?.name, asset.tokenId)
const formattedAsset = {
title,
image: asset.image?.url,
image,
url,
ogImage: asset.image?.url ?? origin + '/images/192x192_App_Icon.png',
}
return formattedAsset
}

View File

@@ -1,8 +1,10 @@
import { CollectionDocument } from '../../src/graphql/data/__generated__/types-and-hooks'
import { CollectionDocument, CollectionQuery } from '../../src/graphql/data/__generated__/types-and-hooks'
import client from '../client'
export default async function getCollection(collectionAddress: string, url: string) {
const { data } = await client.query({
const origin = new URL(url).origin
const image = origin + '/api/image/nfts/collection/' + collectionAddress
const { data } = await client.query<CollectionQuery>({
query: CollectionDocument,
variables: {
addresses: collectionAddress,
@@ -14,8 +16,11 @@ export default async function getCollection(collectionAddress: string, url: stri
}
const formattedAsset = {
title: collection.name + ' on Uniswap',
image: collection.image?.url,
image,
url,
name: collection.name ?? 'Collection',
ogImage: collection.image?.url ?? origin + '/images/192x192_App_Icon.png',
isVerified: collection.isVerified ?? false,
}
return formattedAsset
}

View File

@@ -0,0 +1,39 @@
import { DEFAULT_COLOR } from '../constants'
import getColor from './getColor'
test('should return the average color of a black PNG image', async () => {
const image = 'https://static.vecteezy.com/system/resources/previews/001/209/957/original/square-png.png'
const color = await getColor(image)
expect(color).toEqual([0, 0, 0])
})
test('should return the average color of a blue PNG image', async () => {
const image = 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTB2Ztcim-RKbOu57kfjYpXnnS1MO5YMUaUH9Lk5Eg&s'
const color = await getColor(image)
expect(color).toEqual([2, 6, 251])
})
test('should return the average color of a white PNG image', async () => {
const image = 'https://www.cac.cornell.edu/wiki/images/4/44/White_square.png'
const color = await getColor(image)
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'
const color = await getColor(image)
expect(color).toEqual([0, 0, 0])
})
test('should return default color for a gif image', async () => {
const image = 'https://thumbs.gfycat.com/AgitatedLiveAgouti-size_restricted.gif'
const color = await getColor(image)
expect(color).toEqual(DEFAULT_COLOR)
})

View File

@@ -0,0 +1,75 @@
import { Buffer } from 'buffer'
import JPEG from 'jpeg-js'
import PNG from 'png-ts'
import { DEFAULT_COLOR, predefinedTokenColors } from '../constants'
export default async function getColor(image: string | undefined, checkDistance = false) {
if (!image) {
return DEFAULT_COLOR
}
if (image in predefinedTokenColors) {
return predefinedTokenColors[image]
}
try {
const data = await fetch(image)
const buffer = await data.arrayBuffer()
const arrayBuffer = Buffer.from(buffer)
const type = data.headers.get('content-type') ?? ''
return getAverageColor(arrayBuffer, type, checkDistance)
} catch (e) {
return DEFAULT_COLOR
}
}
function getAverageColor(arrayBuffer: Uint8Array, type: string, checkDistance: boolean) {
let pixels
switch (type) {
case 'image/png': {
const image = PNG.load(arrayBuffer)
pixels = image.decode()
break
}
case 'image/jpeg' || 'image/jpg': {
const jpeg = JPEG.decode(arrayBuffer, { useTArray: true })
pixels = jpeg.data
break
}
default: {
return DEFAULT_COLOR
}
}
const pixelCount = pixels.length / 4
let transparentPixels = 0
let r = 0
let g = 0
let b = 0
for (let i = 0; i < pixelCount; i++) {
if (pixels[i * 4 + 3] === 0) {
transparentPixels++
continue
}
r += pixels[i * 4]
g += pixels[i * 4 + 1]
b += pixels[i * 4 + 2]
}
r = Math.floor(r / (pixelCount - transparentPixels))
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

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

@@ -0,0 +1,16 @@
import { Chain } from '../../src/graphql/data/__generated__/types-and-hooks'
export default function getNetworkLogoUrl(network: string, origin: string) {
switch (network) {
case Chain.Polygon:
return origin + '/images/logos/Polygon_Logo.png'
case Chain.Arbitrum:
return origin + '/images/logos/Arbitrum_Logo.png'
case Chain.Optimism:
return origin + '/images/logos/Optimism_Logo.png'
case Chain.Celo:
return origin + '/images/logos/Celo_Logo.png'
default:
return ''
}
}

View File

@@ -2,8 +2,8 @@ import * as matchers from 'jest-extended'
expect.extend(matchers)
import { mocked } from '../../src/test-utils/mocked'
import Cache from './cache'
import getRequest from './getRequest'
import Cache, { Data } from './cache'
import { getRequest } from './getRequest'
jest.mock('./cache', () => ({
match: jest.fn(),
@@ -17,7 +17,7 @@ test('should call Cache.match before calling getData when request is not cached'
image: 'testImage',
url: 'testUrl',
})
await getRequest(Promise.resolve(new Response()), url, getData)
await getRequest(url, getData, (data): data is Data => true)
expect(Cache.match).toHaveBeenCalledWith(url)
expect(getData).toHaveBeenCalled()
expect(Cache.match).toHaveBeenCalledBefore(getData)
@@ -32,7 +32,7 @@ test('getData should not be called when request is cached', async () => {
url: 'testUrl',
})
const getData = jest.fn()
await getRequest(Promise.resolve(new Response()), url, getData)
await getRequest(url, getData, (data): data is Data => true)
expect(Cache.match).toHaveBeenCalledWith(url)
expect(getData).not.toHaveBeenCalled()
})

View File

@@ -1,31 +1,42 @@
import { MetaTagInjector } from '../components/metaTagInjector'
import Cache from './cache'
import { Data } from './cache'
export default async function getRequest(
export async function getMetadataRequest(
res: Promise<Response>,
url: string,
getData: () => Promise<
| {
title: string
image: string
url: string
}
| undefined
>
getData: () => Promise<Data | undefined>
) {
try {
const cachedData = await Cache.match(url)
const cachedData = await getRequest(url, getData, (data): data is Data => true)
if (cachedData) {
return new HTMLRewriter().on('head', new MetaTagInjector(cachedData)).transform(await res)
} else {
const data = await getData()
if (!data) {
return res
}
await Cache.put(data, url)
return new HTMLRewriter().on('head', new MetaTagInjector(data)).transform(await res)
return res
}
} catch (e) {
return res
}
}
export async function getRequest<T extends Data>(
url: string,
getData: () => Promise<T | undefined>,
validateData: (data: Data) => data is T
): Promise<T | undefined> {
try {
const cachedData = await Cache.match(url)
if (cachedData && validateData(cachedData)) {
return cachedData
} else {
const data = await getData()
if (!data) {
return undefined
}
await Cache.put(data, url)
return data
}
} catch (e) {
return undefined
}
}

View File

@@ -1,7 +1,8 @@
import { TokenDocument } from '../../src/graphql/data/__generated__/types-and-hooks'
import { TokenDocument, TokenQuery } from '../../src/graphql/data/__generated__/types-and-hooks'
import { Chain } from '../../src/graphql/data/__generated__/types-and-hooks'
import client from '../client'
function formatTitleName(symbol: string, name: string) {
function formatTitleName(symbol: string | undefined, name: string | undefined) {
if (symbol) {
return 'Get ' + symbol + ' on Uniswap'
}
@@ -11,23 +12,46 @@ function formatTitleName(symbol: string, name: string) {
return 'View Token on Uniswap'
}
export default async function getToken(networkName: string, tokenAddress: string | undefined, url: string) {
const { data } = await client.query({
const convertTokenAddress = (networkName: string, tokenAddress: string) => {
if (tokenAddress === 'NATIVE') {
switch (networkName) {
case Chain.Celo:
return '0x471EcE3750Da237f93B8E339c536989b8978a438'
case Chain.Polygon:
return '0x0000000000000000000000000000000000001010'
default:
return undefined
}
}
return tokenAddress
}
export default async function getToken(networkName: string, tokenAddress: string, url: string) {
const origin = new URL(url).origin
const image = origin + '/api/image/tokens/' + networkName + '/' + tokenAddress
const uppercaseNetworkName = networkName.toUpperCase()
const convertedTokenAddress = convertTokenAddress(uppercaseNetworkName, tokenAddress)
const { data } = await client.query<TokenQuery>({
query: TokenDocument,
variables: {
chain: networkName,
address: tokenAddress,
chain: uppercaseNetworkName,
address: convertedTokenAddress,
},
})
const asset = data?.token
if (!asset) {
return undefined
}
const title = formatTitleName(asset.symbol, asset.name)
const formattedAsset = {
title,
image: asset.project?.logoUrl,
image,
url,
symbol: asset.symbol ?? 'UNK',
ogImage: asset.project?.logoUrl,
name: asset.name ?? 'Token',
}
return formattedAsset
}

View File

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

View File

@@ -2,7 +2,6 @@
"name": "@uniswap/interface",
"version": "1.1.0",
"description": "Uniswap Interface",
"homepage": ".",
"license": "GPL-3.0-or-later",
"scripts": {
"ajv": "node scripts/compile-ajv-validators.js",
@@ -19,9 +18,8 @@
"i18n": "yarn i18n:extract --clean && yarn i18n:compile",
"prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\"",
"start": "craco start",
"start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --node-compat --proxy=3001 --port=3000 -- yarn start",
"start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --compatibility-flags=nodejs_compat --compatibility-date=2023-08-01 --proxy=3001 --port=3000 -- yarn start",
"build": "craco build",
"build:e2e": "REACT_APP_CSP_ALLOW_UNSAFE_EVAL=true REACT_APP_ADD_COVERAGE_INSTRUMENTATION=true craco build",
"analyze": "source-map-explorer 'build/static/js/*.js' --only-mapped",
"serve": "serve build -l 3000",
"lint": "yarn eslint --ignore-path .gitignore --cache --cache-location node_modules/.cache/eslint/ .",
@@ -32,7 +30,18 @@
"test:cloud": "NODE_OPTIONS=--experimental-vm-modules yarn jest functions --config=functions/jest.config.json",
"cypress:open": "cypress open --browser chrome --e2e",
"cypress:run": "cypress run --browser chrome --e2e",
"deduplicate": "yarn-deduplicate --strategy=highest"
"deduplicate": "yarn-deduplicate --strategy=highest",
"postinstall": "yarn patch-package"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"yarn.lock": [
"yarn deduplicate"
]
},
"jest": {
"collectCoverageFrom": [
@@ -59,9 +68,8 @@
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
">0.5%",
"not dead"
],
"development": [
"last 1 chrome version",
@@ -70,12 +78,15 @@
]
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.22.7",
"@cloudflare/workers-types": "^4.20230710.1",
"@craco/craco": "^7.1.0",
"@ethersproject/experimental": "^5.4.0",
"@lingui/cli": "^3.9.0",
"@lingui/cli": "^4.3.0",
"@lingui/swc-plugin": "^4.0.4",
"@swc/core": "^1.3.72",
"@swc/jest": "^0.2.27",
"@swc/plugin-styled-components": "^1.5.70",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.1",
"@testing-library/user-event": "^14.4.3",
@@ -83,11 +94,11 @@
"@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",
"@types/ms.macro": "^2.0.0",
"@types/ms": "^0.7.31",
"@types/multicodec": "^1.0.0",
"@types/node": "^13.13.5",
"@types/qs": "^6.9.2",
@@ -106,12 +117,11 @@
"@types/wcag-contrast": "^3.0.0",
"@uniswap/default-token-list": "^11.2.0",
"@uniswap/eslint-config": "^1.2.0",
"@vanilla-extract/babel-plugin": "^1.1.7",
"@vanilla-extract/jest-transform": "^1.1.1",
"@vanilla-extract/webpack-plugin": "^2.1.11",
"@vanilla-extract/webpack-plugin": "^2.2.0",
"@vercel/og": "0.5.8",
"@walletconnect/types": "^2.8.6",
"babel-jest": "^29.6.1",
"babel-plugin-istanbul": "^6.1.1",
"browser-cache-mock": "^0.1.7",
"buffer": "^6.0.3",
"concurrently": "^8.0.1",
@@ -122,58 +132,66 @@
"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",
"jest-fail-on-console": "^3.1.1",
"jest-fetch-mock": "^3.0.3",
"jest-styled-components": "^7.0.8",
"ms.macro": "^2.0.0",
"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",
"png-ts": "^0.0.3",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.8.8",
"process": "^0.11.10",
"react-scripts": "^5.0.1",
"resize-observer-polyfill": "^1.5.1",
"serve": "^11.3.2",
"source-map-explorer": "^2.5.3",
"swc-loader": "^0.2.3",
"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.4.3",
"typescript": "^4.9.4",
"webpack": "^5.88.2",
"webpack-retry-chunk-load-plugin": "^3.1.1",
"wrangler": "https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/4925945367/npm-package-wrangler-3048",
"wrangler": "^3.5.0",
"yarn-deduplicate": "^6.0.0"
},
"dependencies": {
"@apollo/client": "^3.7.2",
"@coinbase/wallet-sdk": "^3.6.4",
"@cypress/code-coverage": "^3.10.0",
"@fontsource/ibm-plex-mono": "^4.5.1",
"@fontsource/inter": "^4.5.1",
"@graphql-codegen/cli": "^2.15.0",
"@graphql-codegen/client-preset": "^1.2.1",
"@graphql-codegen/typescript": "^2.8.3",
"@graphql-codegen/typescript-operations": "^2.5.8",
"@graphql-codegen/cli": "^3.3.1",
"@graphql-codegen/client-preset": "^3.0.1",
"@graphql-codegen/typescript": "^3.0.4",
"@graphql-codegen/typescript-operations": "^3.0.2",
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
"@graphql-codegen/typescript-resolvers": "^2.7.8",
"@graphql-codegen/typescript-resolvers": "^3.2.1",
"@juggle/resize-observer": "^3.4.0",
"@lingui/core": "^3.14.0",
"@lingui/macro": "^3.14.0",
"@lingui/react": "^3.14.0",
"@lingui/core": "^4.3.0",
"@lingui/macro": "^4.3.0",
"@lingui/react": "^4.3.0",
"@looksrare/sdk": "^0.10.2",
"@metamask/jazzicon": "^2.0.0",
"@opensea/seaport-js": "^1.2.0",
"@popperjs/core": "^2.4.4",
"@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3",
"@reduxjs/toolkit": "^1.6.1",
"@reduxjs/toolkit": "^1.9.3",
"@sentry/react": "^7.45.0",
"@sentry/tracing": "^7.45.0",
"@sentry/types": "^7.45.0",
"@types/react-window-infinite-loader": "^1.0.6",
"@uniswap/analytics": "^1.4.0",
"@uniswap/analytics-events": "^2.14.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",
@@ -183,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.1.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",
@@ -191,10 +209,10 @@
"@uniswap/v3-core": "^1.0.1",
"@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-sdk": "^3.10.0",
"@vanilla-extract/css": "^1.7.2",
"@vanilla-extract/css-utils": "^0.1.2",
"@vanilla-extract/dynamic": "^2.0.2",
"@vanilla-extract/sprinkles": "^1.4.1",
"@vanilla-extract/css": "^1.12.0",
"@vanilla-extract/css-utils": "^0.1.3",
"@vanilla-extract/dynamic": "^2.0.3",
"@vanilla-extract/sprinkles": "^1.6.1",
"@visx/axis": "^2.12.2",
"@visx/event": "^2.6.0",
"@visx/glyph": "^2.10.0",
@@ -202,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",
@@ -221,6 +239,7 @@
"copy-to-clipboard": "^3.2.0",
"d3": "^7.6.1",
"ethers": "^5.7.2",
"ext-name": "^5.0.0",
"focus-visible": "^5.2.0",
"get-graphql-schema": "^2.1.2",
"graphql": "^16.5.0",
@@ -229,7 +248,9 @@
"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",
"multihashes": "^4.0.2",
"node-vibrant": "^3.2.1-alpha.1",
@@ -258,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

@@ -0,0 +1,50 @@
diff --git a/node_modules/@vercel/og/dist/index.edge.js b/node_modules/@vercel/og/dist/index.edge.js
index 5187f88..c4a1c41 100644
--- a/node_modules/@vercel/og/dist/index.edge.js
+++ b/node_modules/@vercel/og/dist/index.edge.js
@@ -18673,8 +18673,8 @@ var Resvg2 = class extends Resvg {
};
// src/index.edge.ts
-import resvg_wasm from "./resvg.wasm?module";
-import yoga_wasm from "./yoga.wasm?module";
+import resvg_wasm from "./resvg.wasm";
+import yoga_wasm from "./yoga.wasm";
// src/emoji/index.ts
var U200D = String.fromCharCode(8205);
@@ -18809,18 +18809,18 @@ async function render(satori, resvg, opts, defaultFonts, element) {
// src/index.edge.ts
var initializedResvg = initWasm(resvg_wasm);
var initializedYoga = initYoga(yoga_wasm).then((yoga2) => Ll(yoga2));
-var fallbackFont = fetch(new URL("./noto-sans-v27-latin-regular.ttf", import.meta.url)).then((res) => res.arrayBuffer());
+// var fallbackFont = fetch(new URL("https://fonts.gstatic.com/s/notosans/v28/o-0IIpQlx3QUlC5A4PNr6zRF.ttf", import.meta.url)).then((res) => res.arrayBuffer());
var ImageResponse = class {
constructor(element, options = {}) {
const result = new ReadableStream({
async start(controller) {
await initializedYoga;
await initializedResvg;
- const fontData = await fallbackFont;
+ // const fontData = await fallbackFont;
const fonts = [
{
name: "sans serif",
- data: fontData,
+ // data: fontData,
weight: 700,
style: "normal"
}
diff --git a/node_modules/@vercel/og/dist/types.d.ts b/node_modules/@vercel/og/dist/types.d.ts
index dde26cc..eb59ff4 100644
--- a/node_modules/@vercel/og/dist/types.d.ts
+++ b/node_modules/@vercel/og/dist/types.d.ts
@@ -30,7 +30,7 @@ declare type ImageOptions = {
* @type {{ data: ArrayBuffer; name: string; weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; style?: 'normal' | 'italic' }[]}
* @default Noto Sans Latin Regular.
*/
- fonts?: SatoriOptions['fonts'];
+ fonts: SatoriOptions['fonts'];
/**
* Using a specific Emoji style. Defaults to `twemoji`.
*

View File

@@ -5,29 +5,25 @@
"namespace": "android_app",
"package_name": "com.uniswap",
"sha256_cert_fingerprints":
["97:A5:81:51:DA:AF:8F:6E:65:3A:90:1E:82:12:6C:FB:61:2D:36:C7:CF:20:61:6B:A3:4C:52:CA:BC:58:43:8E"]
["97:A5:81:51:DA:AF:8F:6E:65:3A:90:1E:82:12:6C:FB:61:2D:36:C7:CF:20:61:6B:A3:4C:52:CA:BC:58:43:8E", "F9:E9:E3:F0:04:28:66:62:81:44:50:7E:D6:A9:5F:B9:65:39:02:70:1D:13:74:15:D3:E1:A3:1B:D4:38:3A:1F"]
}
},
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.uniswap.beta",
"sha256_cert_fingerprints":
["E5:39:87:DC:4D:FD:4C:1B:A6:74:36:7D:3A:3B:6B:ED:9E:B3:66:89:92:8A:1B:B8:FC:1B:22:56:56:B4:46:A3"]
}
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.uniswap.beta",
"sha256_cert_fingerprints":
["E5:39:87:DC:4D:FD:4C:1B:A6:74:36:7D:3A:3B:6B:ED:9E:B3:66:89:92:8A:1B:B8:FC:1B:22:56:56:B4:46:A3", "54:4B:62:33:17:9B:5F:A8:E6:5D:D3:A6:E5:9D:80:5F:A5:02:7F:E2:14:B8:C1:7A:AC:4B:8D:E0:65:49:87:41"]
}
],
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"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"]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.uniswap.dev",
"sha256_cert_fingerprints":
["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.

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

@@ -4,7 +4,6 @@
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
%PUBLIC_URL% will be replaced with the URL of the `public` folder during build.

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

@@ -0,0 +1,3 @@
<svg width="119" height="99" viewBox="0 0 119 99" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M118.097 29.6061C117.891 67.6376 86.8404 98.3016 48.7425 98.0958C12.5975 97.9006 -16.9048 69.9894 -19.659 34.6507L71.5199 35.1431L71.5825 23.5662L-19.5965 23.0738C-16.4608 -12.2332 13.3412 -39.824 49.4863 -39.6288C87.5842 -39.423 118.302 -8.4256 118.097 29.6061Z" fill="white" fill-opacity="0.1"/>
</svg>

After

Width:  |  Height:  |  Size: 452 B

View File

@@ -1,7 +1,7 @@
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
import { TraceEvent } from 'analytics'
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
import styled from 'styled-components/macro'
import styled from 'styled-components'
import { BREAKPOINTS, ExternalLink, StyledRouterLink } from 'theme'
import { useIsDarkMode } from 'theme/components/ThemeToggle'

View File

@@ -1,7 +1,7 @@
import { BrowserEvent, SharedEventName } from '@uniswap/analytics-events'
import { TraceEvent } from 'analytics'
import { Link } from 'react-router-dom'
import styled, { DefaultTheme } from 'styled-components/macro'
import styled, { DefaultTheme } from 'styled-components'
import { BREAKPOINTS } from 'theme'
import { useIsDarkMode } from 'theme/components/ThemeToggle'

View File

@@ -1,4 +1,4 @@
import styled from 'styled-components/macro'
import styled from 'styled-components'
import { ReactComponent as DiscordI } from './images/discord.svg'
import { ReactComponent as GithubI } from './images/github.svg'

View File

@@ -1,5 +1,5 @@
import { ButtonEmpty } from 'components/Button'
import styled from 'styled-components/macro'
import styled from 'styled-components'
import { BREAKPOINTS } from 'theme'
import { useIsDarkMode } from 'theme/components/ThemeToggle'

View File

@@ -1,6 +1,6 @@
import { InterfaceElementName } from '@uniswap/analytics-events'
import { DollarSign, Terminal } from 'react-feather'
import styled from 'styled-components/macro'
import styled from 'styled-components'
import { lightTheme } from 'theme/colors'
import darkArrowImgSrc from './images/aboutArrowDark.png'

View File

@@ -1,6 +1,5 @@
import { Trans } from '@lingui/macro'
import { BrowserEvent, InterfaceElementName, InterfaceEventName, SharedEventName } from '@uniswap/analytics-events'
import { formatNumber, NumberType } from '@uniswap/conedison/format'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { sendAnalyticsEvent, TraceEvent } from 'analytics'
@@ -21,9 +20,10 @@ import { ArrowDownRight, ArrowUpRight, CreditCard, IconProps, Info, LogOut, Sett
import { useNavigate } from 'react-router-dom'
import { useAppDispatch } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer'
import styled, { useTheme } from 'styled-components/macro'
import styled, { useTheme } from 'styled-components'
import { CopyHelper, ExternalLink, ThemedText } from 'theme'
import { shortenAddress } from 'utils'
import { formatNumber, NumberType } from 'utils/formatNumbers'
import { useCloseModal, useFiatOnrampAvailability, useOpenModal, useToggleModal } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'

View File

@@ -2,9 +2,10 @@ import { useWeb3React } from '@web3-react/core'
import Column from 'components/Column'
import WalletModal from 'components/WalletModal'
import { useCallback, useEffect, useState } from 'react'
import styled from 'styled-components/macro'
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

@@ -1,6 +1,6 @@
import { InterfaceElementName } from '@uniswap/analytics-events'
import { PropsWithChildren, useCallback } from 'react'
import styled from 'styled-components/macro'
import styled from 'styled-components'
import { ClickableStyle } from 'theme'
import { openDownloadApp } from 'utils/openDownloadApp'

View File

@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import Tooltip from 'components/Tooltip'
import useCopyClipboard from 'hooks/useCopyClipboard'
import styled from 'styled-components/macro'
import styled from 'styled-components'
import { ThemedText } from 'theme'
const Container = styled.div`

View File

@@ -1,6 +1,6 @@
import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react'
import { Icon } from 'react-feather'
import styled, { css, DefaultTheme } from 'styled-components/macro'
import styled, { css, DefaultTheme } from 'styled-components'
import useResizeObserver from 'use-resize-observer'
import { TRANSITION_DURATIONS } from '../../theme/styles'

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

@@ -7,7 +7,7 @@ import Row from 'components/Row'
import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
import useENSName from 'hooks/useENSName'
import { useCallback } from 'react'
import styled from 'styled-components/macro'
import styled from 'styled-components'
import { EllipsisStyle, ThemedText } from 'theme'
import { shortenAddress } from 'utils'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'

View File

@@ -16,7 +16,7 @@ import { X } from 'react-feather'
import { InterfaceTrade } from 'state/routing/types'
import { useOrder } from 'state/signatures/hooks'
import { UniswapXOrderDetails } from 'state/signatures/types'
import styled from 'styled-components/macro'
import styled from 'styled-components'
import { ExternalLink, ThemedText } from 'theme'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
@@ -77,6 +77,7 @@ function Loader() {
}
const Success = styled(AnimatedEntranceConfirmationIcon)`
position: relative;
margin-bottom: 10px;
`
@@ -248,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

@@ -19,7 +19,8 @@ function findCancelTx(localActivity: Activity, remoteMap: ActivityMap, account:
if (
remoteTx.nonce === localActivity.nonce &&
remoteTx.from.toLowerCase() === account.toLowerCase() &&
remoteTx.hash.toLowerCase() !== localActivity.hash.toLowerCase()
remoteTx.hash.toLowerCase() !== localActivity.hash.toLowerCase() &&
remoteTx.chainId === localActivity.chainId
) {
return remoteTx.hash
}
@@ -37,6 +38,11 @@ function combineActivities(localMap: ActivityMap = {}, remoteMap: ActivityMap =
const remoteActivity = (remoteMap?.[hash] ?? {}) as Activity
if (localActivity.cancelled) {
// Hides misleading activities caused by cross-chain nonce collisions previously being incorrectly labelled as cancelled txs in redux
if (localActivity.chainId !== remoteActivity.chainId) {
acc.push(remoteActivity)
return acc
}
// Remote data only contains data of the cancel tx, rather than the original tx, so we prefer local data here
acc.push(localActivity)
} else {
@@ -67,6 +73,7 @@ export function useAllActivities(account: string) {
if (!localActivity) return
const cancelHash = findCancelTx(localActivity, remoteMap, account)
if (cancelHash) updateCancelledTx(localActivity.hash, localActivity.chainId, cancelHash)
})
}, [account, localMap, remoteMap, updateCancelledTx])
@@ -79,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()

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